From f82bf1215ad8023bedf8e1d05532a90443eb5e48 Mon Sep 17 00:00:00 2001 From: Simeon Radivoev Date: Thu, 7 May 2026 04:08:17 +0300 Subject: [PATCH 1/7] doc: Added discord link --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d806d89..0e5864e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Gameflow Deck +# Gameflow Deck A Cross-Platform open source Retro gaming frontend designed for handheld and controllers. Focused on building a simple user experience and intuitive UI as a curated community driven experience. @@ -7,6 +7,12 @@ Focused on building a simple user experience and intuitive UI as a curated commu > This app is actively in development, it is constantly changing and improving. > It will have an opinionated design and will be used as an experiment in discovering a good UX. +## Community + +Join us on Discord, where you can ask questions, submit ideas and get help. + +[![](https://invidget.switchblade.xyz/R9KakhY67d)](https://discord.gg/R9KakhY67d) + ## Features ### Integrations From 9051834aceb1dce56e85cfb78a3a5f8455e5846f Mon Sep 17 00:00:00 2001 From: Simeon Radivoev Date: Thu, 7 May 2026 14:43:48 +0300 Subject: [PATCH 2/7] chore: Updated packages --- bun.lock | 436 +++++++++++++++---------------- package.json | 76 +++--- src/bun/api/jobs/update-store.ts | 36 +-- 3 files changed, 266 insertions(+), 282 deletions(-) diff --git a/bun.lock b/bun.lock index 3568727..5e7c073 100644 --- a/bun.lock +++ b/bun.lock @@ -108,19 +108,19 @@ "packages": { "7zip-bin": ["7zip-bin@5.2.0", "", {}, "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A=="], - "@ap0nia/eden": ["@ap0nia/eden@1.0.0-next.22", "", { "peerDependencies": { "elysia": "^1.3.1" } }, "sha512-9iH09koK29Yuem80fz8nCt9iHVcJqxUo2QHAr4psI02PhvL70n6aWVo/hlHyYXwOSsSgRQlLl1vPmiulFOUFoA=="], + "@ap0nia/eden": ["@ap0nia/eden@1.6.1", "", { "dependencies": { "elysia": "1.2.15" } }, "sha512-jlsUyh4PsYNnMcPuQ3IJq0hhDNnyRNGYx+MSAJlcgKs4En9qrokLorSbTRvVjA1Mdx4VdzEADcPn99Kbph0SOw=="], "@ap0nia/eden-tanstack-query": ["@ap0nia/eden-tanstack-query@1.0.0-next.22", "", {}, "sha512-eSQ98G4TYzrAdsfRekrvqIrTqrAUFy+YpibZ5fj5KL6/R6FcrS2U2F51iML98baXT4MTpOJARY9p+7x0hiA8Qw=="], "@auth/core": ["@auth/core@0.34.3", "", { "dependencies": { "@panva/hkdf": "^1.1.1", "@types/cookie": "0.6.0", "cookie": "0.6.0", "jose": "^5.1.3", "oauth4webapi": "^2.10.4", "preact": "10.11.3", "preact-render-to-string": "5.2.3" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^7" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-jMjY/S0doZnWYNV90x0jmU3B+UcrsfGYnukxYrRbj0CVvGI/MX3JbHsxSrx2d4mbnXaUsqJmAcDfoQWA6r0lOw=="], - "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], - "@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], - "@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], @@ -140,7 +140,7 @@ "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], - "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + "@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], @@ -162,9 +162,9 @@ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], - "@elysiajs/cors": ["@elysiajs/cors@1.4.1", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-lQfad+F3r4mNwsxRKbXyJB8Jg43oAOXjRwn7sKUL6bcOW3KjUqUimTS+woNpO97efpzjtDE0tEjGk9DTw8lqTQ=="], + "@elysiajs/cors": ["@elysiajs/cors@1.4.2", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-FTCcbH35brTLigF1W7BYySRZomgI/dBEMK9BgK9RP9Nez7zmpGh4koL/Yr1BFv8nYz7CfhRvcM8d/c+XnwMaVQ=="], - "@elysiajs/eden": ["@elysiajs/eden@1.4.6", "", { "peerDependencies": { "elysia": ">=1.4.19" } }, "sha512-Tsa4NwXEWg/u73vWiYZQ3L5/ecgZSxqiEjYwpS+4qBKXeTZqZKl2hcgHJSVBL+InEDMi35Xugct7qyAXE5oM4Q=="], + "@elysiajs/eden": ["@elysiajs/eden@1.4.9", "", { "peerDependencies": { "elysia": ">=1.4.19" } }, "sha512-3CKVD4ycVjB8nCNssfmhnUuq3SzSHkUES3v5PNCFr9LxIrx39/HVRAZ8z2sLxrFqzUs48dCBZaxoZzJ5UUVHDA=="], "@emulatorjs/core-81": ["@emulatorjs/core-81@4.2.3", "", { "dependencies": { "@emulatorjs/emulatorjs": "latest" } }, "sha512-oPQEqjpR3z7Yedte4u3sOXDZ4NXAykNcbENjYcB+x3QshF8I+3MQCo8kINOT2lsqqgx91WR4kmEaYQqU39YsDA=="], @@ -324,13 +324,13 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "@hey-api/codegen-core": ["@hey-api/codegen-core@0.6.0", "", { "dependencies": { "@hey-api/types": "0.1.3", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-O6TRP3g1188gdpZC9yio64KkvWD97QpLjaCFnwTgykTZtzQXZbkyw+lYPVfZ7Ee+m7eLau1/jEJmQcUY9G0sLw=="], + "@hey-api/codegen-core": ["@hey-api/codegen-core@0.6.1", "", { "dependencies": { "@hey-api/types": "0.1.3", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-khTIpxhKEAqmRmeLUnAFJQs4Sbg9RPokovJk9rRcC8B5MWH1j3/BRSqfpAIiJUBDU1+nbVg2RVCV+eQ174cdvw=="], "@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.2.3", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.1", "lodash": "^4.17.21" } }, "sha512-gRbyyTjzpFVNmbD+Upn3w4dWV+bCXGJbff3A7leDO/tfNxSz1xIb6Ad/5UKtvEW9kDt/2Uyc3XkFZ6rpafvbfQ=="], - "@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.91.0", "", { "dependencies": { "@hey-api/codegen-core": "0.6.0", "@hey-api/json-schema-ref-parser": "1.2.3", "@hey-api/shared": "0.1.0", "@hey-api/types": "0.1.3", "ansi-colors": "4.1.3", "color-support": "1.1.3", "commander": "14.0.2" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-AHkd982HsPz1XpqRm59URwJyJqTzyzzC30EAp07b/0M9KojjneCPxm8FnvFnXLRTMyKgcOymMsYXuLzJ9mpMHA=="], + "@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.91.1", "", { "dependencies": { "@hey-api/codegen-core": "0.6.1", "@hey-api/json-schema-ref-parser": "1.2.3", "@hey-api/shared": "0.1.1", "@hey-api/types": "0.1.3", "ansi-colors": "4.1.3", "color-support": "1.1.3", "commander": "14.0.2" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-d16WR35UtthK/ihAIwJaKxrj/zvb5LbYwtVJCyZFFMin2qzDU8Y3Lpk78ensAykrLoaDLzpd0iIyt9JCP5Qmww=="], - "@hey-api/shared": ["@hey-api/shared@0.1.0", "", { "dependencies": { "@hey-api/codegen-core": "0.6.0", "@hey-api/json-schema-ref-parser": "1.2.3", "@hey-api/types": "0.1.3", "ansi-colors": "4.1.3", "cross-spawn": "7.0.6", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-qEDMSBWEEWxcBU5XHacjCCnFOVq1YWPPR3owURVep60I7ejfSG5OINxM4eF+p3KJGMcZduzzfq9pd1grStHZBg=="], + "@hey-api/shared": ["@hey-api/shared@0.1.1", "", { "dependencies": { "@hey-api/codegen-core": "0.6.1", "@hey-api/json-schema-ref-parser": "1.2.3", "@hey-api/types": "0.1.3", "ansi-colors": "4.1.3", "cross-spawn": "7.0.6", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-/irgNGXw9TL5aKB3S7jCLgh07vgDFkYjSjz7vEWO9xEe6MUhx76zSFzkPspk2UrLghYayvmaKPf1ky4XjNI9ZQ=="], "@hey-api/types": ["@hey-api/types@0.1.3", "", { "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-mZaiPOWH761yD4GjDQvtjS2ZYLu5o5pI1TVSvV/u7cmbybv51/FVtinFBeaE1kFQCKZ8OQpn2ezjLBJrKsGATw=="], @@ -342,63 +342,63 @@ "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "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" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - "@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="], + "@jimp/core": ["@jimp/core@1.6.1", "", { "dependencies": { "@jimp/file-ops": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^21.3.3", "mime": "3" } }, "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A=="], - "@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="], + "@jimp/diff": ["@jimp/diff@1.6.1", "", { "dependencies": { "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "pixelmatch": "^5.3.0" } }, "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ=="], - "@jimp/file-ops": ["@jimp/file-ops@1.6.0", "", {}, "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ=="], + "@jimp/file-ops": ["@jimp/file-ops@1.6.1", "", {}, "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w=="], - "@jimp/js-bmp": ["@jimp/js-bmp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "bmp-ts": "^1.0.9" } }, "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw=="], + "@jimp/js-bmp": ["@jimp/js-bmp@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "bmp-ts": "^1.0.9" } }, "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ=="], - "@jimp/js-gif": ["@jimp/js-gif@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g=="], + "@jimp/js-gif": ["@jimp/js-gif@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w=="], - "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "jpeg-js": "^0.4.4" } }, "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA=="], + "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "jpeg-js": "^0.4.4" } }, "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg=="], - "@jimp/js-png": ["@jimp/js-png@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "pngjs": "^7.0.0" } }, "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg=="], + "@jimp/js-png": ["@jimp/js-png@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "pngjs": "^7.0.0" } }, "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA=="], - "@jimp/js-tiff": ["@jimp/js-tiff@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "utif2": "^4.1.0" } }, "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw=="], + "@jimp/js-tiff": ["@jimp/js-tiff@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "utif2": "^4.1.0" } }, "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw=="], - "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA=="], + "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ=="], - "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw=="], + "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/utils": "1.6.1" } }, "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ=="], - "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw=="], + "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ=="], - "@jimp/plugin-color": ["@jimp/plugin-color@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA=="], + "@jimp/plugin-color": ["@jimp/plugin-color@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A=="], - "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ=="], + "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/plugin-blit": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg=="], - "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA=="], + "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/plugin-crop": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg=="], - "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang=="], + "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg=="], - "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q=="], + "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g=="], - "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0" } }, "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ=="], + "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1" } }, "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew=="], - "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA=="], + "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA=="], - "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg=="], + "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA=="], - "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "any-base": "^1.1.0" } }, "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q=="], + "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/js-bmp": "1.6.1", "@jimp/js-jpeg": "1.6.1", "@jimp/js-png": "1.6.1", "@jimp/js-tiff": "1.6.1", "@jimp/plugin-color": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "any-base": "^1.1.0" } }, "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A=="], - "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA=="], + "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A=="], - "@jimp/plugin-print": ["@jimp/plugin-print@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/types": "1.6.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A=="], + "@jimp/plugin-print": ["@jimp/plugin-print@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/js-jpeg": "1.6.1", "@jimp/js-png": "1.6.1", "@jimp/plugin-blit": "1.6.1", "@jimp/types": "1.6.1", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w=="], - "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.0", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg=="], + "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.1", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww=="], - "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA=="], + "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg=="], - "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw=="], + "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/plugin-crop": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg=="], - "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w=="], + "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/plugin-color": "1.6.1", "@jimp/plugin-hash": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q=="], - "@jimp/types": ["@jimp/types@1.6.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg=="], + "@jimp/types": ["@jimp/types@1.6.1", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w=="], - "@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="], + "@jimp/utils": ["@jimp/utils@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "tinycolor2": "^1.6.0" } }, "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw=="], - "@jimp/wasm-webp": ["@jimp/wasm-webp@1.6.0", "", { "dependencies": { "@jsquash/webp": "^1.4.0", "zod": "^3.23.8" } }, "sha512-P0zUpK6n2XIAn8bt0F6rhSn1+FgteBTrL+TBb6Oqw8v5qEDJoNYkd6LlfZYN8YwtRBTBdZ8GFnWsg2Sar+qOkA=="], + "@jimp/wasm-webp": ["@jimp/wasm-webp@1.6.1", "", { "dependencies": { "@jsquash/webp": "^1.4.0", "zod": "^3.23.8" } }, "sha512-t+Wqkde4xQHP/UZ4bDiDo3pbhFz32E7FvQCUkuFdJDmEDl6gPCs6LQiQVBmumUQYTeVLiLtLzlM9j8s7yF0sXQ=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -416,6 +416,8 @@ "@jsquash/webp": ["@jsquash/webp@1.5.0", "", { "dependencies": { "wasm-feature-detect": "^1.2.11" } }, "sha512-KggLoj2MnRSfIqTeKe1EmbljTX2vuV7mh79k89PCL1pyqiDULcPM1L47twxXt0hkb68F70bXiL31MxsuoZtKFw=="], + "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], + "@node-minify/clean-css": ["@node-minify/clean-css@9.0.1", "", { "dependencies": { "@node-minify/utils": "9.0.1", "clean-css": "5.3.3" } }, "sha512-GHTMmjGloRvNzqdG7foI0iZeS2QmuYCQvdASJP9sCKjkpH45bygODpXPYKnlzUEpQgYvPK9Q3GxqYnVY9SdoqA=="], "@node-minify/core": ["@node-minify/core@9.0.2", "", { "dependencies": { "@node-minify/utils": "9.0.1", "glob": "10.3.3", "mkdirp": "3.0.1" } }, "sha512-FNhv29Wom6wKrrFKaeAfmZqz7TX5A1E6P+bpd0VIc+DYWMLUIhAViS8riaZg3A1oD0s06s+5BG2Fg7RqMKiKHw=="], @@ -424,12 +426,6 @@ "@node-minify/utils": ["@node-minify/utils@9.0.1", "", { "dependencies": { "gzip-size": "6.0.0" } }, "sha512-aC1+mhKTP3IMa2VcuGl3ui92LO/7CPQWldNGzu3BVGKiMNJ70AKJW/R6huuYCSuQyHDGM9oFwiVClsZnFxn67g=="], - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@noriginmedia/norigin-spatial-navigation": ["@noriginmedia/norigin-spatial-navigation@3.1.0", "", { "dependencies": { "@noriginmedia/norigin-spatial-navigation-core": "^3.1.0", "@noriginmedia/norigin-spatial-navigation-react": "^3.1.0" } }, "sha512-KPge4ocpDFde7cpZ2aqrPrKmxOxkue983NsfpmE/vX4k2l+Ik8UkucCWGqkcy81TXkEyRhdsYwFTRePNB5qUCg=="], "@noriginmedia/norigin-spatial-navigation-core": ["@noriginmedia/norigin-spatial-navigation-core@3.1.0", "", { "dependencies": { "lodash-es": "^4.17.21" } }, "sha512-AFxJHurTqy+I3NLnaXsLUBa9FZjUryMNFEdLpPrITSqDjk525aINeLMOK1PN7WTiK5xpHL0pbpw0+uVOfWgp4w=="], @@ -472,7 +468,7 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.56.0", "", { "os": "android", "cpu": "arm" }, "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw=="], @@ -528,88 +524,86 @@ "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], - "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], + "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.4", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="], "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.2.4", "", { "dependencies": { "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "tailwindcss": "4.2.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw=="], - "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.0", "", {}, "sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw=="], + "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.3", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw=="], - "@tanstack/form-core": ["@tanstack/form-core@1.28.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.0", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.7.7" } }, "sha512-MX3YveB6SKHAJ2yUwp+Ca/PCguub8bVEnLcLUbFLwdkSRMkP0lMGdaZl+F0JuEgZw56c6iFoRyfILhS7OQpydA=="], + "@tanstack/form-core": ["@tanstack/form-core@1.29.1", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.9.1" } }, "sha512-NIYPO36eEu7nSWvMpbFDQaBWyVtnH/C8fsZ3/XpJUT4uOWgmxsiUvHGbTbDNIQTXAKIkhwEl0sUrqBNn2SfUnw=="], - "@tanstack/history": ["@tanstack/history@1.154.14", "", {}, "sha512-xyIfof8eHBuub1CkBnbKNKQXeRZC4dClhmzePHVOEel4G7lk/dW+TQ16da7CFdeNLv6u6Owf5VoBQxoo6DFTSA=="], + "@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="], "@tanstack/pacer-lite": ["@tanstack/pacer-lite@0.1.1", "", {}, "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w=="], - "@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="], + "@tanstack/query-core": ["@tanstack/query-core@5.100.9", "", {}, "sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ=="], - "@tanstack/query-devtools": ["@tanstack/query-devtools@5.93.0", "", {}, "sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg=="], + "@tanstack/query-devtools": ["@tanstack/query-devtools@5.100.9", "", {}, "sha512-gqiptrTIhbK2PuCaPRHmWXfJG1NGYVFpAr0HqogEqiSBNB5xDz6fmesQt7w4WgMOqOQPnPHJ3ZDMuhDaXvNO8g=="], - "@tanstack/react-form": ["@tanstack/react-form@1.28.0", "", { "dependencies": { "@tanstack/form-core": "1.28.0", "@tanstack/react-store": "^0.8.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-ibLcf5QkTogV0Ly944CuqGxWTpHyreNA4Cy8Wtky7zE9wtE3HVapQt4/hUuXo51zihfTkv5URiXpoTSKF5Xosg=="], + "@tanstack/react-form": ["@tanstack/react-form@1.29.1", "", { "dependencies": { "@tanstack/form-core": "1.29.1", "@tanstack/react-store": "^0.9.1" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-hVHk4g0phd0HxRsv2ry6Xt8BqmalT55Q3cokhJBCC1St0hcGZhgwJJbohm9atao45BPG9e55DGvtbwExqZe35g=="], - "@tanstack/react-query": ["@tanstack/react-query@5.90.20", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw=="], + "@tanstack/react-query": ["@tanstack/react-query@5.100.9", "", { "dependencies": { "@tanstack/query-core": "5.100.9" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A=="], - "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.91.3", "", { "dependencies": { "@tanstack/query-devtools": "5.93.0" }, "peerDependencies": { "@tanstack/react-query": "^5.90.20", "react": "^18 || ^19" } }, "sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA=="], + "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.100.9", "", { "dependencies": { "@tanstack/query-devtools": "5.100.9" }, "peerDependencies": { "@tanstack/react-query": "^5.100.9", "react": "^18 || ^19" } }, "sha512-mM3slaVGXJmz+pOLgXdANj75ikgQCyudyl3kmFvm6brI1JyVeY/+IeD17uDHIvZrD8hfoO2sdZ54RFsHdYAuhA=="], - "@tanstack/react-router": ["@tanstack/react-router@1.157.16", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/react-store": "^0.8.0", "@tanstack/router-core": "1.157.16", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-xwFQa7S7dhBhm3aJYwU79cITEYgAKSrcL6wokaROIvl2JyIeazn8jueWqUPJzFjv+QF6Q8euKRlKUEyb5q2ymg=="], + "@tanstack/react-router": ["@tanstack/react-router@1.169.2", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.169.2", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-OJM7Kguc7ERnweaNRWsyWgIKcl3z23rD1B4jaxjzd9RGdnzpt2HfrWa9rggbT0Hfzhfo4D2ZmsfoTme035tniQ=="], - "@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.154.12", "", { "dependencies": { "@tanstack/router-devtools-core": "1.154.12" }, "peerDependencies": { "@tanstack/react-router": "^1.154.12", "@tanstack/router-core": "^1.154.12", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-TcGe7pmeVjk1zD58eMR87GG9OXMx6LDGz5QopmJS4LafvK2hvuaht+eKBnZlCvKLPlXu5juwHT4u+2bYdn6sqQ=="], + "@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.166.13", "", { "dependencies": { "@tanstack/router-devtools-core": "1.167.3" }, "peerDependencies": { "@tanstack/react-router": "^1.168.15", "@tanstack/router-core": "^1.168.11", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-6yKRFFJrEEOiGp5RAAuGCYsl81M4XAhJmLcu9PKj+HZle4A3dsP60lwHoqQYWHMK9nKKFkdXR+D8qxzxqtQbEA=="], - "@tanstack/react-router-ssr-query": ["@tanstack/react-router-ssr-query@1.157.17", "", { "dependencies": { "@tanstack/router-ssr-query-core": "1.157.16" }, "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/react-query": ">=5.90.0", "@tanstack/react-router": ">=1.127.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-IrLi+cNLOAiTShuwGUi9WCNWXMG4993fnyXnWMC53M64rhUngPRFCAQiA05BavE/d7bifC6WKDTt1ZhC1Pewaw=="], + "@tanstack/react-router-ssr-query": ["@tanstack/react-router-ssr-query@1.166.12", "", { "dependencies": { "@tanstack/router-ssr-query-core": "1.168.0" }, "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/react-query": ">=5.90.0", "@tanstack/react-router": ">=1.127.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-yDUIoEh+PimAcWmk/2BE0EkI8TwLVeToNzoIuwahmTtBUR+ptZPWbtiPjudO8JZ0BhT3odHtuOn1eBOK0/4NAQ=="], - "@tanstack/react-store": ["@tanstack/react-store@0.8.0", "", { "dependencies": { "@tanstack/store": "0.8.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow=="], + "@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="], - "@tanstack/router-core": ["@tanstack/router-core@1.157.16", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/store": "^0.8.0", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-eJuVgM7KZYTTr4uPorbUzUflmljMVcaX2g6VvhITLnHmg9SBx9RAgtQ1HmT+72mzyIbRSlQ1q0fY/m+of/fosA=="], + "@tanstack/router-core": ["@tanstack/router-core@1.169.2", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.4", "seroval-plugins": "^1.5.4" } }, "sha512-5sm0DJF1A7Mz+9gy4Gz/lLovNailK3yot4vYvz9MkBUPw26uLnhQiR8hSCYxucjE0wD6Mdlc5l+Z0/XTlZ7xHw=="], - "@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.154.12", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@tanstack/router-core": "^1.154.12", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-lvnP9cqknvSSkUjqQRVn61TcBhq72hCFFOzMwdFdFPTO8nMEXvYE6ZZJiXtivwcvsKmO6XVFLMXuJr/928gNkw=="], + "@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.167.3", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16" }, "peerDependencies": { "@tanstack/router-core": "^1.168.11", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-fJ1VMhyQgnoashTrP763c2HRc9kofgF61L7Jb3F6eTHAmCKtGVx8BRtiFt37sr3U0P0jmaaiiSPGP6nT5JtVNg=="], - "@tanstack/router-generator": ["@tanstack/router-generator@1.157.16", "", { "dependencies": { "@tanstack/router-core": "1.157.16", "@tanstack/router-utils": "1.154.7", "@tanstack/virtual-file-routes": "1.154.7", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-Ae2M00VTFjjED7glSCi/mMLENRzhEym6NgjoOx7UVNbCC/rLU/5ASDe5VIlDa8QLEqP5Pj088Gi51gjmRuICvQ=="], + "@tanstack/router-generator": ["@tanstack/router-generator@1.166.42", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.169.2", "@tanstack/router-utils": "1.161.8", "@tanstack/virtual-file-routes": "1.161.7", "jiti": "^2.7.0", "magic-string": "^0.30.21", "prettier": "^3.5.0", "zod": "^3.24.2" } }, "sha512-2qBWC0t78r6b3vI+AbnvCZcFAvbYBDlLuWZrTjQbcjUmwG3qyeQp983tJyDuj9wb5//adG1tgAGXZkJ3aDwdBg=="], - "@tanstack/router-plugin": ["@tanstack/router-plugin@1.157.16", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.157.16", "@tanstack/router-generator": "1.157.16", "@tanstack/router-utils": "1.154.7", "@tanstack/virtual-file-routes": "1.154.7", "babel-dead-code-elimination": "^1.0.11", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.157.16", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-YQg7L06xyCJAYyrEJNZGAnDL8oChILU+G/eSDIwEfcWn5iLk+47x1Gcdxr82++47PWmOPhzuTo8edDQXWs7kAA=="], + "@tanstack/router-plugin": ["@tanstack/router-plugin@1.167.35", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.169.2", "@tanstack/router-generator": "1.166.42", "@tanstack/router-utils": "1.161.8", "@tanstack/virtual-file-routes": "1.161.7", "chokidar": "^3.6.0", "unplugin": "^3.0.0", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2 || ^2.0.0", "@tanstack/react-router": "^1.169.2", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-UAScU5VAzLYVY4FML/Cbc5S5TucT4I8Ata05yozGOe4ZfepTKRffA5xWLtD2N+ov5svdv0KTX/kqlZnYPe28mA=="], - "@tanstack/router-ssr-query-core": ["@tanstack/router-ssr-query-core@1.157.16", "", { "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/router-core": ">=1.127.0" } }, "sha512-YuwNG4jdtn+r90yyti8yP27IKaVoflWmRezqnj0gyJxpRauBkK7MVLvWSNbJadnk88b9H+rdtNOF2k3owGaong=="], + "@tanstack/router-ssr-query-core": ["@tanstack/router-ssr-query-core@1.168.0", "", { "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/router-core": ">=1.127.0" } }, "sha512-5yBUAF1d9z2kOFKoz1spvpvkMSTmRnRXEwi+bGKfrXYmt7CfHu3Pk8KUFMln67uQoKQ9VTkcd5tLkjJVrZ2/AQ=="], - "@tanstack/router-utils": ["@tanstack/router-utils@1.154.7", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "ansis": "^4.1.0", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-61bGx32tMKuEpVRseu2sh1KQe8CfB7793Mch/kyQt0EP3tD7X0sXmimCl3truRiDGUtI0CaSoQV1NPjAII1RBA=="], + "@tanstack/router-utils": ["@tanstack/router-utils@1.161.8", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-xyiLWEKjfBAVhauDSSjXxyf7s8elU6SM+V050sbkofvGmIIvkwPFtDsX7Gvwh14kBd6iCwAT+RiPvXTxAptY0Q=="], - "@tanstack/store": ["@tanstack/store@0.7.7", "", {}, "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ=="], + "@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="], - "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.154.7", "", {}, "sha512-cHHDnewHozgjpI+MIVp9tcib6lYEQK5MyUr0ChHpHFGBl8Xei55rohFK0I0ve/GKoHeioaK42Smd8OixPp6CTg=="], + "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.161.7", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ=="], - "@tanstack/zod-adapter": ["@tanstack/zod-adapter@1.162.4", "", { "peerDependencies": { "@tanstack/react-router": ">=1.43.2", "zod": "^3.23.8" } }, "sha512-sO4n2o9F7gZKHZb/nW/fMcDaeVcbFZ2a7zCA+GkaHJwRmhKKlQQ0dae9pc8wOMMG+QkfH1Wysq+tg2RNvm/kpg=="], + "@tanstack/zod-adapter": ["@tanstack/zod-adapter@1.166.9", "", { "peerDependencies": { "@tanstack/react-router": ">=1.43.2", "zod": "^3.23.8" } }, "sha512-HHllQ/CKGi8YBbftv6OmzojtHM6Rk4UszAFICAgUMbwiqtKqjlIZQ/7mv2IPNxBb8YlOQgzyQ4jz2UTEXIi6YA=="], "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@trysound/sax": ["@trysound/sax@0.2.0", "", {}, "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="], - "@types/adm-zip": ["@types/adm-zip@0.5.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q=="], "@types/audiosprite": ["@types/audiosprite@0.7.3", "", {}, "sha512-P4rUuHPt2kWPMqyObfh1SfqS2H/ZuTxByh00ecuI2tOdvP5b8NznuBeQgemDXV9v8b4pewFPB9G3BuYRONqD7A=="], @@ -622,7 +616,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], + "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -662,7 +656,7 @@ "@types/rclone.js": ["@types/rclone.js@0.6.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-BssKAAVRY//fxGKso8SatyOwiD7X0toDofNnVxZlIXmN7UHrn2UBTxldNAjgUvWA91qJyeEPfKmeJpZVhLugXg=="], - "@types/react": ["@types/react@19.2.9", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], @@ -672,19 +666,17 @@ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.2", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.53", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], "JSONStream": ["JSONStream@1.3.5", "", { "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" }, "bin": { "JSONStream": "./bin.js" } }, "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ=="], - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], - "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "add-stream": ["add-stream@1.0.0", "", {}, "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ=="], - "adm-zip": ["adm-zip@0.5.16", "", {}, "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ=="], + "adm-zip": ["adm-zip@0.5.17", "", {}, "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ=="], "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], @@ -712,8 +704,6 @@ "arrify": ["arrify@1.0.1", "", {}, "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="], - "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], - "async": ["async@0.9.2", "", {}, "sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw=="], "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], @@ -734,8 +724,6 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.9.17", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ=="], @@ -756,13 +744,11 @@ "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], - "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="], - "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], + "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], @@ -832,7 +818,7 @@ "concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="], - "conf": ["conf@15.0.2", "", { "dependencies": { "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "atomically": "^2.0.3", "debounce-fn": "^6.0.0", "dot-prop": "^10.0.0", "env-paths": "^3.0.0", "json-schema-typed": "^8.0.1", "semver": "^7.7.2", "uint8array-extras": "^1.5.0" } }, "sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw=="], + "conf": ["conf@15.1.0", "", { "dependencies": { "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "atomically": "^2.0.3", "debounce-fn": "^6.0.0", "dot-prop": "^10.0.0", "env-paths": "^3.0.0", "json-schema-typed": "^8.0.1", "semver": "^7.7.2", "uint8array-extras": "^1.5.0" } }, "sha512-Uy5YN9KEu0WWDaZAVJ5FAmZoaJt9rdK6kH+utItPyGsCqCgaTKkrmZx3zoE0/3q6S3bcp3Ihkk+ZqPxWxFK5og=="], "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], @@ -876,7 +862,7 @@ "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], - "cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="], + "cookie-es": ["cookie-es@3.1.1", "", {}, "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg=="], "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], @@ -890,7 +876,7 @@ "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], - "css-tree": ["css-tree@2.3.1", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="], + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], @@ -902,7 +888,7 @@ "cycle": ["cycle@1.0.3", "", {}, "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA=="], - "daisyui": ["daisyui@5.5.14", "", {}, "sha512-L47rvw7I7hK68TA97VB8Ee0woHew+/ohR6Lx6Ah/krfISOqcG4My7poNpX5Mo5/ytMxiR40fEaz6njzDi7cuSg=="], + "daisyui": ["daisyui@5.5.19", "", {}, "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA=="], "dargs": ["dargs@7.0.0", "", {}, "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg=="], @@ -958,9 +944,9 @@ "dotgitignore": ["dotgitignore@2.1.0", "", { "dependencies": { "find-up": "^3.0.0", "minimatch": "^3.0.4" } }, "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA=="], - "drizzle-kit": ["drizzle-kit@0.31.9", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg=="], + "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], - "drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="], + "drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], "dts-bundle-generator": ["dts-bundle-generator@9.5.1", "", { "dependencies": { "typescript": ">=5.0.2", "yargs": "^17.6.0" }, "bin": { "dts-bundle-generator": "dist/bin/dts-bundle-generator.js" } }, "sha512-DxpJOb2FNnEyOzMkG11sxO2dmxPjthoVWxfKqWYJ/bI/rT1rvTMktF5EKjAYrRZu6Z6t3NhOUZ0sZ5ZXevOfbA=="], @@ -974,7 +960,7 @@ "electron-to-chromium": ["electron-to-chromium@1.5.277", "", {}, "sha512-wKXFZw4erWmmOz5N/grBoJ2XrNJGDFMu2+W5ACHza5rHtvsqrK4gb6rnLC7XxKB9WlJ+RmyQatuEXmtm86xbnw=="], - "elysia": ["elysia@1.4.22", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.6", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Q90VCb1RVFxnFaRV0FDoSylESQQLWgLHFmWciQJdX9h3b2cSasji9KWEUvaJuy/L9ciAGg4RAhUVfsXHg5K2RQ=="], + "elysia": ["elysia@1.4.28", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Vrx8sBnvq8squS/3yNBzR1jBXI+SgmnmvwawPjNuEHndUe5l1jV2Gp6JJ4ulDkEB8On6bWmmuyPpA+bq4t+WYg=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -984,7 +970,7 @@ "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], - "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], + "enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -1002,23 +988,15 @@ "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], - "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], - "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - - "exact-mirror": ["exact-mirror@0.2.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-7s059UIx9/tnOKSySzUk5cPGkoILhTE4p6ncf6uIPaQ+9aRBQzQjc9+q85l51+oZ+P6aBxh084pD0CzBQPcFUA=="], + "exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="], "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="], @@ -1032,11 +1010,11 @@ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], - "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + "fast-xml-builder": ["fast-xml-builder@1.1.9", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-jcyKVSEX13iseJqg7n/KWw+xnu/7fdrZ333Fac54KjHDIELVCfDDJXYIm6DTJ0Su4gSzrhqiK0DzY/wZbF40mw=="], + + "fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -1044,7 +1022,7 @@ "figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="], - "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], + "file-type": ["file-type@21.3.4", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -1058,7 +1036,7 @@ "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], - "fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], + "fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -1142,7 +1120,7 @@ "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="], - "immutable": ["immutable@5.1.4", "", {}, "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="], + "immutable": ["immutable@5.1.5", "", {}, "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A=="], "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], @@ -1200,7 +1178,7 @@ "jackspeak": ["jackspeak@2.3.6", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ=="], - "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="], + "jimp": ["jimp@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/diff": "1.6.1", "@jimp/js-bmp": "1.6.1", "@jimp/js-gif": "1.6.1", "@jimp/js-jpeg": "1.6.1", "@jimp/js-png": "1.6.1", "@jimp/js-tiff": "1.6.1", "@jimp/plugin-blit": "1.6.1", "@jimp/plugin-blur": "1.6.1", "@jimp/plugin-circle": "1.6.1", "@jimp/plugin-color": "1.6.1", "@jimp/plugin-contain": "1.6.1", "@jimp/plugin-cover": "1.6.1", "@jimp/plugin-crop": "1.6.1", "@jimp/plugin-displace": "1.6.1", "@jimp/plugin-dither": "1.6.1", "@jimp/plugin-fisheye": "1.6.1", "@jimp/plugin-flip": "1.6.1", "@jimp/plugin-hash": "1.6.1", "@jimp/plugin-mask": "1.6.1", "@jimp/plugin-print": "1.6.1", "@jimp/plugin-quantize": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/plugin-rotate": "1.6.1", "@jimp/plugin-threshold": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1" } }, "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw=="], "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], @@ -1232,29 +1210,29 @@ "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], - "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], @@ -1310,14 +1288,12 @@ "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], - "mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="], + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], "meow": ["meow@8.1.2", "", { "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.18.0", "yargs-parser": "^20.2.3" } }, "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q=="], - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], @@ -1360,8 +1336,6 @@ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -1404,14 +1378,12 @@ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], - "node-downloader-helper": ["node-downloader-helper@2.1.10", "", { "bin": { "ndh": "bin/ndh" } }, "sha512-8LdieUd4Bqw/CzfZLf30h+1xSAq3riWSDfWKsPJYz8EULoWxjS1vw6BGLYFZDxQgXjDR7UmC9UpQ0oV93U98Fg=="], + "node-downloader-helper": ["node-downloader-helper@2.1.11", "", { "bin": { "ndh": "bin/ndh" } }, "sha512-882fH2C9AWdiPCwz/2beq5t8FGMZK9Dx8TJUOIxzMCbvG7XUKM5BuJwN5f0NKo4SCQK6jR4p2TPm54mYGdGchQ=="], "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], - "node-html-parser": ["node-html-parser@7.0.2", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-DxodLVh7a6JMkYzWyc8nBX9MaF4M0lLFYkJHlWOiu7+9/I6mwNK9u5TbAMC7qfqDJEPX9OIoWA2A9t4C2l1mUQ=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "node-stream-zip": ["node-stream-zip@1.15.0", "", {}, "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw=="], @@ -1450,7 +1422,7 @@ "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - "p-queue": ["p-queue@9.1.2", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-ktsDOALzTYTWWF1PbkNVg2rOt+HaOaMWJMUnt7T3qf5tvZ1L8dBW3tObzprBcXNMKkwj+yFSLqHso0x+UFcJXw=="], + "p-queue": ["p-queue@9.2.0", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-dWgLE8AH0HjQ9fe74pUkKkvzzYT18Inp4zra3lKHnnwqGvcfcUBrvF2EAVX+envufDNBOzpPq/IBUONDbI7+3g=="], "p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="], @@ -1478,6 +1450,8 @@ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -1490,8 +1464,6 @@ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="], - "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -1528,8 +1500,6 @@ "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], - "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], - "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], @@ -1544,17 +1514,15 @@ "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - "quick-lru": ["quick-lru@4.0.1", "", {}, "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="], "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], - "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + "react": ["react@19.2.6", "", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="], - "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], + "react-dom": ["react-dom@19.2.6", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.6" } }, "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g=="], - "react-error-boundary": ["react-error-boundary@6.1.0", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-02k9WQ/mUhdbXir0tC1NiMesGzRPaCsJEWU/4bcFrbY1YMZOtHShtZP6zw0SJrBWA/31H0KT9/FgdL8+sPKgHA=="], + "react-error-boundary": ["react-error-boundary@6.1.1", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w=="], "react-hot-toast": ["react-hot-toast@2.6.0", "", { "dependencies": { "csstype": "^3.1.3", "goober": "^2.1.16" }, "peerDependencies": { "react": ">=16", "react-dom": ">=16" } }, "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg=="], @@ -1562,7 +1530,7 @@ "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], - "react-qr-code": ["react-qr-code@2.0.18", "", { "dependencies": { "prop-types": "^15.8.1", "qr.js": "0.0.0" }, "peerDependencies": { "react": "*" } }, "sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg=="], + "react-qr-code": ["react-qr-code@2.0.21", "", { "dependencies": { "prop-types": "^15.8.1", "qr.js": "0.0.0" }, "peerDependencies": { "react": "*" } }, "sha512-xaywjo0eaF4S3LOz6ns5eoPbM2E+q9HYl4VATYpxK4bBniOhQ9noY2RJ9G4SnZFhUwzx63FUT6KdHzfKgUwyuQ=="], "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], @@ -1572,12 +1540,8 @@ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], - "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], - "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], @@ -1594,61 +1558,57 @@ "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "rollup": ["rollup@4.56.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.56.0", "@rollup/rollup-android-arm64": "4.56.0", "@rollup/rollup-darwin-arm64": "4.56.0", "@rollup/rollup-darwin-x64": "4.56.0", "@rollup/rollup-freebsd-arm64": "4.56.0", "@rollup/rollup-freebsd-x64": "4.56.0", "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", "@rollup/rollup-linux-arm-musleabihf": "4.56.0", "@rollup/rollup-linux-arm64-gnu": "4.56.0", "@rollup/rollup-linux-arm64-musl": "4.56.0", "@rollup/rollup-linux-loong64-gnu": "4.56.0", "@rollup/rollup-linux-loong64-musl": "4.56.0", "@rollup/rollup-linux-ppc64-gnu": "4.56.0", "@rollup/rollup-linux-ppc64-musl": "4.56.0", "@rollup/rollup-linux-riscv64-gnu": "4.56.0", "@rollup/rollup-linux-riscv64-musl": "4.56.0", "@rollup/rollup-linux-s390x-gnu": "4.56.0", "@rollup/rollup-linux-x64-gnu": "4.56.0", "@rollup/rollup-linux-x64-musl": "4.56.0", "@rollup/rollup-openbsd-x64": "4.56.0", "@rollup/rollup-openharmony-arm64": "4.56.0", "@rollup/rollup-win32-arm64-msvc": "4.56.0", "@rollup/rollup-win32-ia32-msvc": "4.56.0", "@rollup/rollup-win32-x64-gnu": "4.56.0", "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "sass": ["sass@1.97.3", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg=="], + "sass": ["sass@1.99.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q=="], - "sass-embedded": ["sass-embedded@1.97.3", "", { "dependencies": { "@bufbuild/protobuf": "^2.5.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-all-unknown": "1.97.3", "sass-embedded-android-arm": "1.97.3", "sass-embedded-android-arm64": "1.97.3", "sass-embedded-android-riscv64": "1.97.3", "sass-embedded-android-x64": "1.97.3", "sass-embedded-darwin-arm64": "1.97.3", "sass-embedded-darwin-x64": "1.97.3", "sass-embedded-linux-arm": "1.97.3", "sass-embedded-linux-arm64": "1.97.3", "sass-embedded-linux-musl-arm": "1.97.3", "sass-embedded-linux-musl-arm64": "1.97.3", "sass-embedded-linux-musl-riscv64": "1.97.3", "sass-embedded-linux-musl-x64": "1.97.3", "sass-embedded-linux-riscv64": "1.97.3", "sass-embedded-linux-x64": "1.97.3", "sass-embedded-unknown-all": "1.97.3", "sass-embedded-win32-arm64": "1.97.3", "sass-embedded-win32-x64": "1.97.3" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA=="], + "sass-embedded": ["sass-embedded@1.99.0", "", { "dependencies": { "@bufbuild/protobuf": "^2.5.0", "colorjs.io": "^0.5.0", "immutable": "^5.1.5", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-all-unknown": "1.99.0", "sass-embedded-android-arm": "1.99.0", "sass-embedded-android-arm64": "1.99.0", "sass-embedded-android-riscv64": "1.99.0", "sass-embedded-android-x64": "1.99.0", "sass-embedded-darwin-arm64": "1.99.0", "sass-embedded-darwin-x64": "1.99.0", "sass-embedded-linux-arm": "1.99.0", "sass-embedded-linux-arm64": "1.99.0", "sass-embedded-linux-musl-arm": "1.99.0", "sass-embedded-linux-musl-arm64": "1.99.0", "sass-embedded-linux-musl-riscv64": "1.99.0", "sass-embedded-linux-musl-x64": "1.99.0", "sass-embedded-linux-riscv64": "1.99.0", "sass-embedded-linux-x64": "1.99.0", "sass-embedded-unknown-all": "1.99.0", "sass-embedded-win32-arm64": "1.99.0", "sass-embedded-win32-x64": "1.99.0" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-gF/juR1aX02lZHkvwxdF80SapkQeg2fetoDF6gIQkNbSw5YEUFspMkyGTjPjgZSgIHuZpy+Wz4PlebKnLXMjdg=="], - "sass-embedded-all-unknown": ["sass-embedded-all-unknown@1.97.3", "", { "dependencies": { "sass": "1.97.3" }, "cpu": [ "!arm", "!x64", "!arm64", ] }, "sha512-t6N46NlPuXiY3rlmG6/+1nwebOBOaLFOOVqNQOC2cJhghOD4hh2kHNQQTorCsbY9S1Kir2la1/XLBwOJfui0xg=="], + "sass-embedded-all-unknown": ["sass-embedded-all-unknown@1.99.0", "", { "dependencies": { "sass": "1.99.0" }, "cpu": [ "!arm", "!x64", "!arm64", ] }, "sha512-qPIRG8Uhjo6/OKyAKixTnwMliTz+t9K6Duk0mx5z+K7n0Ts38NSJz2sjDnc7cA/8V9Lb3q09H38dZ1CLwD+ssw=="], - "sass-embedded-android-arm": ["sass-embedded-android-arm@1.97.3", "", { "os": "android", "cpu": "arm" }, "sha512-cRTtf/KV/q0nzGZoUzVkeIVVFv3L/tS1w4WnlHapphsjTXF/duTxI8JOU1c/9GhRPiMdfeXH7vYNcMmtjwX7jg=="], + "sass-embedded-android-arm": ["sass-embedded-android-arm@1.99.0", "", { "os": "android", "cpu": "arm" }, "sha512-EHvJ0C7/VuP78Qr6f8gIUVUmCqIorEQpw2yp3cs3SMg02ZuumlhjXvkTcFBxHmFdFR23vTNk1WnhY6QSeV1nFQ=="], - "sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.97.3", "", { "os": "android", "cpu": "arm64" }, "sha512-aiZ6iqiHsUsaDx0EFbbmmA0QgxicSxVVN3lnJJ0f1RStY0DthUkquGT5RJ4TPdaZ6ebeJWkboV4bra+CP766eA=="], + "sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.99.0", "", { "os": "android", "cpu": "arm64" }, "sha512-fNHhdnP23yqqieCbAdym4N47AleSwjbNt6OYIYx4DdACGdtERjQB4iOX/TaKsW034MupfF7SjnAAK8w7Ptldtg=="], - "sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.97.3", "", { "os": "android", "cpu": "none" }, "sha512-zVEDgl9JJodofGHobaM/q6pNETG69uuBIGQHRo789jloESxxZe82lI3AWJQuPmYCOG5ElfRthqgv89h3gTeLYA=="], + "sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.99.0", "", { "os": "android", "cpu": "none" }, "sha512-4zqDFRvgGDTL5vTHuIhRxUpXFoh0Cy7Gm5Ywk19ASd8Settmd14YdPRZPmMxfgS1GH292PofV1fq1ifiSEJWBw=="], - "sass-embedded-android-x64": ["sass-embedded-android-x64@1.97.3", "", { "os": "android", "cpu": "x64" }, "sha512-3ke0le7ZKepyXn/dKKspYkpBC0zUk/BMciyP5ajQUDy4qJwobd8zXdAq6kOkdiMB+d9UFJOmEkvgFJHl3lqwcw=="], + "sass-embedded-android-x64": ["sass-embedded-android-x64@1.99.0", "", { "os": "android", "cpu": "x64" }, "sha512-Uk53k/dGYt04RjOL4gFjZ0Z9DH9DKh8IA8WsXUkNqsxerAygoy3zqRBS2zngfE9K2jiOM87q+1R1p87ory9oQQ=="], - "sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.97.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fuqMTqO4gbOmA/kC5b9y9xxNYw6zDEyfOtMgabS7Mz93wimSk2M1quQaTJnL98Mkcsl2j+7shNHxIS/qpcIDDA=="], + "sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.99.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-u61/7U3IGLqoO6gL+AHeiAtlTPFwJK1+964U8gp45ZN0hzh1yrARf5O1mivXv8NnNgJvbG2wWJbiNZP0lG/lTg=="], - "sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.97.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-b/2RBs/2bZpP8lMkyZ0Px0vkVkT8uBd0YXpOwK7iOwYkAT8SsO4+WdVwErsqC65vI5e1e5p1bb20tuwsoQBMVA=="], + "sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.99.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-j/kkk/NcXdIameLezSfXjgCiBkVcA+G60AXrX768/3g0miK1g7M9dj7xOhCb1i7/wQeiEI3rw2LLuO63xRIn4A=="], - "sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.97.3", "", { "os": "linux", "cpu": "arm" }, "sha512-2lPQ7HQQg4CKsH18FTsj2hbw5GJa6sBQgDsls+cV7buXlHjqF8iTKhAQViT6nrpLK/e8nFCoaRgSqEC8xMnXuA=="], + "sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.99.0", "", { "os": "linux", "cpu": "arm" }, "sha512-d4IjJZrX2+AwB2YCy1JySwdptJECNP/WfAQLUl8txI3ka8/d3TUI155GtelnoZUkio211PwIeFvvAeZ9RXPQnw=="], - "sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.97.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-IP1+2otCT3DuV46ooxPaOKV1oL5rLjteRzf8ldZtfIEcwhSgSsHgA71CbjYgLEwMY9h4jeal8Jfv3QnedPvSjg=="], + "sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.99.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-btNcFpItcB56L40n8hDeL7sRSMLDXQ56nB5h2deddJx1n60rpKSElJmkaDGHtpkrY+CTtDRV0FZDjHeTJddYew=="], - "sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.97.3", "", { "os": "linux", "cpu": "arm" }, "sha512-cBTMU68X2opBpoYsSZnI321gnoaiMBEtc+60CKCclN6PCL3W3uXm8g4TLoil1hDD6mqU9YYNlVG6sJ+ZNef6Lg=="], + "sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.99.0", "", { "os": "linux", "cpu": "arm" }, "sha512-2gvHOupgIw3ytatXT4nFUow71LFbuOZPEwG+HUzcNQDH8ue4Ez8cr03vsv5MDv3lIjOKcXwDvWD980t18MwkoQ=="], - "sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.97.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Lij0SdZCsr+mNRSyDZ7XtJpXEITrYsaGbOTz5e6uFLJ9bmzUbV7M8BXz2/cA7bhfpRPT7/lwRKPdV4+aR9Ozcw=="], + "sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.99.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Hi2bt/IrM5P4FBKz6EcHAlniwfpoz9mnTdvSd58y+avA3SANM76upIkAdSayA8ZGwyL3gZokru1AKDPF9lJDNw=="], - "sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.97.3", "", { "os": "linux", "cpu": "none" }, "sha512-sBeLFIzMGshR4WmHAD4oIM7WJVkSoCIEwutzptFtGlSlwfNiijULp+J5hA2KteGvI6Gji35apR5aWj66wEn/iA=="], + "sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.99.0", "", { "os": "linux", "cpu": "none" }, "sha512-mKqGvVaJ9rHMqyZsF0kikQe4NO0f4osb67+X6nLhBiVDKvyazQHJ3zJQreNefIE36yL2sjHIclSB//MprzaQDg=="], - "sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.97.3", "", { "os": "linux", "cpu": "x64" }, "sha512-/oWJ+OVrDg7ADDQxRLC/4g1+Nsz1g4mkYS2t6XmyMJKFTFK50FVI2t5sOdFH+zmMp+nXHKM036W94y9m4jjEcw=="], + "sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.99.0", "", { "os": "linux", "cpu": "x64" }, "sha512-huhgOMmOc30r7CH7qbRbT9LerSEGSnWuS4CYNOskr9BvNeQp4dIneFufNRGZ7hkOAxUM8DglxIZJN/cyAT95Ew=="], - "sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.97.3", "", { "os": "linux", "cpu": "none" }, "sha512-l3IfySApLVYdNx0Kjm7Zehte1CDPZVcldma3dZt+TfzvlAEerM6YDgsk5XEj3L8eHBCgHgF4A0MJspHEo2WNfA=="], + "sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.99.0", "", { "os": "linux", "cpu": "none" }, "sha512-mevFPIFAVhrH90THifxLfOntFmHtcEKOcdWnep2gJ0X4DVva4AiVIRlQe/7w9JFx5+gnDRE1oaJJkzuFUuYZsA=="], - "sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.97.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Kwqwc/jSSlcpRjULAOVbndqEy2GBzo6OBmmuBVINWUaJLJ8Kczz3vIsDUWLfWz/kTEw9FHBSiL0WCtYLVAXSLg=="], + "sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.99.0", "", { "os": "linux", "cpu": "x64" }, "sha512-9k7IkULqIZdCIVt4Mboryt6vN8Mjmm3EhI1P3mClU5y5i3wLK5ExC3cbVWk047KsID/fvB1RLslqghXJx5BoxA=="], - "sass-embedded-unknown-all": ["sass-embedded-unknown-all@1.97.3", "", { "dependencies": { "sass": "1.97.3" }, "os": [ "!linux", "!win32", "!darwin", "!android", ] }, "sha512-/GHajyYJmvb0IABUQHbVHf1nuHPtIDo/ClMZ81IDr59wT5CNcMe7/dMNujXwWugtQVGI5UGmqXWZQCeoGnct8Q=="], + "sass-embedded-unknown-all": ["sass-embedded-unknown-all@1.99.0", "", { "dependencies": { "sass": "1.99.0" }, "os": [ "!linux", "!win32", "!darwin", "!android", ] }, "sha512-P7MxiUtL/XzGo3PX0CaB8lNNEFLQWKikPA8pbKytx9ZCLZSDkt2NJcdAbblB/sqMs4AV3EK2NadV8rI/diq3xg=="], - "sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.97.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-RDGtRS1GVvQfMGAmVXNxYiUOvPzn9oO1zYB/XUM9fudDRnieYTcUytpNTQZLs6Y1KfJxgt5Y+giRceC92fT8Uw=="], + "sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.99.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8whpsW7S+uO8QApKfQuc36m3P9EISzbVZOgC79goob4qGy09u8Gz/rYvw8h1prJDSjltpHGhOzBE6LDz7WvzVw=="], - "sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.97.3", "", { "os": "win32", "cpu": "x64" }, "sha512-SFRa2lED9UEwV6vIGeBXeBOLKF+rowF3WmNfb/BzhxmdAsKofCXrJ8ePW7OcDVrvNEbTOGwhsReIsF5sH8fVaw=="], + "sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.99.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ipuOv1R2K4MHeuCEAZGpuUbAgma4gb0sdacyrTjJtMOy/OY9UvWfVlwErdB09KIkp4fPDpQJDJfvYN6bC8jeNg=="], - "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], @@ -1656,9 +1616,9 @@ "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "seroval": ["seroval@1.4.2", "", {}, "sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ=="], + "seroval": ["seroval@1.5.4", "", {}, "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw=="], - "seroval-plugins": ["seroval-plugins@1.4.2", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-X7p4MEDTi+60o2sXZ4bnDBhgsUYDSkQEvzYZuJyFqWg9jcoPsHts5nrg5O956py2wyt28lUrBxk0M0/wU8URpA=="], + "seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -1688,7 +1648,7 @@ "socket.io-parser": ["socket.io-parser@4.2.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ=="], - "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -1730,6 +1690,8 @@ "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + "strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="], + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="], @@ -1744,7 +1706,9 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "svgo": ["svgo@3.3.2", "", { "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0" }, "bin": "./bin/svgo" }, "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw=="], + "svg-icon-baker": ["svg-icon-baker@2.0.1", "", { "dependencies": { "css-tree": "^3.2.1", "fast-xml-builder": "^1.1.5", "fast-xml-parser": "^5.7.2", "svgo": "^4.0.1" } }, "sha512-1QWVlle2fSUra129CEKpo5sn4hLGa0KCd7v8kX+PkD8e1x8fAQXB5rkc8J8mXTHe6iSzcOS7j4Y2K+q9RaUoNQ=="], + + "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], "sync-child-process": ["sync-child-process@1.0.2", "", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="], @@ -1754,15 +1718,15 @@ "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], - "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], + "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], - "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="], - "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="], + "terser": ["terser@5.36.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w=="], "text-extensions": ["text-extensions@1.9.0", "", {}, "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ=="], @@ -1770,10 +1734,6 @@ "through2": ["through2@4.0.2", "", { "dependencies": { "readable-stream": "3" } }, "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw=="], - "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], - - "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], - "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], @@ -1788,7 +1748,7 @@ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], - "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], + "tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="], "tough-cookie-file-store": ["tough-cookie-file-store@3.3.0", "", { "dependencies": { "tough-cookie": "^6.0.0" } }, "sha512-FbO/cOi/jp4wweo8soVNG/ZjDsgpBZWqaxWwu7gRKvsjg/Qt44kStp87VLfJnin749DlTbZDYvV1wuSr5jly2g=="], @@ -1840,7 +1800,7 @@ "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], + "unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="], "unzip-stream": ["unzip-stream@0.3.4", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="], @@ -1866,9 +1826,9 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "vite": ["vite@7.3.3", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA=="], - "vite-plugin-svg-icons-ng": ["vite-plugin-svg-icons-ng@1.5.2", "", { "dependencies": { "fast-glob": "^3.3.3", "fs-extra": "^11.3.2", "node-html-parser": "^7.0.1", "svgo": "^3.3.2" }, "peerDependencies": { "vite": ">=5.0.0" } }, "sha512-A68obs8XDT+q8q8dKyjrT/v0qw8h5pEBKXJ27aUXjARYeJ6MNvhIhRLLiUwnSrbn/B4TBF4UVaWRXKftAqP7+A=="], + "vite-plugin-svg-icons-ng": ["vite-plugin-svg-icons-ng@1.9.0", "", { "dependencies": { "svg-icon-baker": "2.0.1", "tinyglobby": "^0.2.16" }, "peerDependencies": { "vite": ">=5.0.0" } }, "sha512-vIyinFqjR5gEJiDt1MTFGewAJnwyB7tkZ9fjKQ9m9Wa7XmxTTAcj8h1l3C4zA02K6y/4ZuPYCLzHLovoUPDW6w=="], "vite-static-assets-plugin": ["vite-static-assets-plugin@1.2.2", "", { "dependencies": { "chalk": "^5.4.1", "chokidar": "^3.5.3", "minimatch": "^10.0.1" }, "peerDependencies": { "typescript": "^5.0.0", "vite": "^6.2.0" } }, "sha512-0mzHsxFa46Np5AixQcdWYLVH6eJxeok7qL7tXmxYavg/Uo0e5z+J6gavJ0TJ6dmJSe2Z+gwmDb64bCCZfg+gqA=="], @@ -1922,16 +1882,36 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "zod-to-ts": ["zod-to-ts@2.0.0", "", { "peerDependencies": { "typescript": "^5.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-aHsUgIl+CQutKAxtRNeZslLCLXoeuSq+j5HU7q3kvi/c2KIAo6q4YjT7/lwFfACxLB923ELHYMkHmlxiqFy4lw=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@ap0nia/eden/elysia": ["elysia@1.2.15", "", { "dependencies": { "@sinclair/typebox": "^0.34.15", "cookie": "^1.0.2", "memoirist": "^0.3.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-/oUSNb83jIWAGi6uSmbQ7Uy0RSJ9NimbVToSLnYS8jjsGId3zgdHqprsdf4rIMInOmEM8skjsFhZ4x8C5AB6+w=="], + + "@babel/core/@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/core/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/generator/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/parser/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@babel/template/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/template/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -1940,8 +1920,6 @@ "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "@jimp/core/file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="], - "@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], "@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -1982,8 +1960,6 @@ "@node-minify/core/mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], - "@node-minify/terser/terser": ["terser@5.36.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], @@ -1996,22 +1972,32 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@tanstack/react-store/@tanstack/store": ["@tanstack/store@0.8.0", "", {}, "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ=="], + "@tanstack/router-generator/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - "@tanstack/router-core/@tanstack/store": ["@tanstack/store@0.8.0", "", {}, "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ=="], + "@tanstack/router-generator/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@tanstack/router-utils/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@tanstack/router-utils/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "@types/babel__core/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@types/babel__template/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "babel-dead-code-elimination/@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], + + "babel-dead-code-elimination/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "compare-func/dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], "conventional-changelog-writer/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -2038,8 +2024,6 @@ "glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "handlebars/wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], @@ -2060,8 +2044,6 @@ "meow/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "minimist-options/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="], "nypm/citty": ["citty@0.2.0", "", {}, "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA=="], @@ -2084,34 +2066,38 @@ "read-pkg-up/find-up": ["find-up@2.1.0", "", { "dependencies": { "locate-path": "^2.0.0" } }, "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ=="], - "readable-web-to-node-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], - "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "sass/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "standard-version/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], "standard-version/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "svgo/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "tough-cookie-file-store/tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], + "tsx/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], "vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + "vite-plugin-svg-icons-ng/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "vite-static-assets-plugin/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "winston/async": ["async@1.0.0", "", {}, "sha512-5mO7DX4CbJzp9zjaFXusQQ4tzKJARjNB1Ih1pVBi8wkbmXy/xzIDgEMXxWePLzt2OdFwaxfneIlT1nCiXubrPQ=="], + "xml2js/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + + "@ap0nia/eden/elysia/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "@ap0nia/eden/elysia/memoirist": ["memoirist@0.3.1", "", {}, "sha512-lmk4Z45IuVZPT67nxAdD3rAsNExxMEBFXgCeJGJnoLkYOjmZnJ8Hmi+MGdl9oLKtAENFAAgG8FvV3Z8BNiqy8w=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -2162,13 +2148,15 @@ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "@jimp/core/file-type/strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="], - - "@jimp/core/file-type/token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="], - "@node-minify/core/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "@node-minify/terser/terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "@tanstack/router-utils/tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "babel-dead-code-elimination/@babel/core/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "babel-dead-code-elimination/@babel/core/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "babel-dead-code-elimination/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], @@ -2258,6 +2246,8 @@ "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "vite-plugin-svg-icons-ng/tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], @@ -2342,6 +2332,8 @@ "meow/read-pkg-up/read-pkg/normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + "meow/read-pkg-up/read-pkg/parse-json/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + "read-pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@1.3.0", "", { "dependencies": { "p-try": "^1.0.0" } }, "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q=="], "standard-version/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], diff --git a/package.json b/package.json index 0f246cb..da5fcec 100644 --- a/package.json +++ b/package.json @@ -55,53 +55,53 @@ "dependencies": { "7zip-bin": "^5.2.0", "@auth/core": "^0.34.3", - "@elysiajs/cors": "^1.4.1", - "@elysiajs/eden": "^1.4.6", - "@jimp/wasm-webp": "^1.6.0", + "@elysiajs/cors": "^1.4.2", + "@elysiajs/eden": "^1.4.9", + "@jimp/wasm-webp": "^1.6.1", "@phalcode/ts-igdb-client": "^1.0.26", "cheerio": "^1.2.0", - "conf": "^15.0.2", - "drizzle-orm": "^0.45.1", - "elysia": "^1.4.22", - "fs-extra": "^11.3.3", + "conf": "^15.1.0", + "drizzle-orm": "^0.45.2", + "elysia": "^1.4.28", + "fs-extra": "^11.3.5", "get-folder-size": "^5.0.0", "ini": "^6.0.0", - "jimp": "^1.6.0", + "jimp": "^1.6.1", "mustache": "^4.2.0", "node-7z": "^3.0.0", "node-disk-info": "^1.3.0", - "node-downloader-helper": "^2.1.10", + "node-downloader-helper": "^2.1.11", "node-stream-zip": "^1.15.0", "node-unrar-js": "^2.0.2", "open": "^11.0.0", - "p-queue": "^9.1.2", + "p-queue": "^9.2.0", "pathe": "^2.0.3", "slugify": "^1.6.9", "smol-toml": "^1.6.1", "systeminformation": "^5.31.5", - "tapable": "^2.3.0", - "tough-cookie": "^6.0.0", + "tapable": "^2.3.3", + "tough-cookie": "^6.0.1", "tough-cookie-file-store": "^3.3.0", "unzip-stream": "^0.3.4", "webview-bun": "^2.4.0", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { - "@ap0nia/eden": "^1.0.0-next.22", + "@ap0nia/eden": "^1.6.1", "@ap0nia/eden-tanstack-query": "^1.0.0-next.22", "@emulatorjs/emulatorjs": "^4.2.3", - "@hey-api/openapi-ts": "^0.91.0", + "@hey-api/openapi-ts": "^0.91.1", "@noriginmedia/norigin-spatial-navigation": "^3.1.0", "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.1.18", - "@tanstack/react-form": "^1.28.0", - "@tanstack/react-query": "^5.90.20", - "@tanstack/react-query-devtools": "^5.91.3", - "@tanstack/react-router": "^1.157.16", - "@tanstack/react-router-devtools": "^1.154.12", - "@tanstack/react-router-ssr-query": "^1.157.17", - "@tanstack/router-plugin": "^1.157.16", - "@tanstack/zod-adapter": "^1.162.4", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/react-form": "^1.29.1", + "@tanstack/react-query": "^5.100.9", + "@tanstack/react-query-devtools": "^5.100.9", + "@tanstack/react-router": "^1.169.2", + "@tanstack/react-router-devtools": "^1.166.13", + "@tanstack/react-router-ssr-query": "^1.166.12", + "@tanstack/router-plugin": "^1.167.35", + "@tanstack/zod-adapter": "^1.166.9", "@types/adm-zip": "^0.5.8", "@types/audiosprite": "^0.7.3", "@types/bun": "latest", @@ -112,11 +112,11 @@ "@types/mustache": "^4.2.6", "@types/node-7z": "^2.1.11", "@types/rclone.js": "^0.6.3", - "@types/react": "^19.2.9", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/unzip-stream": "^0.3.4", - "@vitejs/plugin-react": "^5.1.2", - "adm-zip": "^0.5.16", + "@vitejs/plugin-react": "^5.2.0", + "adm-zip": "^0.5.17", "animate.css": "^4.1.1", "app-builder-bin": "^5.0.0-alpha.13", "audiosprite": "^0.7.2", @@ -124,29 +124,29 @@ "classnames": "^2.5.1", "concurrently": "^9.2.1", "cross-env": "^10.1.0", - "daisyui": "^5.5.14", - "drizzle-kit": "^0.31.9", + "daisyui": "^5.5.19", + "drizzle-kit": "^0.31.10", "dts-bundle-generator": "^9.5.1", "eden-tanstack-query": "^0.0.9", "howler": "^2.2.4", "lucide-react": "^0.563.0", "pretty-bytes": "^7.1.0", "pretty-ms": "^9.3.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-error-boundary": "^6.1.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-error-boundary": "^6.1.1", "react-hot-toast": "^2.6.0", "react-markdown": "^10.1.0", - "react-qr-code": "^2.0.18", - "sass-embedded": "^1.97.3", + "react-qr-code": "^2.0.21", + "sass-embedded": "^1.99.0", "standard-version": "^9.5.0", - "tailwind-merge": "^3.4.0", - "tailwindcss": "^4.1.18", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.4", "tailwindcss-animate": "^1.0.7", "typescript": "^5.9.3", "usehooks-ts": "^3.1.1", - "vite": "^7.3.1", - "vite-plugin-svg-icons-ng": "^1.5.2", + "vite": "^7.3.3", + "vite-plugin-svg-icons-ng": "^1.9.0", "vite-static-assets-plugin": "^1.2.2", "vite-tsconfig-paths": "^6.1.1", "zod-to-ts": "^2.0.0" diff --git a/src/bun/api/jobs/update-store.ts b/src/bun/api/jobs/update-store.ts index b051f07..48129a0 100644 --- a/src/bun/api/jobs/update-store.ts +++ b/src/bun/api/jobs/update-store.ts @@ -20,16 +20,12 @@ export default class UpdateStoreJob implements IJob this.storeVersion = process.env.STORE_VERSION ?? "^0.1.0"; } - async start (context: JobContext) + async runCommand (commands: string[]) { - if (process.env.CUSTOM_STORE_PATH) return; - const tempCache = path.join(tmpdir(), "gameflow-bun-cache"); const storeFolder = getStoreRootFolder(); - await ensureDir(storeFolder); - console.log("Adding Store Package"); - let proc = Bun.spawn([process.execPath, "add", `${this.packageName}@${this.storeVersion}`, "--registry", this.registry.href], { + let proc = Bun.spawn([process.execPath, ...commands, "--registry", this.registry.href, '--json'], { cwd: storeFolder, stdout: 'pipe', stderr: 'pipe', @@ -45,23 +41,19 @@ export default class UpdateStoreJob implements IJob if (stderr) console.error(stderr); await proc.exited; + } + + async start (context: JobContext) + { + if (process.env.CUSTOM_STORE_PATH) return; + + const storeFolder = getStoreRootFolder(); + await ensureDir(storeFolder); + + console.log("Adding Store Package"); + await this.runCommand(["add", `${this.packageName}@${this.storeVersion}`]); console.log("Updating Store Package"); - proc = Bun.spawn([process.execPath, "update", `${this.packageName}@${this.storeVersion}`, "--registry", this.registry.href], { - cwd: storeFolder, - stdout: 'pipe', - stderr: 'pipe', - env: { - BUN_BE_BUN: "1", - BUN_INSTALL_CACHE_DIR: tempCache - } - }); - - stdout = await new Response(proc.stdout).text(); - console.log(stdout); - stderr = await new Response(proc.stderr).text(); - if (stderr) - console.error(stderr); - await proc.exited; + await this.runCommand(["update", `${this.packageName}@${this.storeVersion}`]); } } \ No newline at end of file From 38cb7525527b5ad4f6eb284cdad0001fd87eaf7e Mon Sep 17 00:00:00 2001 From: Simeon Radivoev Date: Sun, 10 May 2026 01:46:57 +0300 Subject: [PATCH 3/7] feat: Implemented public plugin system accessible from the store. feat: Implemented external ryujinx integration plugin refactor: moved sdk types and schemas to own workspace package fix: Fixed emulator launch with no game --- bun.lock | 128 ++-- package.json | 14 +- scripts/build-sdk.ts | 64 -- scripts/sdk/package.json | 9 - scripts/sdk/sdk.ts | 18 - src/bun/api/app.ts | 5 +- src/bun/api/cache.ts | 5 +- src/bun/api/drives.ts | 2 +- src/bun/api/emulatorjs/emulatorjs.ts | 2 +- src/bun/api/games/collections.ts | 2 +- src/bun/api/games/games.ts | 5 +- src/bun/api/games/platforms.ts | 2 +- .../api/games/services/launchGameService.ts | 2 +- src/bun/api/games/services/statusService.ts | 43 +- src/bun/api/games/services/utils.ts | 2 +- src/bun/api/jobs/bios-download-job.ts | 2 +- src/bun/api/jobs/emulator-download-job.ts | 6 +- src/bun/api/jobs/import-job.ts | 4 +- src/bun/api/jobs/install-job.ts | 4 +- src/bun/api/jobs/jobs.ts | 2 +- src/bun/api/jobs/launch-game-job.ts | 6 +- src/bun/api/jobs/login-job.ts | 2 +- src/bun/api/jobs/plugin-operation-job.ts | 62 ++ src/bun/api/jobs/reload-plugins-job.ts | 2 +- src/bun/api/jobs/self-update-job.ts | 2 +- src/bun/api/jobs/twitch-login-job.ts | 2 +- src/bun/api/jobs/update-store.ts | 70 +- src/bun/api/notifications.ts | 2 +- .../com.simeonradivoev.gameflow.cemu/cemu.ts | 2 +- .../dolphin.ts | 2 +- .../pcsx2.ts | 4 +- .../ppsspp.ts | 4 +- .../com.simeonradivoev.gameflow.xemu/xemu.ts | 2 +- .../xenia.ts | 4 +- .../com.simeonradivoev.gameflow.es/es-de.ts | 4 +- .../rclone.ts | 2 +- .../com.simeonradivoev.gameflow.igdb/igdb.ts | 4 +- .../com.simeonradivoev.gameflow.romm/romm.ts | 4 +- .../package.json | 4 +- .../services.ts | 3 +- .../store.ts | 7 +- src/bun/api/plugins/plugin-manager.ts | 56 +- src/bun/api/plugins/plugins.ts | 40 +- src/bun/api/plugins/register-plugins.ts | 138 ++-- src/bun/api/plugins/services.ts | 62 ++ src/bun/api/schema/app.ts | 3 +- src/bun/api/settings/services.ts | 2 +- src/bun/api/settings/settings.ts | 14 +- .../api/store/services/emulatorsService.ts | 3 +- src/bun/api/store/services/gamesService.ts | 3 +- src/bun/api/store/store.ts | 56 +- src/bun/api/system.ts | 3 +- src/bun/types/types.ts | 18 - src/bun/utils.ts | 3 +- src/bun/utils/downloader.ts | 2 +- src/mainview/components/AppCommunication.tsx | 2 +- src/mainview/components/CollectionsDetail.tsx | 2 +- src/mainview/components/FilePicker.tsx | 2 +- src/mainview/components/FrontEndGameCard.tsx | 2 +- src/mainview/components/GameList.tsx | 5 +- src/mainview/components/GamepadKeyboard.tsx | 2 +- src/mainview/components/HeaderSearchField.tsx | 1 - src/mainview/components/LoadMoreButton.tsx | 1 + src/mainview/components/Notifications.tsx | 2 +- src/mainview/components/SideFilters.tsx | 4 +- src/mainview/components/game/Achievements.tsx | 2 +- .../components/game/ActionButtons.tsx | 2 +- src/mainview/components/game/Details.tsx | 2 +- src/mainview/components/game/GameLookup.tsx | 2 +- src/mainview/components/game/MainActions.tsx | 3 +- .../options/DownloadDirectoryOption.tsx | 3 +- .../components/options/LocalOption.tsx | 2 +- .../components/options/PathSettingsOption.tsx | 3 +- .../components/options/SettingsDropdown.tsx | 3 +- .../components/options/SettingsOption.tsx | 3 +- .../components/store/EmulatorsSection.tsx | 2 +- .../components/store/GamesSection.tsx | 2 +- .../store/MissingEmulatorsSection.tsx | 2 +- .../components/store/StoreEmulatorCard.tsx | 2 +- src/mainview/gen/routeTree.gen.ts | 42 ++ src/mainview/query-options.ts | 3 +- .../routes/collection.$source.$id.tsx | 2 +- src/mainview/routes/game/$source.$id.tsx | 2 +- .../routes/game/update.$source.$id.tsx | 6 +- src/mainview/routes/games.tsx | 2 +- src/mainview/routes/index.tsx | 2 +- src/mainview/routes/platform.$source.$id.tsx | 3 +- src/mainview/routes/settings/accounts.tsx | 3 +- src/mainview/routes/settings/directories.tsx | 5 +- src/mainview/routes/settings/emulators.tsx | 8 +- src/mainview/routes/settings/interface.tsx | 6 +- .../routes/settings/plugin.$source.tsx | 55 +- src/mainview/routes/settings/plugins.tsx | 16 +- src/mainview/routes/settings/update.tsx | 2 +- .../routes/store/details.emulator.$id.tsx | 2 +- .../routes/store/details.plugin.$id.tsx | 161 +++++ src/mainview/routes/store/tab/games.tsx | 4 +- src/mainview/routes/store/tab/index.tsx | 2 +- src/mainview/routes/store/tab/plugins.tsx | 151 +++++ src/mainview/routes/store/tab/route.tsx | 8 +- src/mainview/scripts/contexts.ts | 3 +- src/mainview/scripts/queries/plugins.ts | 50 +- src/mainview/scripts/queries/romm.ts | 4 +- src/mainview/scripts/queries/settings.ts | 10 +- src/mainview/scripts/queries/store.ts | 22 +- src/mainview/scripts/types.ts | 3 +- src/mainview/scripts/utils.ts | 2 +- .../packages/gameflow-sdk}/README.md | 15 + src/packages/gameflow-sdk/build.ts | 27 + .../gameflow-sdk}/hooks/app.ts | 2 +- .../gameflow-sdk}/hooks/auth.ts | 3 +- .../gameflow-sdk}/hooks/emulators.ts | 9 +- .../gameflow-sdk}/hooks/games.ts | 6 +- .../gameflow-sdk}/hooks/store.ts | 3 +- .../gameflow-sdk/index.ts} | 61 +- src/packages/gameflow-sdk/package.json | 51 ++ .../packages/gameflow-sdk}/sdk.tsconfig.json | 22 +- src/packages/gameflow-sdk/shared.ts | 631 ++++++++++++++++++ .../gameflow-sdk}/task-queue.ts | 3 +- src/shared/constants.ts | 209 +----- src/shared/types.schema.ts | 0 src/shared/types.ts | 387 ----------- src/tests/downloads.test.ts | 2 +- tsconfig.json | 1 + 124 files changed, 1918 insertions(+), 1067 deletions(-) delete mode 100644 scripts/build-sdk.ts delete mode 100644 scripts/sdk/package.json delete mode 100644 scripts/sdk/sdk.ts create mode 100644 src/bun/api/jobs/plugin-operation-job.ts create mode 100644 src/bun/api/plugins/services.ts delete mode 100644 src/bun/types/types.ts create mode 100644 src/mainview/routes/store/details.plugin.$id.tsx create mode 100644 src/mainview/routes/store/tab/plugins.tsx rename {scripts/sdk => src/packages/gameflow-sdk}/README.md (53%) create mode 100644 src/packages/gameflow-sdk/build.ts rename src/{bun/api => packages/gameflow-sdk}/hooks/app.ts (88%) rename src/{bun/api => packages/gameflow-sdk}/hooks/auth.ts (81%) rename src/{bun/api => packages/gameflow-sdk}/hooks/emulators.ts (83%) rename src/{bun/api => packages/gameflow-sdk}/hooks/games.ts (92%) rename src/{bun/api => packages/gameflow-sdk}/hooks/store.ts (86%) rename src/{bun/types/types.schema.ts => packages/gameflow-sdk/index.ts} (72%) create mode 100644 src/packages/gameflow-sdk/package.json rename {scripts/sdk => src/packages/gameflow-sdk}/sdk.tsconfig.json (52%) create mode 100644 src/packages/gameflow-sdk/shared.ts rename src/{bun/api => packages/gameflow-sdk}/task-queue.ts (99%) create mode 100644 src/shared/types.schema.ts diff --git a/bun.lock b/bun.lock index 5e7c073..cd65d27 100644 --- a/bun.lock +++ b/bun.lock @@ -7,53 +7,54 @@ "dependencies": { "7zip-bin": "^5.2.0", "@auth/core": "^0.34.3", - "@elysiajs/cors": "^1.4.1", - "@elysiajs/eden": "^1.4.6", - "@jimp/wasm-webp": "^1.6.0", + "@elysiajs/cors": "^1.4.2", + "@elysiajs/eden": "^1.4.9", + "@jimp/wasm-webp": "^1.6.1", "@phalcode/ts-igdb-client": "^1.0.26", "cheerio": "^1.2.0", - "conf": "^15.0.2", - "drizzle-orm": "^0.45.1", - "elysia": "^1.4.22", - "fs-extra": "^11.3.3", + "conf": "^15.1.0", + "drizzle-orm": "^0.45.2", + "elysia": "^1.4.28", + "fs-extra": "^11.3.5", "get-folder-size": "^5.0.0", "ini": "^6.0.0", - "jimp": "^1.6.0", + "jimp": "^1.6.1", "mustache": "^4.2.0", "node-7z": "^3.0.0", "node-disk-info": "^1.3.0", - "node-downloader-helper": "^2.1.10", + "node-downloader-helper": "^2.1.11", "node-stream-zip": "^1.15.0", "node-unrar-js": "^2.0.2", + "npm-check-updates": "^22.1.1", "open": "^11.0.0", - "p-queue": "^9.1.2", + "p-queue": "^9.2.0", "pathe": "^2.0.3", "slugify": "^1.6.9", "smol-toml": "^1.6.1", "systeminformation": "^5.31.5", - "tapable": "^2.3.0", - "tough-cookie": "^6.0.0", + "tapable": "^2.3.3", + "tough-cookie": "^6.0.1", "tough-cookie-file-store": "^3.3.0", "unzip-stream": "^0.3.4", "webview-bun": "^2.4.0", - "zod": "^4.3.6", + "zod": "^4.4.3", }, "devDependencies": { - "@ap0nia/eden": "^1.0.0-next.22", + "@ap0nia/eden": "^1.6.1", "@ap0nia/eden-tanstack-query": "^1.0.0-next.22", "@emulatorjs/emulatorjs": "^4.2.3", - "@hey-api/openapi-ts": "^0.91.0", + "@hey-api/openapi-ts": "^0.91.1", "@noriginmedia/norigin-spatial-navigation": "^3.1.0", "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.1.18", - "@tanstack/react-form": "^1.28.0", - "@tanstack/react-query": "^5.90.20", - "@tanstack/react-query-devtools": "^5.91.3", - "@tanstack/react-router": "^1.157.16", - "@tanstack/react-router-devtools": "^1.154.12", - "@tanstack/react-router-ssr-query": "^1.157.17", - "@tanstack/router-plugin": "^1.157.16", - "@tanstack/zod-adapter": "^1.162.4", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/react-form": "^1.29.1", + "@tanstack/react-query": "^5.100.9", + "@tanstack/react-query-devtools": "^5.100.9", + "@tanstack/react-router": "^1.169.2", + "@tanstack/react-router-devtools": "^1.166.13", + "@tanstack/react-router-ssr-query": "^1.166.12", + "@tanstack/router-plugin": "^1.167.35", + "@tanstack/zod-adapter": "^1.166.9", "@types/adm-zip": "^0.5.8", "@types/audiosprite": "^0.7.3", "@types/bun": "latest", @@ -64,11 +65,11 @@ "@types/mustache": "^4.2.6", "@types/node-7z": "^2.1.11", "@types/rclone.js": "^0.6.3", - "@types/react": "^19.2.9", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/unzip-stream": "^0.3.4", - "@vitejs/plugin-react": "^5.1.2", - "adm-zip": "^0.5.16", + "@vitejs/plugin-react": "^5.2.0", + "adm-zip": "^0.5.17", "animate.css": "^4.1.1", "app-builder-bin": "^5.0.0-alpha.13", "audiosprite": "^0.7.2", @@ -76,32 +77,71 @@ "classnames": "^2.5.1", "concurrently": "^9.2.1", "cross-env": "^10.1.0", - "daisyui": "^5.5.14", - "drizzle-kit": "^0.31.9", - "dts-bundle-generator": "^9.5.1", + "daisyui": "^5.5.19", + "drizzle-kit": "^0.31.10", "eden-tanstack-query": "^0.0.9", "howler": "^2.2.4", "lucide-react": "^0.563.0", "pretty-bytes": "^7.1.0", "pretty-ms": "^9.3.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-error-boundary": "^6.1.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-error-boundary": "^6.1.1", "react-hot-toast": "^2.6.0", "react-markdown": "^10.1.0", - "react-qr-code": "^2.0.18", - "sass-embedded": "^1.97.3", + "react-qr-code": "^2.0.21", + "sass-embedded": "^1.99.0", "standard-version": "^9.5.0", - "tailwind-merge": "^3.4.0", - "tailwindcss": "^4.1.18", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.4", "tailwindcss-animate": "^1.0.7", "typescript": "^5.9.3", "usehooks-ts": "^3.1.1", - "vite": "^7.3.1", - "vite-plugin-svg-icons-ng": "^1.5.2", + "vite": "^7.3.3", + "vite-plugin-svg-icons-ng": "^1.9.0", "vite-static-assets-plugin": "^1.2.2", "vite-tsconfig-paths": "^6.1.1", - "zod-to-ts": "^2.0.0", + }, + }, + "src/packages/gameflow-sdk": { + "name": "@simeonradivoev/gameflow-sdk", + "version": "1.5.3", + "bin": { + "gameflow-build": "build.ts", + }, + "peerDependencies": { + "7zip-bin": "^5.2.0", + "@auth/core": "^0.34.3", + "@elysiajs/cors": "^1.4.2", + "@elysiajs/eden": "^1.4.9", + "@jimp/wasm-webp": "^1.6.1", + "@phalcode/ts-igdb-client": "^1.0.26", + "cheerio": "^1.2.0", + "conf": "^15.1.0", + "drizzle-orm": "^0.45.2", + "elysia": "^1.4.28", + "fs-extra": "^11.3.5", + "get-folder-size": "^5.0.0", + "ini": "^6.0.0", + "jimp": "^1.6.1", + "mustache": "^4.2.0", + "node-7z": "^3.0.0", + "node-disk-info": "^1.3.0", + "node-downloader-helper": "^2.1.11", + "node-stream-zip": "^1.15.0", + "node-unrar-js": "^2.0.2", + "open": "^11.0.0", + "p-queue": "^9.2.0", + "pathe": "^2.0.3", + "slugify": "^1.6.9", + "smol-toml": "^1.6.1", + "systeminformation": "^5.31.5", + "tapable": "^2.3.3", + "tough-cookie": "^6.0.1", + "tough-cookie-file-store": "^3.3.0", + "unzip-stream": "^0.3.4", + "webview-bun": "^2.4.0", + "zod": "^4.4.3", }, }, }, @@ -520,6 +560,8 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g=="], + "@simeonradivoev/gameflow-sdk": ["@simeonradivoev/gameflow-sdk@workspace:src/packages/gameflow-sdk"], + "@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="], "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], @@ -948,8 +990,6 @@ "drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], - "dts-bundle-generator": ["dts-bundle-generator@9.5.1", "", { "dependencies": { "typescript": ">=5.0.2", "yargs": "^17.6.0" }, "bin": { "dts-bundle-generator": "dist/bin/dts-bundle-generator.js" } }, "sha512-DxpJOb2FNnEyOzMkG11sxO2dmxPjthoVWxfKqWYJ/bI/rT1rvTMktF5EKjAYrRZu6Z6t3NhOUZ0sZ5ZXevOfbA=="], - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="], @@ -1394,6 +1434,8 @@ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "npm-check-updates": ["npm-check-updates@22.1.1", "", { "bin": { "npm-check-updates": "build/cli.js", "ncu": "build/cli.js" } }, "sha512-uWSxJW25dy5ZM4SdLsi0VBgPSJlo7u+jARQ6Xql+85YYCoqXU2ZaympAZ6237/oybCq/I4nXddE9S9BTwBfBXA=="], + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], "nypm": ["nypm@0.6.4", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw=="], @@ -1884,8 +1926,6 @@ "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "zod-to-ts": ["zod-to-ts@2.0.0", "", { "peerDependencies": { "typescript": "^5.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-aHsUgIl+CQutKAxtRNeZslLCLXoeuSq+j5HU7q3kvi/c2KIAo6q4YjT7/lwFfACxLB923ELHYMkHmlxiqFy4lw=="], - "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], "@ap0nia/eden/elysia": ["elysia@1.2.15", "", { "dependencies": { "@sinclair/typebox": "^0.34.15", "cookie": "^1.0.2", "memoirist": "^0.3.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-/oUSNb83jIWAGi6uSmbQ7Uy0RSJ9NimbVToSLnYS8jjsGId3zgdHqprsdf4rIMInOmEM8skjsFhZ4x8C5AB6+w=="], diff --git a/package.json b/package.json index da5fcec..bf49ccc 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,9 @@ }, "packageManager": "bun@1.3.9", "type": "module", + "workspaces": [ + "./src/packages/gameflow-sdk" + ], "scripts": { "dev": "NODE_ENV=development bun run build:vite && conc 'bun run ./scripts/dev.ts'", "dev:hmr": "PUBLIC_ACCESS=true conc -k 'bun run hmr' 'bun run ./scripts/dev.ts'", @@ -27,6 +30,7 @@ "build:prod:vite": "NODE_ENV=production bun run build:vite", "build:dev:vite": "NODE_ENV=development bun run build:vite", "build": "bun run build:vite && bun run ./scripts/package-bun.ts", + "build:non-compiled": "bun run build:vite && NON_COMPILED=true bun run ./scripts/package-bun.ts", "build:prod": "NODE_ENV=production bun run build", "build:linux": "TARGET=bun-linux-x64 bun run build", "openapi-ts": "bun run ./scripts/romm/openapi-ts.ts", @@ -49,8 +53,7 @@ "download:nwjs": "bun scripts/download-nw.ts", "build:audiosprites": "bun ./scripts/generate-audio-sprites.ts", "tsc": "tsc --noEmit", - "build:sdk": "bun ./scripts/build-sdk.ts", - "publish:sdk": "bun build:sdk && bun publish --cwd ./dist-sdk/ --access public" + "publish:sdk": "bun publish --cwd ./src/packages/gameflow-sdk/ --access public" }, "dependencies": { "7zip-bin": "^5.2.0", @@ -73,6 +76,7 @@ "node-downloader-helper": "^2.1.11", "node-stream-zip": "^1.15.0", "node-unrar-js": "^2.0.2", + "npm-check-updates": "^22.1.1", "open": "^11.0.0", "p-queue": "^9.2.0", "pathe": "^2.0.3", @@ -126,7 +130,6 @@ "cross-env": "^10.1.0", "daisyui": "^5.5.19", "drizzle-kit": "^0.31.10", - "dts-bundle-generator": "^9.5.1", "eden-tanstack-query": "^0.0.9", "howler": "^2.2.4", "lucide-react": "^0.563.0", @@ -148,7 +151,6 @@ "vite": "^7.3.3", "vite-plugin-svg-icons-ng": "^1.9.0", "vite-static-assets-plugin": "^1.2.2", - "vite-tsconfig-paths": "^6.1.1", - "zod-to-ts": "^2.0.0" + "vite-tsconfig-paths": "^6.1.1" } -} +} \ No newline at end of file diff --git a/scripts/build-sdk.ts b/scripts/build-sdk.ts deleted file mode 100644 index 11ee929..0000000 --- a/scripts/build-sdk.ts +++ /dev/null @@ -1,64 +0,0 @@ -import path from 'node:path'; -import appPkg from '../package.json'; -import sdkTsConfig from './sdk/sdk.tsconfig.json'; -import sdkPackage from './sdk/package.json'; -import { emptyDir } from 'fs-extra'; -import { generateDtsBundle } from 'dts-bundle-generator'; -import { zodToTs, createAuxiliaryTypeStore, printNode } from 'zod-to-ts'; -import fs from 'node:fs/promises'; - -import * as types from './sdk/sdk'; - -const zodTypeRegex = /z\.infer/gm; - -async function generateApiDeclarations () -{ - const tmpConfigPath = "./scripts/sdk/sdk.tsconfig.json"; - const outDir = path.join(path.dirname(tmpConfigPath), sdkTsConfig.compilerOptions.outDir); - await emptyDir(outDir); - - const results = generateDtsBundle([{ - filePath: './scripts/sdk/sdk.ts', - output: { - inlineDeclareGlobals: true, - sortNodes: true, - } - },], { preferredConfigPath: './scripts/sdk/sdk.tsconfig.json' }); - - const auxiliaryTypeStore = createAuxiliaryTypeStore(); - - await Bun.write('./dist-sdk/index.d.ts', results.map(r => - { - const result = r; - return result.replaceAll(zodTypeRegex, (e, name) => - { - const schema = types[name as keyof typeof types]; - if (schema) - { - try - { - const { node } = zodToTs(schema as any, { auxiliaryTypeStore, unrepresentable: 'any' }); - return printNode(node); - } catch (error) - { - console.error(error); - return e; - } - } - return e; - }); - })); - - const pkg = { - ...sdkPackage, - license: appPkg.license, - version: appPkg.version, - repository: appPkg.repository, - author: appPkg.author, - peerDependencies: appPkg.dependencies - }; - await Bun.write(path.join(outDir, 'package.json'), JSON.stringify(pkg, null, 3)); - await fs.cp('./scripts/sdk/README.md', path.join(outDir, 'README.md')); -} - -await generateApiDeclarations(); \ No newline at end of file diff --git a/scripts/sdk/package.json b/scripts/sdk/package.json deleted file mode 100644 index 17d855e..0000000 --- a/scripts/sdk/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@simeonradivoev/gameflow-sdk", - "types": "index.d.ts", - "description": "plugin SDK for the Gameflow Deck Launcher", - "keywords": [ - "gameflow", - "sdk" - ] -} \ No newline at end of file diff --git a/scripts/sdk/sdk.ts b/scripts/sdk/sdk.ts deleted file mode 100644 index 6568653..0000000 --- a/scripts/sdk/sdk.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { SettingsType } from '@/shared/constants'; -import Conf from 'conf'; -import { AppEventMap } from '../../src/bun/types/types'; -import EventEmitter from "node:events"; -import { TaskQueue } from '@/bun/api/task-queue'; - -export * from '../../src/bun/types/types.schema'; -export * from '../../src/bun/types/types'; -export * from '../../src/bun/api/hooks/app'; -export * from '../../src/shared/constants'; -export * from '../../src/shared/types'; -export * from '../../src/shared/utils'; - -export declare const config: Conf; -export declare let events: EventEmitter; -export declare let taskQueue: TaskQueue; - -export { }; \ No newline at end of file diff --git a/src/bun/api/app.ts b/src/bun/api/app.ts index db06c81..68ec287 100644 --- a/src/bun/api/app.ts +++ b/src/bun/api/app.ts @@ -1,5 +1,5 @@ -import { TaskQueue } from "./task-queue"; +import { TaskQueue, AppEventMap } from "@simeonradivoev/gameflow-sdk"; import { Database } from "bun:sqlite"; import { CookieJar } from 'tough-cookie'; import FileCookieStore from 'tough-cookie-file-store'; @@ -8,7 +8,7 @@ import { migrate } from "drizzle-orm/bun-sqlite/migrator"; import { BunSQLiteDatabase, drizzle } from "drizzle-orm/bun-sqlite"; import Conf from "conf"; import projectPackage from '~/package.json'; -import { SettingsSchema, SettingsType } from "@shared/constants"; +import { SettingsType, SettingsSchema } from '@simeonradivoev/gameflow-sdk/shared'; import { client } from "@clients/romm/client.gen"; import * as schema from "@schema/app"; import cacheSchema from "@schema/cache"; @@ -24,7 +24,6 @@ import controls from './controls/controls'; import { RunAPIServer } from "./rpc"; import { RunBunServer } from "../server"; import ReloadPluginsJob from "./jobs/reload-plugins-job"; -import { AppEventMap } from "../types/types"; export let config: Conf; export let customEmulators: Conf>; diff --git a/src/bun/api/cache.ts b/src/bun/api/cache.ts index cdad6b1..04abd1e 100644 --- a/src/bun/api/cache.ts +++ b/src/bun/api/cache.ts @@ -1,7 +1,7 @@ import { eq } from "drizzle-orm"; import { cache } from "./app"; import cacheSchema from "@schema/cache"; -import { GithubReleaseSchema } from "@/shared/constants"; +import { GithubReleaseSchema } from '@simeonradivoev/gameflow-sdk/shared'; import PQueue from "p-queue"; import z from "zod"; @@ -11,7 +11,8 @@ export const CACHE_KEYS = { STORE_GAME_MANIFEST: 'store-game-manifest' } as const; -export const githubRequestQueue = new PQueue({ intervalCap: 10, interval: 1000 * 60 * 10, strict: true }); +// we aggressively cache github data so burst of calls is fine. +export const githubRequestQueue = new PQueue({ intervalCap: 60, interval: 1000 * 60 * 60, strict: true }); export async function getOrCached (key: string, getter: (lastValue: T | undefined) => Promise, options?: { expireMs?: number; force?: boolean; }): Promise { diff --git a/src/bun/api/drives.ts b/src/bun/api/drives.ts index a7f0565..99452d8 100644 --- a/src/bun/api/drives.ts +++ b/src/bun/api/drives.ts @@ -1,7 +1,7 @@ import si from 'systeminformation'; import fs from 'node:fs'; import os from "node:os"; -import { Drive } from '@/shared/types'; +import { Drive } from '@simeonradivoev/gameflow-sdk/shared'; async function getAccess (path: string) { diff --git a/src/bun/api/emulatorjs/emulatorjs.ts b/src/bun/api/emulatorjs/emulatorjs.ts index d770a43..d733d34 100644 --- a/src/bun/api/emulatorjs/emulatorjs.ts +++ b/src/bun/api/emulatorjs/emulatorjs.ts @@ -5,7 +5,7 @@ import z from "zod"; import path from 'node:path'; import { config, events, plugins } from "../app"; import { getLocalGame, updateLocalLastPlayed } from "../games/services/statusService"; -import { SaveFileChange } from "@/shared/types"; +import { SaveFileChange } from "@simeonradivoev/gameflow-sdk/shared"; // TODO: use the retroarch cores based on ES-DE export const cores: Record = { diff --git a/src/bun/api/games/collections.ts b/src/bun/api/games/collections.ts index 1a49a12..ae31430 100644 --- a/src/bun/api/games/collections.ts +++ b/src/bun/api/games/collections.ts @@ -1,6 +1,6 @@ import Elysia, { status } from "elysia"; import { plugins } from "../app"; -import { FrontEndCollection } from "@/shared/types"; +import { FrontEndCollection } from "@simeonradivoev/gameflow-sdk/shared"; export default new Elysia() .get('/collections', async () => diff --git a/src/bun/api/games/games.ts b/src/bun/api/games/games.ts index 1b73e78..3c2575c 100644 --- a/src/bun/api/games/games.ts +++ b/src/bun/api/games/games.ts @@ -4,7 +4,8 @@ import { and, desc, eq, getTableColumns, inArray, like, sql } from "drizzle-orm" import z from "zod"; import * as schema from "@schema/app"; import fs from "node:fs/promises"; -import { GameListFilterSchema, SERVER_URL } from "@shared/constants"; +import { SERVER_URL } from "@shared/constants"; +import { GameListFilterSchema } from '@simeonradivoev/gameflow-sdk/shared'; import { InstallJob } from "../jobs/install-job"; import path from "node:path"; import { convertLocalToFrontend, getLocalGameMatch, getSourceGameDetailed } from "./services/utils"; @@ -22,7 +23,7 @@ import { LaunchGameJob } from "../jobs/launch-game-job"; import { cores } from "../emulatorjs/emulatorjs"; import { findEmulatorPluginIntegration } from "../store/services/emulatorsService"; import { ImportJob } from "../jobs/import-job"; -import { EmulatorSourceEntryType, EmulatorSystem, FrontEndFilterLists, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailedEmulator, FrontEndGameTypeWithIds, FrontEndId, GameLookup } from "@/shared/types"; +import { EmulatorSourceEntryType, EmulatorSystem, FrontEndFilterLists, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailedEmulator, FrontEndGameTypeWithIds, FrontEndId, GameLookup } from "@simeonradivoev/gameflow-sdk/shared"; // A custom jimp that supports webp const Jimp = createJimp({ diff --git a/src/bun/api/games/platforms.ts b/src/bun/api/games/platforms.ts index de888ba..10aaf42 100644 --- a/src/bun/api/games/platforms.ts +++ b/src/bun/api/games/platforms.ts @@ -4,7 +4,7 @@ import { and, count, eq, getTableColumns, not, notExists, or } from "drizzle-orm import { config, db, plugins } from "../app"; import * as schema from "@schema/app"; import { findPlatform } from "./services/utils"; -import { FrontEndPlatformType } from "@/shared/types"; +import { FrontEndPlatformType } from "@simeonradivoev/gameflow-sdk/shared"; export default new Elysia() .get('/platforms', async () => diff --git a/src/bun/api/games/services/launchGameService.ts b/src/bun/api/games/services/launchGameService.ts index 248d6f2..490850d 100644 --- a/src/bun/api/games/services/launchGameService.ts +++ b/src/bun/api/games/services/launchGameService.ts @@ -6,7 +6,7 @@ import { config, taskQueue } from '../../app'; import { LaunchGameJob } from '../../jobs/launch-game-job'; import { getStoreEmulatorPackage } from '../../store/services/gamesService'; import { getOrCachedScoopPackage } from '../../store/services/emulatorsService'; -import { CommandEntry, EmulatorSourceEntryType, FrontEndId } from '@/shared/types'; +import { CommandEntry, EmulatorSourceEntryType, FrontEndId } from '@simeonradivoev/gameflow-sdk/shared'; export async function launchCommand (validCommand: CommandEntry, id: FrontEndId, source?: string, sourceId?: string) { diff --git a/src/bun/api/games/services/statusService.ts b/src/bun/api/games/services/statusService.ts index 05bade3..1eaed5b 100644 --- a/src/bun/api/games/services/statusService.ts +++ b/src/bun/api/games/services/statusService.ts @@ -8,9 +8,10 @@ import z from "zod"; import { InstallJob, InstallJobStates } from "../../jobs/install-job"; import { LaunchGameJob } from "../../jobs/launch-game-job"; import * as appSchema from "@schema/app"; -import { DownloadSourceSchema, RPC_URL } from "@/shared/constants"; +import { RPC_URL } from "@/shared/constants"; +import { DownloadSourceSchema } from '@simeonradivoev/gameflow-sdk/shared'; import { host } from "@/bun/utils/host"; -import { CommandEntry, FrontEndId, GameLookup, GameStatusType, LocalDownloadFileEntry } from "@/shared/types"; +import { CommandEntry, FrontEndId, GameLookup, GameStatusType, LocalDownloadFileEntry } from "@simeonradivoev/gameflow-sdk/shared"; export class CommandSearchError extends Error { @@ -115,11 +116,15 @@ export async function update (source: string, id: string) const paths_screenshots: string[] = [...sourceGame.paths_screenshots.map(s => `${RPC_URL(host)}${s}`)]; if (paths_screenshots.length <= 0 && sourceGame.igdb_id) { - const matches: GameLookup[] = []; - await plugins.hooks.games.gameLookup.promise({ source: 'igdb', id: String(sourceGame.igdb_id), matches }); - if (matches.length > 0) + const matches = new Map(); + await plugins.hooks.games.gameLookup.promise(matches, { source: 'igdb', id: String(sourceGame.igdb_id) }); + if (matches.size > 0) { - paths_screenshots.push(...matches[0].screenshotUrls); + const firstMatches = matches.values().next().value; + if (firstMatches && firstMatches.length > 0) + { + paths_screenshots.push(...firstMatches[0].screenshotUrls); + } } } @@ -244,7 +249,31 @@ export async function getValidLaunchCommandsForGame (source: string, id: string) commands: commands.filter(c => c.valid), gameId: { id: String(localGame.id), source: 'local' }, source: localGame.source ?? source, - sourceId: String(localGame.source_id) ?? id, + sourceId: localGame.source_id ? String(localGame.source_id) : id, + }; + } + else + { + return new CommandSearchError('missing-emulator', `Missing One Of Emulators: ${Array.from(new Set(commands.filter(e => e.emulator && e.emulator !== "OS-SHELL").map(e => e.emulator))).join(', ')}`); + } + } else if (source === 'emulator') + { + const commands = await plugins.hooks.games.buildLaunchCommands.promise({ + source, + sourceId: id, + id: { source: source, id: id }, + systemSlug: "", + gamePath: null + }); + + if (commands instanceof Error || !commands) return commands; + + const validCommand = commands.find(c => c.valid); + if (validCommand) + { + return { + commands: commands.filter(c => c.valid), + gameId: { id, source } }; } else diff --git a/src/bun/api/games/services/utils.ts b/src/bun/api/games/services/utils.ts index fd4b2d9..aaac97b 100644 --- a/src/bun/api/games/services/utils.ts +++ b/src/bun/api/games/services/utils.ts @@ -8,7 +8,7 @@ import { RPC_URL } from "@shared/constants"; import { hashFile } from "@/bun/utils"; import { host } from "@/bun/utils/host"; import * as emulatorSchema from "@schema/emulators"; -import { DownloadFileEntry, FrontEndGameType, FrontEndGameTypeDetailed, GameLookup, LocalDownloadFileEntry, LocalGameMetadata } from "@/shared/types"; +import { DownloadFileEntry, FrontEndGameType, FrontEndGameTypeDetailed, GameLookup, LocalDownloadFileEntry, LocalGameMetadata } from "@simeonradivoev/gameflow-sdk/shared"; export async function calculateSize (installPath: string | null) { diff --git a/src/bun/api/jobs/bios-download-job.ts b/src/bun/api/jobs/bios-download-job.ts index be46c5f..64537c1 100644 --- a/src/bun/api/jobs/bios-download-job.ts +++ b/src/bun/api/jobs/bios-download-job.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { IJob, JobContext } from "../task-queue"; +import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; import { config, plugins } from "../app"; import { simulateProgress } from "@/bun/utils"; import { Downloader } from "@/bun/utils/downloader"; diff --git a/src/bun/api/jobs/emulator-download-job.ts b/src/bun/api/jobs/emulator-download-job.ts index 7dbaf6e..9aa35a6 100644 --- a/src/bun/api/jobs/emulator-download-job.ts +++ b/src/bun/api/jobs/emulator-download-job.ts @@ -1,6 +1,6 @@ -import { EmulatorPackageType } from "@/shared/constants"; +import { EmulatorPackageType } from '@simeonradivoev/gameflow-sdk/shared'; import { getStoreEmulatorPackage } from "../store/services/gamesService"; -import { IJob, JobContext } from "../task-queue"; +import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; import z from "zod"; import { config, plugins } from "../app"; import path from 'node:path'; @@ -12,7 +12,7 @@ import { simulateProgress } from "@/bun/utils"; import { path7za } from "7zip-bin"; import { getEmulatorDownload, getEmulatorPath } from "../store/services/emulatorsService"; import { $ } from "bun"; -import { EmulatorSourceEntryType } from "@/shared/types"; +import { EmulatorSourceEntryType } from "@simeonradivoev/gameflow-sdk/shared"; type EmulatorDownloadStates = "download" | "extract"; diff --git a/src/bun/api/jobs/import-job.ts b/src/bun/api/jobs/import-job.ts index 3e608a4..f1d25be 100644 --- a/src/bun/api/jobs/import-job.ts +++ b/src/bun/api/jobs/import-job.ts @@ -1,10 +1,10 @@ import { eq, or } from "drizzle-orm"; import { db, plugins } from "../app"; import { createLocalGame } from "../games/services/utils"; -import { IJob, JobContext } from "../task-queue"; +import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; import * as schema from "@schema/app"; import z from "zod"; -import { GameLookup } from "@/shared/types"; +import { GameLookup } from "@simeonradivoev/gameflow-sdk/shared"; export class ImportJob implements IJob, string> { diff --git a/src/bun/api/jobs/install-job.ts b/src/bun/api/jobs/install-job.ts index b6809e2..a9433d4 100644 --- a/src/bun/api/jobs/install-job.ts +++ b/src/bun/api/jobs/install-job.ts @@ -1,4 +1,4 @@ -import { IJob, JobContext } from "../task-queue"; +import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; import fs from 'node:fs/promises'; import path from 'node:path'; import { config, events, plugins } from "../app"; @@ -11,7 +11,7 @@ import { ensureDir, move } from "fs-extra"; import { path7za } from "7zip-bin"; import StreamZip from 'node-stream-zip'; import { which } from "bun"; -import { DownloadInfo } from "@/shared/types"; +import { DownloadInfo } from "@simeonradivoev/gameflow-sdk/shared"; interface JobConfig { diff --git a/src/bun/api/jobs/jobs.ts b/src/bun/api/jobs/jobs.ts index 328c04e..f75605c 100644 --- a/src/bun/api/jobs/jobs.ts +++ b/src/bun/api/jobs/jobs.ts @@ -6,7 +6,7 @@ import TwitchLoginJob from "./twitch-login-job"; import UpdateStoreJob from "./update-store"; import { EmulatorDownloadJob } from "./emulator-download-job"; import { getErrorMessage } from "@/bun/utils"; -import { IJob } from "../task-queue"; +import { IJob } from "../../../packages/gameflow-sdk/task-queue"; import { LaunchGameJob } from "./launch-game-job"; import { BiosDownloadJob } from "./bios-download-job"; import { InstallJob } from "./install-job"; diff --git a/src/bun/api/jobs/launch-game-job.ts b/src/bun/api/jobs/launch-game-job.ts index d81c25c..f5072e9 100644 --- a/src/bun/api/jobs/launch-game-job.ts +++ b/src/bun/api/jobs/launch-game-job.ts @@ -1,13 +1,13 @@ import z from "zod"; -import { IJob, JobContext } from "../task-queue"; -import { ActiveGameSchema, ActiveGameType } from "@/bun/types/types.schema"; +import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; +import { ActiveGameSchema, ActiveGameType } from "@simeonradivoev/gameflow-sdk"; import { config, db, events, plugins } from "../app"; import * as appSchema from "@schema/app"; import { eq } from "drizzle-orm"; import { spawn } from 'node:child_process'; import { updateLocalLastPlayed } from "../games/services/statusService"; import { getErrorMessage } from "@/bun/utils"; -import { CommandEntry, FrontEndId, SaveSlots } from "@/shared/types"; +import { CommandEntry, FrontEndId, SaveSlots } from "@simeonradivoev/gameflow-sdk/shared"; export class LaunchGameJob implements IJob, string> { diff --git a/src/bun/api/jobs/login-job.ts b/src/bun/api/jobs/login-job.ts index f0726bd..dd112ad 100644 --- a/src/bun/api/jobs/login-job.ts +++ b/src/bun/api/jobs/login-job.ts @@ -1,5 +1,5 @@ import Elysia, { status } from "elysia"; -import { IJob, JobContext } from "../task-queue"; +import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; import { LOGIN_PORT, SERVER_URL } from "@/shared/constants"; import { host, localIp } from "@/bun/utils/host"; import cors from "@elysiajs/cors"; diff --git a/src/bun/api/jobs/plugin-operation-job.ts b/src/bun/api/jobs/plugin-operation-job.ts new file mode 100644 index 0000000..db39819 --- /dev/null +++ b/src/bun/api/jobs/plugin-operation-job.ts @@ -0,0 +1,62 @@ +import z from "zod"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk"; +import { plugins } from "../app"; +import { canUninstall, runBunPackageCommand } from "../plugins/services"; +import { getPlugin, registerPlugin, unregisterPlugin } from "../plugins/register-plugins"; +import { PluginRegistry } from "@/shared/constants"; + +export default class PluginOperationJob implements IJob +{ + static id = "plugin-operation-job" as const; + static dataSchema = z.never(); + group = "plugin-operations"; + operation: "add" | "update" | "remove"; + plugin: string; + + constructor(operation: "add" | "update" | "remove", plugin: string) + { + this.plugin = plugin; + this.operation = operation; + } + + async start (context: JobContext, never, string>) + { + switch (this.operation) + { + case "add": + //TODO: find the latest compatible version with the current sdk version + const addResponse = await runBunPackageCommand(["add", this.plugin, '--omit', 'peer', "--registry", PluginRegistry]); + console.log(addResponse); + const addPlugin = await getPlugin(this.plugin, plugins); + if (!addPlugin) throw new Error(`${this.plugin} Not Found`); + await registerPlugin(addPlugin, 'store', plugins); + break; + case "update": + const existingPlugin = plugins.plugins[this.plugin]; + if (!existingPlugin) throw new Error(`${this.plugin} Not Found`); + if (!existingPlugin.update?.new) throw new Error(`No Update Found`); + let updatePlugin = await getPlugin(this.plugin, plugins); + if (!updatePlugin) throw new Error(`${this.plugin} Not Found`); + await unregisterPlugin(this.plugin, plugins); + const updateResponse = await runBunPackageCommand(["update", `${this.plugin}@${existingPlugin.update?.new}`, '--omit', 'peer', "--registry", PluginRegistry, '--latest']); + console.log(updateResponse); + updatePlugin = await getPlugin(this.plugin, plugins); + if (!updatePlugin) throw new Error(`Something Went Wrong during update. Missing Plugin: ${this.plugin}`); + await registerPlugin(updatePlugin, existingPlugin.source, plugins); + break; + case "remove": + const removePlugin = plugins.plugins[this.plugin]; + if (!removePlugin) throw new Error(`${this.plugin} Not Found`); + if (!canUninstall(removePlugin.description, removePlugin.source)) + { + throw new Error("Uninstall Not Allowed"); + } + const response = await runBunPackageCommand(['remove', this.plugin, "--registry", PluginRegistry, '--omit', 'peer']); + console.log(response); + await unregisterPlugin(this.plugin, plugins); + break; + } + + + } +} \ No newline at end of file diff --git a/src/bun/api/jobs/reload-plugins-job.ts b/src/bun/api/jobs/reload-plugins-job.ts index 4796fc8..5e404d3 100644 --- a/src/bun/api/jobs/reload-plugins-job.ts +++ b/src/bun/api/jobs/reload-plugins-job.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { IJob, JobContext } from "../task-queue"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk"; import { plugins } from "../app"; export default class ReloadPluginsJob implements IJob diff --git a/src/bun/api/jobs/self-update-job.ts b/src/bun/api/jobs/self-update-job.ts index 05ac4e6..ca2684e 100644 --- a/src/bun/api/jobs/self-update-job.ts +++ b/src/bun/api/jobs/self-update-job.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { IJob, JobContext } from "../task-queue"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk"; import { events } from "../app"; import { Downloader } from "@/bun/utils/downloader"; import path from 'node:path'; diff --git a/src/bun/api/jobs/twitch-login-job.ts b/src/bun/api/jobs/twitch-login-job.ts index 1023e83..42d98a9 100644 --- a/src/bun/api/jobs/twitch-login-job.ts +++ b/src/bun/api/jobs/twitch-login-job.ts @@ -1,4 +1,4 @@ -import { IJob, JobContext } from "../task-queue"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk"; import secrets from "../secrets"; import open from "open"; import z from "zod"; diff --git a/src/bun/api/jobs/update-store.ts b/src/bun/api/jobs/update-store.ts index 48129a0..697fb3a 100644 --- a/src/bun/api/jobs/update-store.ts +++ b/src/bun/api/jobs/update-store.ts @@ -1,59 +1,57 @@ import { ensureDir } from "fs-extra"; -import { IJob, JobContext } from "../task-queue"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk"; import { getStoreRootFolder } from "../store/services/gamesService"; -import { tmpdir } from "node:os"; -import path from "node:path"; import z from "zod"; +import { runBunPackageCommand } from "../plugins/services"; +import { PluginRegistry } from "@/shared/constants"; +import path from "node:path"; +import sdkPkg from '@simeonradivoev/gameflow-sdk/package.json'; -export default class UpdateStoreJob implements IJob +export default class UpdateStoreJob implements IJob { static id = "update-store" as const; static dataSchema = z.never(); packageName: string; - registry: URL; storeVersion: string; constructor() { this.packageName = process.env.STORE_PACKAGE_NAME ?? "@simeonradivoev/gameflow-store"; - this.registry = new URL(process.env.STORE_REGISTRY ?? "https://registry.npmjs.org"); this.storeVersion = process.env.STORE_VERSION ?? "^0.1.0"; } - async runCommand (commands: string[]) + async start (context: JobContext) { - const tempCache = path.join(tmpdir(), "gameflow-bun-cache"); - const storeFolder = getStoreRootFolder(); - - let proc = Bun.spawn([process.execPath, ...commands, "--registry", this.registry.href, '--json'], { - cwd: storeFolder, - stdout: 'pipe', - stderr: 'pipe', - env: { - BUN_BE_BUN: "1", - BUN_INSTALL_CACHE_DIR: tempCache - } - }); - - let stdout = await new Response(proc.stdout).text(); - console.log(stdout); - let stderr = await new Response(proc.stderr).text(); - if (stderr) - console.error(stderr); - await proc.exited; - } - - async start (context: JobContext) - { - if (process.env.CUSTOM_STORE_PATH) return; - const storeFolder = getStoreRootFolder(); await ensureDir(storeFolder); + const storePackageFile = Bun.file(path.join(storeFolder, "package.json")); + if (!await storePackageFile.exists()) + { + await storePackageFile.write(JSON.stringify({ dependencies: {} }, null, 3)); + } - console.log("Adding Store Package"); - await this.runCommand(["add", `${this.packageName}@${this.storeVersion}`]); + const storePackage = await Bun.file(path.join(storeFolder, "package.json")).json(); - console.log("Updating Store Package"); - await this.runCommand(["update", `${this.packageName}@${this.storeVersion}`]); + if (!storePackage.dependencies?.[sdkPkg.name] || storePackage.dependencies?.[sdkPkg.name] !== sdkPkg.version) + { + let response = await runBunPackageCommand(["add", `${sdkPkg.name}@${sdkPkg.version}`, "--registry", PluginRegistry, '--omit', 'peer']); + console.log(response); + } + + // probably just means we couldn't find a version of the sdk, just install latest + if (storePackage.dependencies?.[sdkPkg.name] !== sdkPkg.version) + { + let response = await runBunPackageCommand(["add", '--exact', `${sdkPkg.name}@latest`, "--registry", PluginRegistry, '--omit', 'peer']); + console.log(response); + } + + if (process.env.CUSTOM_STORE_PATH) return; + + if (!storePackage.dependencies?.['@simeonradivoev/gameflow-store']) + { + context.setProgress(0.5, "Adding Store"); + let response = await runBunPackageCommand(["add", `${this.packageName}@${this.storeVersion}`, "--registry", PluginRegistry, '--omit', 'peer']); + console.log(response); + } } } \ No newline at end of file diff --git a/src/bun/api/notifications.ts b/src/bun/api/notifications.ts index 514ee58..e1c135c 100644 --- a/src/bun/api/notifications.ts +++ b/src/bun/api/notifications.ts @@ -1,5 +1,5 @@ -import { FrontendNotification } from '@/shared/types'; +import { FrontendNotification } from '@simeonradivoev/gameflow-sdk/shared'; import { events } from './app'; export default function buildNotificationsStream () diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.cemu/cemu.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.cemu/cemu.ts index 9fe34a4..a9e6865 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.cemu/cemu.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.cemu/cemu.ts @@ -1,4 +1,4 @@ -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import desc from './package.json'; import path from 'node:path'; import { config } from "@/bun/api/app"; diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin.ts index 111b9c5..6a44901 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin.ts @@ -1,6 +1,6 @@ import { config } from "@/bun/api/app"; -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import path from 'node:path'; import desc from './package.json'; import { ensureDir } from "fs-extra"; diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2.ts index 23c2736..58d61aa 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2.ts @@ -1,12 +1,12 @@ import { config } from "@/bun/api/app"; -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import defaultConfig from './PCSX2.ini' with { type: 'file' }; import path from 'node:path'; import { ensureDir } from "fs-extra"; import desc from './package.json'; import ini from 'ini'; -import { EmulatorCapabilities } from "@/shared/types"; +import { EmulatorCapabilities } from "@simeonradivoev/gameflow-sdk/shared"; export default class PCSX2Integration implements PluginType { diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp.ts index 87886dd..f69fdaf 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp.ts @@ -1,4 +1,4 @@ -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import desc from './package.json'; import { config } from "@/bun/api/app"; import configFilePathWin32 from './win32/ppsspp.ini' with { type: 'file' }; @@ -11,7 +11,7 @@ import { ensureDir } from "fs-extra"; import { homedir } from "node:os"; import ini from 'ini'; import fs from 'node:fs/promises'; -import { EmulatorCapabilities } from "@/shared/types"; +import { EmulatorCapabilities } from "@simeonradivoev/gameflow-sdk/shared"; export default class PPSSPPIntegration implements PluginType { diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xemu/xemu.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xemu/xemu.ts index ff8c3f9..57506de 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xemu/xemu.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xemu/xemu.ts @@ -1,4 +1,4 @@ -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import desc from './package.json'; import { config } from "@/bun/api/app"; import path from "node:path"; diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/xenia.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/xenia.ts index eb0715d..9d37d99 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/xenia.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/xenia.ts @@ -1,6 +1,6 @@ -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import desc from './package.json'; -import GameflowHooks from "@/bun/api/hooks/app"; +import { GameflowHooks } from "@simeonradivoev/gameflow-sdk"; import { config } from "@/bun/api/app"; import path from "node:path"; import { ensureDir } from "fs-extra"; diff --git a/src/bun/api/plugins/builtin/launchers/com.simeonradivoev.gameflow.es/es-de.ts b/src/bun/api/plugins/builtin/launchers/com.simeonradivoev.gameflow.es/es-de.ts index cf57e13..77cd201 100644 --- a/src/bun/api/plugins/builtin/launchers/com.simeonradivoev.gameflow.es/es-de.ts +++ b/src/bun/api/plugins/builtin/launchers/com.simeonradivoev.gameflow.es/es-de.ts @@ -1,4 +1,4 @@ -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import desc from './package.json'; import { config, customEmulators, db, emulatorsDb } from "@/bun/api/app"; import * as emulatorSchema from '@schema/emulators'; @@ -13,7 +13,7 @@ import { findStoreEmulatorExec } from "@/bun/api/games/services/launchGameServic import { which } from "bun"; import os from 'node:os'; import { getLocalGameMatch } from "@/bun/api/games/services/utils"; -import { CommandEntry, EmulatorSourceEntryType } from "@/shared/types"; +import { CommandEntry, EmulatorSourceEntryType } from "@simeonradivoev/gameflow-sdk/shared"; export default class IgdbIntegration implements PluginType { diff --git a/src/bun/api/plugins/builtin/other/com.simeonradivoev.gameflow.rclone/rclone.ts b/src/bun/api/plugins/builtin/other/com.simeonradivoev.gameflow.rclone/rclone.ts index a31c94d..8ab31a0 100644 --- a/src/bun/api/plugins/builtin/other/com.simeonradivoev.gameflow.rclone/rclone.ts +++ b/src/bun/api/plugins/builtin/other/com.simeonradivoev.gameflow.rclone/rclone.ts @@ -1,4 +1,4 @@ -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import desc from './package.json'; import { config, db, events } from "@/bun/api/app"; import path from 'node:path'; diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.igdb/igdb.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.igdb/igdb.ts index 7c39e01..c2be6d3 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.igdb/igdb.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.igdb/igdb.ts @@ -1,10 +1,10 @@ -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import desc from './package.json'; import secrets from "@/bun/api/secrets"; import PQueue from 'p-queue'; import * as igdb from '@phalcode/ts-igdb-client'; import { checkLoginAndRefreshTwitch } from "@/bun/api/auth"; -import { GameLookup } from "@/shared/types"; +import { GameLookup } from "@simeonradivoev/gameflow-sdk/shared"; export default class IgdbIntegration implements PluginType { diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts index ac9474f..93c6fbe 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts @@ -1,6 +1,6 @@ -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import desc from './package.json'; import { DetailedRomSchema, getCollectionApiCollectionsIdGet, getCollectionsApiCollectionsGet, getCurrentUserApiUsersMeGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformsApiPlatformsGet, getRomApiRomsIdGet, getRomByMetadataProviderApiRomsByMetadataProviderGet, getRomContentApiRomsIdContentFileNameGet, getRomFiltersApiRomsFiltersGet, getRomsApiRomsGet, getSavesSummaryApiSavesSummaryGet, PlatformSchema, SimpleRomSchema, updateRomUserApiRomsIdPropsPut } from "@/clients/romm"; import { config, events } from "@/bun/api/app"; @@ -14,7 +14,7 @@ import { client } from "@/clients/romm/client.gen"; import { validateGameSource } from "@/bun/api/games/services/statusService"; import z from "zod"; import { checkLoginAndRefreshRomm } from "@/bun/api/auth"; -import { DownloadFileEntry, DownloadInfo, FrontEndCollection, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeDetailedAchievement, FrontEndGameTypeWithIds, FrontEndPlatformType } from "@/shared/types"; +import { DownloadFileEntry, DownloadInfo, FrontEndCollection, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeDetailedAchievement, FrontEndGameTypeWithIds, FrontEndPlatformType } from "@simeonradivoev/gameflow-sdk/shared"; import Conf from "conf"; const SettingsSchema = z.object({ diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/package.json b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/package.json index 713f76f..644c332 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/package.json +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/package.json @@ -1,8 +1,8 @@ { "name": "com.simeonradivoev.gameflow.store", - "displayName": "Gameflow Store", + "displayName": "Gameflow Store Integration", "version": "0.0.1", - "description": "The internal gameflow store", + "description": "The internal gameflow store integration. This is the logic of the store that uses the data only store package", "main": "./store.ts", "category": "sources", "canDisable": false, diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts index de08c46..17c5f12 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts @@ -1,5 +1,4 @@ import { getStoreFolder } from "@/bun/api/store/services/gamesService"; -import { EmulatorDownloadInfoSchema, EmulatorDownloadInfoType, EmulatorPackageType, StoreDownloadType, StoreGameSchema, StoreGameType } from "@/shared/constants"; import os from 'node:os'; import path from "node:path"; import * as appSchema from '@schema/app'; @@ -12,7 +11,7 @@ import { shuffleInPlace } from "@/bun/utils"; import mustache from "mustache"; import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService"; import fs from "node:fs/promises"; -import { CommandEntry, EmulatorSourceEntryType, EmulatorSystem, FrontEndEmulator, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailed, SaveFileChange } from "@/shared/types"; +import { CommandEntry, EmulatorSourceEntryType, EmulatorSystem, FrontEndEmulator, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailed, SaveFileChange, EmulatorDownloadInfoType, StoreDownloadType, StoreGameType, EmulatorPackageType, EmulatorDownloadInfoSchema, StoreGameSchema } from "@simeonradivoev/gameflow-sdk/shared"; export async function getStoreGames (gamesManifest: any[], filter?: { limit?: number; offset?: number; }) { diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts index 3215548..9b514a2 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts @@ -1,4 +1,4 @@ -import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema"; +import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; import desc from './package.json'; import path, { } from 'node:path'; import { buildStoreFrontendEmulatorSystems, getAllStoreEmulatorPackages, getStoreEmulatorPackage, getStoreFolder } from "@/bun/api/store/services/gamesService"; @@ -12,7 +12,7 @@ import { getSourceGameDetailed } from "@/bun/api/games/services/utils"; import UpdateStoreJob from "@/bun/api/jobs/update-store"; import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService"; import { buildFilters, buildLaunchCommand, buildSaves, convertStoreEmulatorToFrontend, convertStoreToFrontend, convertStoreToFrontendDetailed, getExistingStoreEmulatorDownload, getShuffledStoreGames, getStoreGame, getValidDownloads } from "./services"; -import { DownloadInfo, FrontEndEmulatorDetailed, FrontEndGameTypeWithIds } from "@/shared/types"; +import { DownloadInfo, FrontEndEmulatorDetailed, FrontEndGameTypeWithIds } from "@simeonradivoev/gameflow-sdk/shared"; export default class RommIntegration implements PluginType { @@ -151,7 +151,8 @@ export default class RommIntegration implements PluginType if (!validDownload || !validDownload.bin) return; const glob = new Glob(validDownload.bin); const files = await Array.fromAsync(glob.scan({ cwd: emulatorPath })); - if (files.length > 0) + // es-de also searches for store executables so there might be duplicates, check first. + if (files.length > 0 && !sources.find(s => s.type === 'store')) { sources.push({ binPath: path.join(emulatorPath, files[0]), exists: true, rootPath: emulatorPath, type: 'store' }); } diff --git a/src/bun/api/plugins/plugin-manager.ts b/src/bun/api/plugins/plugin-manager.ts index e3511b5..1fab907 100644 --- a/src/bun/api/plugins/plugin-manager.ts +++ b/src/bun/api/plugins/plugin-manager.ts @@ -1,10 +1,13 @@ -import GameflowHooks from "../hooks/app"; -import { PluginDescriptionType, PluginLoadingContextType, PluginType } from "../../types/types.schema"; -import { config } from "../app"; +import { GameflowHooks } from "@simeonradivoev/gameflow-sdk"; +import { PluginDescriptionType, PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-sdk"; +import { config, events, taskQueue } from "../app"; import Conf from "conf"; import projectPackage from '~/package.json'; import z from "zod"; -import { PluginSourceType } from "@/shared/types"; +import { PluginSourceType, PluginUpdateCheck } from "@simeonradivoev/gameflow-sdk/shared"; +import { getUpdates } from "./services"; +import sdkPkg from '@simeonradivoev/gameflow-sdk/package.json'; +import { semver } from "bun"; export const pluginZodRegistry = z.registry<{ requiresRestart?: boolean; @@ -21,9 +24,19 @@ export class PluginManager description: PluginDescriptionType, source: PluginSourceType; config?: Conf; + update?: PluginUpdateCheck; + incompatible?: boolean; }> = {}; + unregister (id: string) + { + if (!this.plugins[id]) return false; + delete this.plugins[id]; + console.log("Plugin", id, "unregistered"); + return true; + } + register (plugin: PluginType, description: PluginDescriptionType, source: PluginSourceType) { try @@ -68,16 +81,33 @@ export class PluginManager }; } - private async reload (name: string, reloadCtx: { setProgress: (progress: number, state: string) => void; }) + checkValidity (plugin: PluginDescriptionType) + { + const sdkDep = plugin.peerDependencies?.[sdkPkg.name]; + if (sdkDep) + { + return semver.satisfies(sdkPkg.version, sdkDep); + } + return true; + } + + private async reload (name: string, reloadCtx: { setProgress: (progress: number, state: string) => void; }, update: string | undefined) { const plugin = this.plugins[name]; if (plugin) { + plugin.update = update && !semver.satisfies(plugin.description.version, update) ? { current: plugin.description.version, new: update } : undefined; + const ctx: PluginLoadingContextType = { hooks: this.hooks, setProgress: reloadCtx.setProgress.bind(reloadCtx), config: plugin.config as any, - zodRegistry: pluginZodRegistry + zodRegistry: pluginZodRegistry, + app: { + config, + events, + taskQueue + } }; if (plugin.loaded) @@ -88,7 +118,14 @@ export class PluginManager try { - if (plugin.enabled || plugin.description.canDisable === false) + plugin.incompatible = !this.checkValidity(plugin.description); + if (plugin.incompatible) + { + console.error(plugin.description.name, "Incompatible sdk verison"); + return; + } + + if (plugin.enabled || plugin.description.canDisable === false || plugin.description.name === '@simeonradivoev/gameflow-store') { console.log("Loading Plugin", plugin.description.name); await plugin.plugin.load(ctx); @@ -106,10 +143,13 @@ export class PluginManager async reloadAll (ctx: { setProgress: (progress: number, state: string) => void; }) { this.hooks = new GameflowHooks(); + + const outdated = await getUpdates(); + for await (const id of Object.keys(this.plugins)) { ctx.setProgress(0, `Loading ${id}`); - await this.reload(id, ctx); + await this.reload(id, ctx, outdated?.[id]); } } diff --git a/src/bun/api/plugins/plugins.ts b/src/bun/api/plugins/plugins.ts index eed9466..ddfad06 100644 --- a/src/bun/api/plugins/plugins.ts +++ b/src/bun/api/plugins/plugins.ts @@ -3,7 +3,9 @@ import { plugins, taskQueue } from "../app"; import z from "zod"; import { toggleElementInConfig } from "@/bun/utils"; import ReloadPluginsJob from "../jobs/reload-plugins-job"; -import { FrontendPlugin } from "@/shared/types"; +import { FrontendPlugin } from "@simeonradivoev/gameflow-sdk/shared"; +import { canDisable, canUninstall } from "./services"; +import PluginOperationJob from "../jobs/plugin-operation-job"; export default new Elysia({ prefix: '/plugins' }) .get('/', async () => @@ -17,25 +19,27 @@ export default new Elysia({ prefix: '/plugins' }) description: p.description.description, source: p.source, version: p.description.version, - canDisable: p.description.canDisable ?? true, + canDisable: canDisable(p.description), icon: p.description.icon, category: p.description.category, - hasSettings: !!p.config || !!p.plugin.eventsNames + hasSettings: !!p.config || !!p.plugin.eventsNames, + canUninstall: canUninstall(p.description, p.source), + update: p.update }; return plugin; }); }) .get('/:id', async ({ params: { id } }) => { - const plugin = plugins.plugins[id]; - return plugin.description; + const plugin = plugins.plugins[decodeURIComponent(id)]; + return { ...plugin.description, update: plugin.update }; }) .post('/:id', async ({ params: { id }, body: { enabled } }) => { - const plugin = plugins.plugins[id]; + const plugin = plugins.plugins[decodeURIComponent(id)]; if (plugin) { - if (plugin.description.canDisable === false) + if (!canDisable(plugin.description)) { return status("Forbidden"); } @@ -48,4 +52,26 @@ export default new Elysia({ prefix: '/plugins' }) } }, { body: z.object({ enabled: z.boolean() }) + }).post('/install', async ({ body: { id } }) => + { + if (taskQueue.hasActiveOfType(PluginOperationJob) || taskQueue.hasActiveOfType(ReloadPluginsJob)) return; + await taskQueue.enqueue(PluginOperationJob.id, new PluginOperationJob("add", id)); + await taskQueue.enqueue(ReloadPluginsJob.id, new ReloadPluginsJob()); + }, { + body: z.object({ id: z.string() }) + }).post('/update', async ({ body: { id } }) => + { + if (taskQueue.hasActiveOfType(PluginOperationJob) || taskQueue.hasActiveOfType(ReloadPluginsJob)) return; + await taskQueue.enqueue(PluginOperationJob.id, new PluginOperationJob("update", id)); + await taskQueue.enqueue(ReloadPluginsJob.id, new ReloadPluginsJob()); + }, { + body: z.object({ id: z.string() }) + }) + .post('/uninstall', async ({ body: { id } }) => + { + if (taskQueue.hasActiveOfType(PluginOperationJob) || taskQueue.hasActiveOfType(ReloadPluginsJob)) return; + await taskQueue.enqueue(PluginOperationJob.id, new PluginOperationJob("remove", id)); + await taskQueue.enqueue(ReloadPluginsJob.id, new ReloadPluginsJob()); + }, { + body: z.object({ id: z.string() }) }); \ No newline at end of file diff --git a/src/bun/api/plugins/register-plugins.ts b/src/bun/api/plugins/register-plugins.ts index ead6f54..1275740 100644 --- a/src/bun/api/plugins/register-plugins.ts +++ b/src/bun/api/plugins/register-plugins.ts @@ -11,12 +11,78 @@ import igdb from './builtin/sources/com.simeonradivoev.gameflow.igdb/package.jso import store from './builtin/sources/com.simeonradivoev.gameflow.store/package.json'; import es from './builtin/launchers/com.simeonradivoev.gameflow.es/package.json'; import rclone from './builtin/other/com.simeonradivoev.gameflow.rclone/package.json'; -import { PluginDescriptionSchema, PluginDescriptionType, PluginSchema } from "@/bun/types/types.schema"; +import { PluginDescriptionSchema, PluginDescriptionType, PluginSchema } from "@simeonradivoev/gameflow-sdk"; import path from 'node:path'; import { getStoreRootFolder } from "../store/services/gamesService"; +import { getUpdates } from "./services"; +import { PluginSourceType } from "@simeonradivoev/gameflow-sdk/shared"; +import { taskQueue } from "../app"; +import UpdateStoreJob from "../jobs/update-store"; type PluginEntry = PluginDescriptionType & { load: () => Promise; }; +const blacklist = new Set(['@simeonradivoev/gameflow-sdk']); + +export async function getPlugin (id: string, pluginManager: PluginManager) +{ + const pluginPath = path.join(getStoreRootFolder(), 'node_modules', id); + const pluginPackageFile = Bun.file(path.join(pluginPath, 'package.json')); + if (await pluginPackageFile.exists()) + { + const pluginPackage = await PluginDescriptionSchema.safeParseAsync(await pluginPackageFile.json()); + if (pluginPackage.success) + { + const mainPath = path.join(pluginPath, pluginPackage.data.main); + if (await Bun.file(mainPath).exists()) + { + const entry: PluginEntry = { ...pluginPackage.data, load: () => import(mainPath) }; + return entry; + } else + { + console.error("Main file for", id, "does not exist"); + } + } else + { + console.error("Invalid Package for", id, pluginPackage.error.message); + } + } else + { + console.error("Package for", id, "does not exist"); + } +} + +export async function unregisterPlugin (id: string, pluginManager: PluginManager) +{ + return pluginManager.unregister(id); +} + +export async function registerPlugin (plugin: PluginEntry, source: PluginSourceType, pluginManager: PluginManager) +{ + if (process.env.PLUGIN_WHITELIST && !process.env.PLUGIN_WHITELIST.includes(plugin.name)) + { + console.log("Skipping", plugin.name, "missing in whitelist"); + return; + } + + if (process.env.PLUGIN_BLACKLIST && process.env.PLUGIN_BLACKLIST.includes(plugin.name)) + { + console.log("Skipping", plugin.name, "found in whitelist"); + return; + } + + const file = await plugin.load(); + if (file.default && typeof file.default === 'function') + { + const pluginInstance = new file.default(); + await PluginSchema.parseAsync(pluginInstance); + const description = await PluginDescriptionSchema.parseAsync(plugin); + pluginManager.register(pluginInstance, description, source); + } else + { + console.log("Skipping", plugin.name, "invalid main. Has to be class with load method"); + } +} + export default async function register (pluginManager: PluginManager) { const plugins: PluginEntry[] = [ @@ -33,53 +99,41 @@ export default async function register (pluginManager: PluginManager) { ...rclone, load: () => import('./builtin/other/com.simeonradivoev.gameflow.rclone/rclone') }, ]; - const storePackageFile = path.join(getStoreRootFolder(), 'package.json'); - const storePackage = await Bun.file(storePackageFile).json(); + await Promise.all(plugins.map(p => registerPlugin(p, 'builtin', pluginManager))); - if (storePackage.dependencies) + const storePackageFilePath = path.join(getStoreRootFolder(), 'package.json'); + if (!await Bun.file(storePackageFilePath).exists()) { - const storePlugins = await Promise.all(Object.keys(storePackage.dependencies).map(async p => + console.log("Store is missing. Updating it."); + await taskQueue.enqueue(UpdateStoreJob.id, new UpdateStoreJob()); + console.log("Store Updated"); + } + const storePackage = await Bun.file(storePackageFilePath).json(); + + if (storePackage?.dependencies) + { + const storePlugins = await Promise.all(Object.keys(storePackage.dependencies).filter(p => !blacklist.has(p)).map(async p => { - const pluginPath = path.join(getStoreRootFolder(), 'node_modules', p); - const pluginPackageFile = Bun.file(path.join(pluginPath, 'package.json')); - if (await pluginPackageFile.exists()) - { - const pluginPackage = await PluginDescriptionSchema.safeParseAsync(await pluginPackageFile.json()); - if (pluginPackage.success) - { - const mainPath = path.join(pluginPath, pluginPackage.data.main); - if (await Bun.file(mainPath).exists()) - { - const entry: PluginEntry = { ...pluginPackage.data, load: () => import(mainPath) }; - return entry; - } - } - } + return getPlugin(p, pluginManager); })); - plugins.push(...storePlugins.filter(p => !!p)); - } + console.log("Checking for outdated packages"); + const outdated = await getUpdates(); - await Promise.all(plugins.filter(p => - { - if (process.env.PLUGIN_WHITELIST && !process.env.PLUGIN_WHITELIST.includes(p.name)) + const validPlugins = storePlugins.filter(p => !!p); + + if (outdated) { - return false; + validPlugins.forEach(p => + { + const newVersion = outdated[p.name]; + if (newVersion) + { + console.log("Plugin", p.name, "has update", p.version, "=>", newVersion); + } + }); } - if (process.env.PLUGIN_BLACKLIST && process.env.PLUGIN_BLACKLIST.includes(p.name)) - { - return false; - } - return true; - }).map(async (pluginPackage) => - { - const file = await pluginPackage.load(); - if (file.default && typeof file.default === 'function') - { - const pluginInstance = new file.default(); - await PluginSchema.parseAsync(pluginInstance); - const description = await PluginDescriptionSchema.parseAsync(pluginPackage); - pluginManager.register(pluginInstance, description, 'builtin'); - } - })); + + await Promise.all(validPlugins.map(p => registerPlugin(p, 'store', pluginManager))); + } } \ No newline at end of file diff --git a/src/bun/api/plugins/services.ts b/src/bun/api/plugins/services.ts new file mode 100644 index 0000000..9452b7e --- /dev/null +++ b/src/bun/api/plugins/services.ts @@ -0,0 +1,62 @@ +import path from 'node:path'; +import os from 'node:os'; +import { getStoreRootFolder } from '../store/services/gamesService'; +import { PluginDescriptionType } from '@simeonradivoev/gameflow-sdk'; +import { run } from 'npm-check-updates'; + +export function canDisable (description: PluginDescriptionType) +{ + if (description.name === '@simeonradivoev/gameflow-store') + { + return false; + } + return description.canDisable ?? true; +} + +export async function getUpdates () +{ + const updated = await run({ packageManager: 'bun', peer: true, cwd: getStoreRootFolder(), jsonUpgraded: true, reject: ['@simeonradivoev/gameflow-sdk'] }); + return updated as Record; +} + +export function canUninstall (description: PluginDescriptionType, source: string) +{ + if (description.name === '@simeonradivoev/gameflow-store') + { + return false; + } + return source !== 'builtin'; +} + +export async function runBunPackageCommand (commands: string[]) +{ + const tempCache = path.join(os.tmpdir(), "gameflow-bun-cache"); + const storeFolder = getStoreRootFolder(); + + let proc = Bun.spawn([process.execPath, ...commands, '--json'], { + cwd: storeFolder, + stdout: 'pipe', + stderr: 'pipe', + env: { + BUN_BE_BUN: "1", + BUN_INSTALL_CACHE_DIR: tempCache + } + }); + + let stdout = await new Response(proc.stdout).text(); + let stderr = await new Response(proc.stderr).text(); + if (stderr) + console.error(stderr); + await proc.exited; + return stdout; +} + +export async function hasPackage (id: string) +{ + const storeFolder = getStoreRootFolder(); + const packagePath = path.join(storeFolder, 'package.json'); + const packageFile = Bun.file(packagePath); + if (!await packageFile.exists()) return false; + const pkg = await packageFile.json(); + return !!pkg.dependencies?.[id]; +} \ No newline at end of file diff --git a/src/bun/api/schema/app.ts b/src/bun/api/schema/app.ts index a30c4fb..7db68ba 100644 --- a/src/bun/api/schema/app.ts +++ b/src/bun/api/schema/app.ts @@ -1,4 +1,5 @@ -import { LocalGameMetadata } from "@/shared/types"; + +import { LocalGameMetadata } from "@simeonradivoev/gameflow-sdk/shared"; import { sql, relations } from "drizzle-orm"; import { integer, text, sqliteTable, blob } from "drizzle-orm/sqlite-core"; diff --git a/src/bun/api/settings/services.ts b/src/bun/api/settings/services.ts index a560de6..e0897ea 100644 --- a/src/bun/api/settings/services.ts +++ b/src/bun/api/settings/services.ts @@ -7,7 +7,7 @@ import { cores } from '../emulatorjs/emulatorjs'; import { SERVER_URL } from '@/shared/constants'; import { host } from '@/bun/utils/host'; import { findEmulatorPluginIntegration } from '../store/services/emulatorsService'; -import { EmulatorSourceEntryType, FrontEndEmulator } from '@/shared/types'; +import { EmulatorSourceEntryType, FrontEndEmulator } from '@simeonradivoev/gameflow-sdk/shared'; /** * Get emulators based on local games. Only the ones we probably need. diff --git a/src/bun/api/settings/settings.ts b/src/bun/api/settings/settings.ts index c315701..e4e2da1 100644 --- a/src/bun/api/settings/settings.ts +++ b/src/bun/api/settings/settings.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { SettingsSchema } from "@shared/constants"; +import { SettingsSchema } from '@simeonradivoev/gameflow-sdk/shared'; import Elysia, { status } from "elysia"; import { config, customEmulators, plugins, taskQueue } from "../app"; import fs from 'node:fs/promises'; @@ -96,27 +96,27 @@ export const settings = new Elysia({ prefix: '/api/settings' }) }) .get('/definitions/:source', async ({ params: { source } }) => { - return plugins.plugins[source].plugin.settingsSchema?.toJSONSchema() as JSONSchema7; + return plugins.plugins[decodeURIComponent(source)].plugin.settingsSchema?.toJSONSchema() as JSONSchema7; }) .get('/actions/:source', async ({ params: { source } }) => { - const plugin = plugins.plugins[source]?.plugin; + const plugin = plugins.plugins[decodeURIComponent(source)]?.plugin; if (!plugin.eventsNames) return []; return plugin.eventsNames; }) .post('/actions/:source/:id', async ({ params: { source, id } }) => { - return await plugins.plugins[source]?.plugin.onEvent?.(id); + return await plugins.plugins[decodeURIComponent(source)]?.plugin.onEvent?.(decodeURIComponent(id)); }) .get('/:source/:id', async ({ params: { source, id } }) => { - return { value: plugins.plugins[source].config?.get(id) }; + return { value: plugins.plugins[decodeURIComponent(source)].config?.get(decodeURIComponent(id)) }; }) .put('/:source/:id', async ({ params: { source, id }, body: { value } }) => { - const plugin = plugins.plugins[source]; + const plugin = plugins.plugins[decodeURIComponent(source)]; if (!plugin.config) return status("Not Found", "Plugin has no config"); - const settingSchema = plugin.plugin.settingsSchema?.shape[id] as z.ZodObject; + const settingSchema = plugin.plugin.settingsSchema?.shape[decodeURIComponent(id)] as z.ZodObject; if (!settingSchema) return status("Not Found", "Could not find setting"); const meta = pluginZodRegistry.get(settingSchema); diff --git a/src/bun/api/store/services/emulatorsService.ts b/src/bun/api/store/services/emulatorsService.ts index c61ed00..6dfcde1 100644 --- a/src/bun/api/store/services/emulatorsService.ts +++ b/src/bun/api/store/services/emulatorsService.ts @@ -1,8 +1,7 @@ -import { EmulatorDownloadInfoType, EmulatorPackageType, ScoopPackageSchema } from "@/shared/constants"; import { config, plugins } from "../../app"; import { getOrCached, getOrCachedGithubRelease } from "../../cache"; import path from "node:path"; -import { EmulatorSourceEntryType, EmulatorSupport } from "@/shared/types"; +import { EmulatorSourceEntryType, EmulatorSupport, ScoopPackageSchema, EmulatorPackageType, EmulatorDownloadInfoType } from "@simeonradivoev/gameflow-sdk/shared"; export function findEmulatorPluginIntegration (name: string, validSources: (EmulatorSourceEntryType | undefined)[]): EmulatorSupport[] { diff --git a/src/bun/api/store/services/gamesService.ts b/src/bun/api/store/services/gamesService.ts index f2149ff..b475b89 100644 --- a/src/bun/api/store/services/gamesService.ts +++ b/src/bun/api/store/services/gamesService.ts @@ -1,10 +1,9 @@ -import { EmulatorPackageSchema, EmulatorPackageType } from "@/shared/constants"; import { and, eq, or } from "drizzle-orm"; import { config, emulatorsDb } from '../../app'; import path from "node:path"; import fs from 'node:fs/promises'; import * as emulatorSchema from '@schema/emulators'; -import { EmulatorSystem } from "@/shared/types"; +import { EmulatorSystem, EmulatorPackageType, EmulatorPackageSchema } from "@simeonradivoev/gameflow-sdk/shared"; export function getStoreRootFolder () { diff --git a/src/bun/api/store/store.ts b/src/bun/api/store/store.ts index 85463b3..39d5630 100644 --- a/src/bun/api/store/store.ts +++ b/src/bun/api/store/store.ts @@ -3,7 +3,6 @@ import Elysia, { status } from "elysia"; import { config, db, plugins, taskQueue } from "../app"; import path from "node:path"; import fs from 'node:fs/promises'; -import { EmulatorDownloadInfoSchema } from "@/shared/constants"; import * as appSchema from '@schema/app'; import z from "zod"; import { convertLocalToFrontendDetailed, getLocalGameMatch } from "../games/services/utils"; @@ -13,7 +12,17 @@ import { getStoreFolder } from "./services/gamesService"; import { EmulatorDownloadJob } from "../jobs/emulator-download-job"; import { BiosDownloadJob } from "../jobs/bios-download-job"; import { findEmulatorPluginIntegration, getEmulatorPath } from "./services/emulatorsService"; -import { EmulatorSourceEntryType, FrontEndEmulator, FrontEndGameTypeDetailed } from "@/shared/types"; +import { EmulatorSourceEntryType, FrontEndEmulator, FrontEndGameTypeDetailed, PluginBunDetailsSchema, PluginEntrySchema, EmulatorDownloadInfoSchema } from "@simeonradivoev/gameflow-sdk/shared"; +import PQueue from "p-queue"; +import { hasPackage, runBunPackageCommand } from "../plugins/services"; +import { semver } from "bun"; + +const npmQueue = new PQueue({ intervalCap: 60, interval: 1000 * 60, strict: true }); +const pluginsResponseSchema = z.object({ + objects: z.array(PluginEntrySchema), + total: z.number(), + time: z.coerce.date() +}); export const store = new Elysia({ prefix: '/api/store' }) .get('/emulators', async ({ query }) => @@ -109,6 +118,49 @@ export const store = new Elysia({ prefix: '/api/store' }) gameCount }; }) + .get('/plugin', async ({ query: { plugin } }) => + { + const pluginsRes = await runBunPackageCommand(['info', plugin]); + const pluginData = await PluginBunDetailsSchema.parseAsync(JSON.parse(pluginsRes)); + const existingVersion = plugins.plugins[plugin]?.description.version; + + return { + ...pluginData, + installed: !!plugins.plugins[plugin] || await hasPackage(plugin), + update: existingVersion && semver.order(pluginData.version, existingVersion) > 0 ? { from: existingVersion } : undefined + }; + }, + { + query: z.object({ plugin: z.string() }) + }) + .get('/plugins', async ({ query: { search } }) => + { + //TODO: Find a better way to search keywords and a search term at the same time + const pluginsRes = await npmQueue.add(() => fetch(`https://registry.npmjs.com/-/v1/search?text=keywords:gameflow-plugin`)); + if (!pluginsRes.ok) return status(pluginsRes.status, pluginsRes.statusText); + const data: z.infer = await pluginsRes.json(); + if (search) + { + data.objects = data.objects.filter(o => + { + if (o.package.description && o.package.description.includes(search)) return true; + if (o.package.name.includes(search)) return true; + if (o.package.keywords.includes(search)) return true; + return false; + }); + data.total = data.objects.length; + } + await Promise.all(data.objects.map(async o => + { + const existingVersion = plugins.plugins[o.package.name]?.description.version; + o.installed = !!plugins.plugins[o.package.name] || await hasPackage(o.package.name); + o.update = existingVersion && semver.order(o.package.version, existingVersion) > 0 ? { from: existingVersion } : undefined; + })); + return data as any; + }, { + query: z.object({ search: z.string().optional() }), + response: pluginsResponseSchema + }) .get('/media/*', async ({ params }) => { return Bun.file(path.join(getStoreFolder(), params["*"])); diff --git a/src/bun/api/system.ts b/src/bun/api/system.ts index 66e7742..5408a6d 100644 --- a/src/bun/api/system.ts +++ b/src/bun/api/system.ts @@ -7,7 +7,7 @@ import { getAppVersion, isSteamDeck, openExternal } from "../utils"; import fs from 'node:fs/promises'; import buildNotificationsStream from "./notifications"; import path, { dirname } from "node:path"; -import { DirSchema, SystemInfoSchema } from "@/shared/constants"; +import { SystemInfoSchema, DirSchema, DownloadsDrive } from '@simeonradivoev/gameflow-sdk/shared'; import { getDevices, getDevicesCurated } from "./drives"; import getFolderSize from "get-folder-size"; import si from 'systeminformation'; @@ -16,7 +16,6 @@ import ReloadPluginsJob from "./jobs/reload-plugins-job"; import { semver } from "bun"; import { getOrCachedGithubRelease } from "./cache"; import SelfUpdateJob from "./jobs/self-update-job"; -import { DownloadsDrive } from "@/shared/types"; async function checkUpdate (force?: boolean) { diff --git a/src/bun/types/types.ts b/src/bun/types/types.ts deleted file mode 100644 index 6802ff9..0000000 --- a/src/bun/types/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { EmulatorDownloadInfoType, EmulatorPackageType } from "@/shared/constants"; -import { FrontendNotification } from "@/shared/types"; - -export interface AppEventMap -{ - exitapp: []; - notification: [FrontendNotification]; - focus: []; -} - -export interface EmulatorPostInstallContext -{ - emulator: string; - emulatorPackage?: EmulatorPackageType; - path: string; - update: boolean; - info: EmulatorDownloadInfoType; -} \ No newline at end of file diff --git a/src/bun/utils.ts b/src/bun/utils.ts index f03a42c..fe44ad2 100644 --- a/src/bun/utils.ts +++ b/src/bun/utils.ts @@ -1,10 +1,9 @@ import { $, sleep } from 'bun'; import path from 'node:path'; -import { SettingsType } from '@/shared/constants'; +import { SettingsType, KeysWithValueAssignableTo } from '@simeonradivoev/gameflow-sdk/shared'; import { config } from './api/app'; import fs from 'node:fs/promises'; import packageDef from '~/package.json'; -import { KeysWithValueAssignableTo } from '@/shared/types'; export function checkRunning (pid: number) { diff --git a/src/bun/utils/downloader.ts b/src/bun/utils/downloader.ts index 000542a..f0f30ca 100644 --- a/src/bun/utils/downloader.ts +++ b/src/bun/utils/downloader.ts @@ -5,7 +5,7 @@ import fs from 'node:fs/promises'; import { createWriteStream } from "node:fs"; import { config, jar } from "../api/app"; import { moveAllFiles } from "../utils"; -import { DownloadFileEntry } from "@/shared/types"; +import { DownloadFileEntry } from "@simeonradivoev/gameflow-sdk/shared"; export interface ProgressStats { diff --git a/src/mainview/components/AppCommunication.tsx b/src/mainview/components/AppCommunication.tsx index bbb26c3..3dec17a 100644 --- a/src/mainview/components/AppCommunication.tsx +++ b/src/mainview/components/AppCommunication.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from "react"; import { SystemInfoContext } from "../scripts/contexts"; import { systemApi } from "../scripts/clientApi"; -import { SystemInfoType } from "@/shared/constants"; +import { SystemInfoType } from '@simeonradivoev/gameflow-sdk/shared'; import LoadingScreen from "./LoadingScreen"; import { GamepadKeyboard } from "./GamepadKeyboard"; diff --git a/src/mainview/components/CollectionsDetail.tsx b/src/mainview/components/CollectionsDetail.tsx index 9d6632c..72c391a 100644 --- a/src/mainview/components/CollectionsDetail.tsx +++ b/src/mainview/components/CollectionsDetail.tsx @@ -5,7 +5,7 @@ import { JSX, Suspense } from 'react'; import { FloatingShortcuts } from './Shortcuts'; import { AutoFocus } from './AutoFocus'; import { GamePadButtonCode, useShortcuts } from '../scripts/shortcuts'; -import { GameListFilterType } from '@/shared/constants'; +import { GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; import { HandleGoBack } from '../scripts/utils'; import LoadingCardList from './LoadingCardList'; import { useQuery, useQueryClient } from '@tanstack/react-query'; diff --git a/src/mainview/components/FilePicker.tsx b/src/mainview/components/FilePicker.tsx index 67c1a8b..aefa842 100644 --- a/src/mainview/components/FilePicker.tsx +++ b/src/mainview/components/FilePicker.tsx @@ -4,7 +4,7 @@ import { FocusEventHandler, useContext, useRef, useState } from "react"; import path from "pathe"; import { Check, File, FileInput, Folder, FolderInput, FolderOutput, FolderPlus, HardDrive, Usb, X } from "lucide-react"; import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation"; -import { DirType } from "@/shared/constants"; +import { DirType } from '@simeonradivoev/gameflow-sdk/shared'; import classNames from "classnames"; import { twMerge } from "tailwind-merge"; import { GamePadButtonCode, Shortcut, useShortcuts } from "../scripts/shortcuts"; diff --git a/src/mainview/components/FrontEndGameCard.tsx b/src/mainview/components/FrontEndGameCard.tsx index c6b8e12..093be25 100644 --- a/src/mainview/components/FrontEndGameCard.tsx +++ b/src/mainview/components/FrontEndGameCard.tsx @@ -4,7 +4,7 @@ import { FileQuestion, HardDrive, Store } from "lucide-react"; import { JSX } from "react"; import { FOCUS_KEYS } from "../scripts/types"; import { useRouter } from "@tanstack/react-router"; -import { FrontEndGameType, FrontEndId } from "@/shared/types"; +import { FrontEndGameType, FrontEndId } from "@simeonradivoev/gameflow-sdk/shared"; export default function FrontEndGameCard (data: { index: number, game: FrontEndGameType; showSource?: boolean; } & FocusParams & InteractParams) { diff --git a/src/mainview/components/GameList.tsx b/src/mainview/components/GameList.tsx index 67e689e..80c4944 100644 --- a/src/mainview/components/GameList.tsx +++ b/src/mainview/components/GameList.tsx @@ -1,13 +1,14 @@ import { useSuspenseQuery } from "@tanstack/react-query"; import { GameMetaExtra, CardList } from "./CardList"; -import { DefaultRommStaleTime, GameListFilterType, RPC_URL } from "@shared/constants"; +import { DefaultRommStaleTime, RPC_URL } from "@shared/constants"; +import { GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; import { useNavigate } from "@tanstack/react-router"; import { HardDrive } from "lucide-react"; import { JSX, useContext } from "react"; import { useLocalSetting } from "../scripts/utils"; import { AnimatedBackgroundContext } from "../scripts/contexts"; import { allGamesQuery } from "@queries/romm"; -import { FrontEndGameType, FrontEndId } from "@/shared/types"; +import { FrontEndGameType, FrontEndId } from "@simeonradivoev/gameflow-sdk/shared"; export interface GameListParams extends FocusParams { diff --git a/src/mainview/components/GamepadKeyboard.tsx b/src/mainview/components/GamepadKeyboard.tsx index 7f3b994..37e533b 100644 --- a/src/mainview/components/GamepadKeyboard.tsx +++ b/src/mainview/components/GamepadKeyboard.tsx @@ -60,7 +60,7 @@ function buildWheel (side: 0 | 1, shift: boolean, characters: boolean) const elements: JSX.Element[] = []; const refs: RefObject[] = []; const positions: { left: string; top: string; }[] = []; - const W = 258, C = 129, R2 = 107, R1 = 42, n = GetKeys(characters)[side].length, GAP = 0.028; + const n = GetKeys(characters)[side].length, GAP = 0.028; for (let i = 0; i < n; i++) { diff --git a/src/mainview/components/HeaderSearchField.tsx b/src/mainview/components/HeaderSearchField.tsx index 36d0eb0..823af58 100644 --- a/src/mainview/components/HeaderSearchField.tsx +++ b/src/mainview/components/HeaderSearchField.tsx @@ -5,7 +5,6 @@ import { oneShot } from "../scripts/audio/audio"; import { Search } from "lucide-react"; import { RoundButton } from "./RoundButton"; import { useEventListener } from "usehooks-ts"; -import useActiveControl from "../scripts/gamepads"; import { twMerge } from "tailwind-merge"; function SearchInput (data: { diff --git a/src/mainview/components/LoadMoreButton.tsx b/src/mainview/components/LoadMoreButton.tsx index d042049..d52e4e0 100644 --- a/src/mainview/components/LoadMoreButton.tsx +++ b/src/mainview/components/LoadMoreButton.tsx @@ -1,6 +1,7 @@ import { setFocus, useFocusable } from "@noriginmedia/norigin-spatial-navigation"; import { FOCUS_KEYS } from "../scripts/types"; import { useIntersectionObserver } from "usehooks-ts"; +import { FrontEndId } from "@simeonradivoev/gameflow-sdk/shared"; export default function LoadMoreButton (data: { isFetching: boolean; hidden?: boolean, lastId?: FrontEndId; } & FocusParams & InteractParams) { diff --git a/src/mainview/components/Notifications.tsx b/src/mainview/components/Notifications.tsx index 4fbe03d..c13c4b6 100644 --- a/src/mainview/components/Notifications.tsx +++ b/src/mainview/components/Notifications.tsx @@ -1,5 +1,5 @@ import { RPC_URL } from "@/shared/constants"; -import { FrontendNotification } from "@/shared/types"; +import { FrontendNotification } from "@simeonradivoev/gameflow-sdk/shared"; import { Clock, CloudUpload, Save } from "lucide-react"; import { useEffect } from "react"; import toast, { ToastOptions } from "react-hot-toast"; diff --git a/src/mainview/components/SideFilters.tsx b/src/mainview/components/SideFilters.tsx index 180030d..6f99336 100644 --- a/src/mainview/components/SideFilters.tsx +++ b/src/mainview/components/SideFilters.tsx @@ -1,4 +1,4 @@ -import { GameListFilterType } from "@/shared/constants"; +import { GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; import { RoundButton } from "./RoundButton"; import classNames from "classnames"; import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts"; @@ -6,7 +6,7 @@ import { useFocusable, FocusContext } from "@noriginmedia/norigin-spatial-naviga import { ArrowDownAz, ClockArrowDown, CalendarArrowDown, Rocket, HardDrive, SortDesc, User, Drama, FunnelX, Store } from "lucide-react"; import { sourceIconMap } from "./Constants"; import { useContextDialog, ContextList, DialogEntry } from "./ContextDialog"; -import { FrontEndFilterLists } from "@/shared/types"; +import { FrontEndFilterLists } from "@simeonradivoev/gameflow-sdk/shared"; function FilterButton (data: { id: string, diff --git a/src/mainview/components/game/Achievements.tsx b/src/mainview/components/game/Achievements.tsx index e9445cb..9fbe814 100644 --- a/src/mainview/components/game/Achievements.tsx +++ b/src/mainview/components/game/Achievements.tsx @@ -1,5 +1,5 @@ -import { FrontEndGameTypeDetailed, FrontEndGameTypeDetailedAchievement } from "@/shared/types"; +import { FrontEndGameTypeDetailed, FrontEndGameTypeDetailedAchievement } from "@simeonradivoev/gameflow-sdk/shared"; import { useFocusable } from "@noriginmedia/norigin-spatial-navigation"; import { Medal } from "lucide-react"; diff --git a/src/mainview/components/game/ActionButtons.tsx b/src/mainview/components/game/ActionButtons.tsx index 02db473..1a60c93 100644 --- a/src/mainview/components/game/ActionButtons.tsx +++ b/src/mainview/components/game/ActionButtons.tsx @@ -10,7 +10,7 @@ import ActionButton from "./ActionButton"; import { useLocalStorage } from "usehooks-ts"; import FocusTooltip from "../FocusTooltip"; import { useBlocker, useNavigate, useRouter } from "@tanstack/react-router"; -import { FrontEndGameTypeDetailed } from "@/shared/types"; +import { FrontEndGameTypeDetailed } from "@simeonradivoev/gameflow-sdk/shared"; function AchievementsInfo (data: { game: FrontEndGameTypeDetailed; } & InteractParams) { diff --git a/src/mainview/components/game/Details.tsx b/src/mainview/components/game/Details.tsx index c0ac4ea..99a7054 100644 --- a/src/mainview/components/game/Details.tsx +++ b/src/mainview/components/game/Details.tsx @@ -10,7 +10,7 @@ import prettyMilliseconds from 'pretty-ms'; import { useQuery } from "@tanstack/react-query"; import { validateSourceQuery } from "@/mainview/scripts/queries/romm"; import { sourceIconMap } from "../Constants"; -import { FrontEndGameTypeDetailed } from "@/shared/types"; +import { FrontEndGameTypeDetailed } from "@simeonradivoev/gameflow-sdk/shared"; export function DetailElement (data: { icon: JSX.Element; tooltip?: string | null, children?: any | any[]; }) { diff --git a/src/mainview/components/game/GameLookup.tsx b/src/mainview/components/game/GameLookup.tsx index 3b15009..bac4928 100644 --- a/src/mainview/components/game/GameLookup.tsx +++ b/src/mainview/components/game/GameLookup.tsx @@ -6,7 +6,7 @@ import HeaderSearchField from "../HeaderSearchField"; import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts"; import { scrollIntoViewHandler } from "@/mainview/scripts/utils"; import { FOCUS_KEYS } from "@/mainview/scripts/types"; -import { FrontEndId, GameLookup } from "@/shared/types"; +import { FrontEndId, GameLookup } from "@simeonradivoev/gameflow-sdk/shared"; import { gameLookupQuery } from "@/mainview/scripts/queries/romm"; import { Button } from "../options/Button"; import { useNavigate } from "@tanstack/react-router"; diff --git a/src/mainview/components/game/MainActions.tsx b/src/mainview/components/game/MainActions.tsx index a2caabc..20bb27b 100644 --- a/src/mainview/components/game/MainActions.tsx +++ b/src/mainview/components/game/MainActions.tsx @@ -9,9 +9,8 @@ import { Clock, Crosshair, Download, EllipsisVertical, Import, PackageOpen, Play import { gameInvalidationQuery, installMutation, playMutation } from "@/mainview/scripts/queries/romm"; import ActionButton from "./ActionButton"; import { useRouter } from "@tanstack/react-router"; -import { DownloadSourceType } from "@/shared/constants"; import { GamePadButtonCode, Shortcut, useShortcuts } from "@/mainview/scripts/shortcuts"; -import { CommandEntry, FrontEndGameTypeDetailed } from "@/shared/types"; +import { CommandEntry, FrontEndGameTypeDetailed, DownloadSourceType } from "@simeonradivoev/gameflow-sdk/shared"; export default function MainActions (data: { game?: FrontEndGameTypeDetailed, source: string, id: string; }) { diff --git a/src/mainview/components/options/DownloadDirectoryOption.tsx b/src/mainview/components/options/DownloadDirectoryOption.tsx index 9cbe29f..de902d3 100644 --- a/src/mainview/components/options/DownloadDirectoryOption.tsx +++ b/src/mainview/components/options/DownloadDirectoryOption.tsx @@ -2,8 +2,7 @@ import { useState } from "react"; import { PathSettingsOptionBase, PathSettingsOptionParams } from "./PathSettingsOption"; import { useMutation, useQuery } from "@tanstack/react-query"; import { changeDownloadsMutation, getSettingQuery } from "@queries/settings"; -import { SettingsType } from "@/shared/constants"; -import { KeysWithValueAssignableTo } from "@/shared/types"; +import { KeysWithValueAssignableTo, SettingsType } from "@simeonradivoev/gameflow-sdk/shared"; export default function DownloadDirectoryOption (data: PathSettingsOptionParams & { id: KeysWithValueAssignableTo; }) { diff --git a/src/mainview/components/options/LocalOption.tsx b/src/mainview/components/options/LocalOption.tsx index d596123..25ac7b6 100644 --- a/src/mainview/components/options/LocalOption.tsx +++ b/src/mainview/components/options/LocalOption.tsx @@ -1,5 +1,5 @@ import { JSX } from "react"; -import { LocalSettingsSchema, LocalSettingsType } from "@shared/constants"; +import { LocalSettingsSchema, LocalSettingsType } from '@simeonradivoev/gameflow-sdk/shared'; import { OptionSpace } from "./OptionSpace"; import { OptionInput } from "./OptionInput"; import { useLocalStorage } from "usehooks-ts"; diff --git a/src/mainview/components/options/PathSettingsOption.tsx b/src/mainview/components/options/PathSettingsOption.tsx index 2c25fb2..7b2789f 100644 --- a/src/mainview/components/options/PathSettingsOption.tsx +++ b/src/mainview/components/options/PathSettingsOption.tsx @@ -1,5 +1,4 @@ import { HTMLInputTypeAttribute, JSX, useEffect, useState } from "react"; -import { SettingsType } from "../../../shared/constants"; import { useMutation, useQuery } from "@tanstack/react-query"; import { OptionSpace } from "./OptionSpace"; import { OptionInput } from "./OptionInput"; @@ -9,7 +8,7 @@ import { ContextDialog } from "../ContextDialog"; import FilePicker from "../FilePicker"; import { setFocus } from "@noriginmedia/norigin-spatial-navigation"; import { getSettingQuery, setSettingMutation } from "@queries/settings"; -import { KeysWithValueAssignableTo } from "@/shared/types"; +import { KeysWithValueAssignableTo, SettingsType } from "@simeonradivoev/gameflow-sdk/shared"; export interface PathSettingsOptionParams { diff --git a/src/mainview/components/options/SettingsDropdown.tsx b/src/mainview/components/options/SettingsDropdown.tsx index 18eabd5..6887b52 100644 --- a/src/mainview/components/options/SettingsDropdown.tsx +++ b/src/mainview/components/options/SettingsDropdown.tsx @@ -1,10 +1,9 @@ import { JSX, useCallback, useEffect, useState } from "react"; -import { SettingsType } from "../../../shared/constants"; import { useMutation, useQuery } from "@tanstack/react-query"; import { OptionSpace } from "./OptionSpace"; import { getSettingQuery, setSettingMutation } from "@queries/settings"; import { OptionDropdown } from "./OptionDropdown"; -import { KeysWithValueAssignableTo } from "@/shared/types"; +import { KeysWithValueAssignableTo, SettingsType } from "@simeonradivoev/gameflow-sdk/shared"; export function SettingsDropdown (data: { label: string; diff --git a/src/mainview/components/options/SettingsOption.tsx b/src/mainview/components/options/SettingsOption.tsx index 55357d7..20bcda0 100644 --- a/src/mainview/components/options/SettingsOption.tsx +++ b/src/mainview/components/options/SettingsOption.tsx @@ -1,10 +1,9 @@ import { HTMLInputTypeAttribute, JSX, useCallback, useEffect, useState } from "react"; -import { SettingsType } from "../../../shared/constants"; import { useMutation, useQuery } from "@tanstack/react-query"; import { OptionSpace } from "./OptionSpace"; import { OptionInput } from "./OptionInput"; import { getSettingQuery, setSettingMutation } from "@queries/settings"; -import { KeysWithValueAssignableTo } from "@/shared/types"; +import { KeysWithValueAssignableTo, SettingsType } from "@simeonradivoev/gameflow-sdk/shared"; export function SettingsOption (data: { label: string; diff --git a/src/mainview/components/store/EmulatorsSection.tsx b/src/mainview/components/store/EmulatorsSection.tsx index eec1325..a7712dc 100644 --- a/src/mainview/components/store/EmulatorsSection.tsx +++ b/src/mainview/components/store/EmulatorsSection.tsx @@ -12,7 +12,7 @@ import { StoreEmulatorCard } from "./StoreEmulatorCard"; import { FOCUS_KEYS } from "@/mainview/scripts/types"; import Carousel from "../Carousel"; import { useRouter } from "@tanstack/react-router"; -import { FrontEndEmulator } from "@/shared/types"; +import { FrontEndEmulator } from "@simeonradivoev/gameflow-sdk/shared"; function SeeAllCard (data: { id: string; onAction: () => void; onFocus?: (details: { node: HTMLElement, instant?: boolean; }) => void; }) { diff --git a/src/mainview/components/store/GamesSection.tsx b/src/mainview/components/store/GamesSection.tsx index ff3cbbb..ccdd076 100644 --- a/src/mainview/components/store/GamesSection.tsx +++ b/src/mainview/components/store/GamesSection.tsx @@ -10,7 +10,7 @@ import FrontEndGameCard from "../FrontEndGameCard"; import { FOCUS_KEYS } from "@/mainview/scripts/types"; import Carousel from "../Carousel"; import { twMerge } from "tailwind-merge"; -import { FrontEndGameType, FrontEndId } from "@/shared/types"; +import { FrontEndGameType, FrontEndId } from "@simeonradivoev/gameflow-sdk/shared"; export function GamesSection (data: { games?: FrontEndGameType[]; diff --git a/src/mainview/components/store/MissingEmulatorsSection.tsx b/src/mainview/components/store/MissingEmulatorsSection.tsx index 84f150b..6339dde 100644 --- a/src/mainview/components/store/MissingEmulatorsSection.tsx +++ b/src/mainview/components/store/MissingEmulatorsSection.tsx @@ -8,7 +8,7 @@ import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts"; import { RPC_URL } from "@/shared/constants"; import { FOCUS_KEYS } from "@/mainview/scripts/types"; import { oneShot } from "@/mainview/scripts/audio/audio"; -import { FrontEndEmulator } from "@/shared/types"; +import { FrontEndEmulator } from "@simeonradivoev/gameflow-sdk/shared"; // ── Single missing-emulator card ─────────────────────────────────────────── interface MissingCardProps diff --git a/src/mainview/components/store/StoreEmulatorCard.tsx b/src/mainview/components/store/StoreEmulatorCard.tsx index 09b3ca3..8645a01 100644 --- a/src/mainview/components/store/StoreEmulatorCard.tsx +++ b/src/mainview/components/store/StoreEmulatorCard.tsx @@ -10,7 +10,7 @@ import { JSX } from "react"; import { oneShot } from "@/mainview/scripts/audio/audio"; import { useQuery } from "@tanstack/react-query"; import { getUpdateInfoForEmulator } from "@/mainview/scripts/queries/store"; -import { FrontEndEmulator } from "@/shared/types"; +import { FrontEndEmulator } from "@simeonradivoev/gameflow-sdk/shared"; export const emulatorStatusIcons: Record = { store: , diff --git a/src/mainview/gen/routeTree.gen.ts b/src/mainview/gen/routeTree.gen.ts index ad674ce..62fef18 100644 --- a/src/mainview/gen/routeTree.gen.ts +++ b/src/mainview/gen/routeTree.gen.ts @@ -22,6 +22,7 @@ import { Route as SettingsAboutRouteImport } from './../routes/settings/about' import { Route as GameAddRouteImport } from './../routes/game/add' import { Route as StoreTabRouteRouteImport } from './../routes/store/tab/route' import { Route as StoreTabIndexRouteImport } from './../routes/store/tab/index' +import { Route as StoreTabPluginsRouteImport } from './../routes/store/tab/plugins' import { Route as StoreTabGamesRouteImport } from './../routes/store/tab/games' import { Route as StoreTabEmulatorsRouteImport } from './../routes/store/tab/emulators' import { Route as SettingsPluginSourceRouteImport } from './../routes/settings/plugin.$source' @@ -30,6 +31,7 @@ import { Route as LauncherSourceIdRouteImport } from './../routes/launcher.$sour import { Route as GameSourceIdRouteImport } from './../routes/game/$source.$id' import { Route as EmbeddedSourceIdRouteImport } from './../routes/embedded.$source.$id' import { Route as CollectionSourceIdRouteImport } from './../routes/collection.$source.$id' +import { Route as StoreDetailsPluginIdRouteImport } from './../routes/store/details.plugin.$id' import { Route as StoreDetailsEmulatorIdRouteImport } from './../routes/store/details.emulator.$id' import { Route as GameUpdateSourceIdRouteImport } from './../routes/game/update.$source.$id' @@ -98,6 +100,11 @@ const StoreTabIndexRoute = StoreTabIndexRouteImport.update({ path: '/', getParentRoute: () => StoreTabRouteRoute, } as any) +const StoreTabPluginsRoute = StoreTabPluginsRouteImport.update({ + id: '/plugins', + path: '/plugins', + getParentRoute: () => StoreTabRouteRoute, +} as any) const StoreTabGamesRoute = StoreTabGamesRouteImport.update({ id: '/games', path: '/games', @@ -138,6 +145,11 @@ const CollectionSourceIdRoute = CollectionSourceIdRouteImport.update({ path: '/collection/$source/$id', getParentRoute: () => rootRouteImport, } as any) +const StoreDetailsPluginIdRoute = StoreDetailsPluginIdRouteImport.update({ + id: '/store/details/plugin/$id', + path: '/store/details/plugin/$id', + getParentRoute: () => rootRouteImport, +} as any) const StoreDetailsEmulatorIdRoute = StoreDetailsEmulatorIdRouteImport.update({ id: '/store/details/emulator/$id', path: '/store/details/emulator/$id', @@ -170,9 +182,11 @@ export interface FileRoutesByFullPath { '/settings/plugin/$source': typeof SettingsPluginSourceRoute '/store/tab/emulators': typeof StoreTabEmulatorsRoute '/store/tab/games': typeof StoreTabGamesRoute + '/store/tab/plugins': typeof StoreTabPluginsRoute '/store/tab/': typeof StoreTabIndexRoute '/game/update/$source/$id': typeof GameUpdateSourceIdRoute '/store/details/emulator/$id': typeof StoreDetailsEmulatorIdRoute + '/store/details/plugin/$id': typeof StoreDetailsPluginIdRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -194,9 +208,11 @@ export interface FileRoutesByTo { '/settings/plugin/$source': typeof SettingsPluginSourceRoute '/store/tab/emulators': typeof StoreTabEmulatorsRoute '/store/tab/games': typeof StoreTabGamesRoute + '/store/tab/plugins': typeof StoreTabPluginsRoute '/store/tab': typeof StoreTabIndexRoute '/game/update/$source/$id': typeof GameUpdateSourceIdRoute '/store/details/emulator/$id': typeof StoreDetailsEmulatorIdRoute + '/store/details/plugin/$id': typeof StoreDetailsPluginIdRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -220,9 +236,11 @@ export interface FileRoutesById { '/settings/plugin/$source': typeof SettingsPluginSourceRoute '/store/tab/emulators': typeof StoreTabEmulatorsRoute '/store/tab/games': typeof StoreTabGamesRoute + '/store/tab/plugins': typeof StoreTabPluginsRoute '/store/tab/': typeof StoreTabIndexRoute '/game/update/$source/$id': typeof GameUpdateSourceIdRoute '/store/details/emulator/$id': typeof StoreDetailsEmulatorIdRoute + '/store/details/plugin/$id': typeof StoreDetailsPluginIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -247,9 +265,11 @@ export interface FileRouteTypes { | '/settings/plugin/$source' | '/store/tab/emulators' | '/store/tab/games' + | '/store/tab/plugins' | '/store/tab/' | '/game/update/$source/$id' | '/store/details/emulator/$id' + | '/store/details/plugin/$id' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -271,9 +291,11 @@ export interface FileRouteTypes { | '/settings/plugin/$source' | '/store/tab/emulators' | '/store/tab/games' + | '/store/tab/plugins' | '/store/tab' | '/game/update/$source/$id' | '/store/details/emulator/$id' + | '/store/details/plugin/$id' id: | '__root__' | '/' @@ -296,9 +318,11 @@ export interface FileRouteTypes { | '/settings/plugin/$source' | '/store/tab/emulators' | '/store/tab/games' + | '/store/tab/plugins' | '/store/tab/' | '/game/update/$source/$id' | '/store/details/emulator/$id' + | '/store/details/plugin/$id' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -314,6 +338,7 @@ export interface RootRouteChildren { PlatformSourceIdRoute: typeof PlatformSourceIdRoute GameUpdateSourceIdRoute: typeof GameUpdateSourceIdRoute StoreDetailsEmulatorIdRoute: typeof StoreDetailsEmulatorIdRoute + StoreDetailsPluginIdRoute: typeof StoreDetailsPluginIdRoute } declare module '@tanstack/react-router' { @@ -409,6 +434,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof StoreTabIndexRouteImport parentRoute: typeof StoreTabRouteRoute } + '/store/tab/plugins': { + id: '/store/tab/plugins' + path: '/plugins' + fullPath: '/store/tab/plugins' + preLoaderRoute: typeof StoreTabPluginsRouteImport + parentRoute: typeof StoreTabRouteRoute + } '/store/tab/games': { id: '/store/tab/games' path: '/games' @@ -465,6 +497,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CollectionSourceIdRouteImport parentRoute: typeof rootRouteImport } + '/store/details/plugin/$id': { + id: '/store/details/plugin/$id' + path: '/store/details/plugin/$id' + fullPath: '/store/details/plugin/$id' + preLoaderRoute: typeof StoreDetailsPluginIdRouteImport + parentRoute: typeof rootRouteImport + } '/store/details/emulator/$id': { id: '/store/details/emulator/$id' path: '/store/details/emulator/$id' @@ -511,12 +550,14 @@ const SettingsRouteRouteWithChildren = SettingsRouteRoute._addFileChildren( interface StoreTabRouteRouteChildren { StoreTabEmulatorsRoute: typeof StoreTabEmulatorsRoute StoreTabGamesRoute: typeof StoreTabGamesRoute + StoreTabPluginsRoute: typeof StoreTabPluginsRoute StoreTabIndexRoute: typeof StoreTabIndexRoute } const StoreTabRouteRouteChildren: StoreTabRouteRouteChildren = { StoreTabEmulatorsRoute: StoreTabEmulatorsRoute, StoreTabGamesRoute: StoreTabGamesRoute, + StoreTabPluginsRoute: StoreTabPluginsRoute, StoreTabIndexRoute: StoreTabIndexRoute, } @@ -537,6 +578,7 @@ const rootRouteChildren: RootRouteChildren = { PlatformSourceIdRoute: PlatformSourceIdRoute, GameUpdateSourceIdRoute: GameUpdateSourceIdRoute, StoreDetailsEmulatorIdRoute: StoreDetailsEmulatorIdRoute, + StoreDetailsPluginIdRoute: StoreDetailsPluginIdRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/src/mainview/query-options.ts b/src/mainview/query-options.ts index a52c649..879d632 100644 --- a/src/mainview/query-options.ts +++ b/src/mainview/query-options.ts @@ -1,6 +1,7 @@ import { keepPreviousData, queryOptions } from "@tanstack/react-query"; import { getRomApiRomsIdGetOptions, getRomsApiRomsGetOptions } from "../clients/romm/@tanstack/react-query.gen"; -import { DefaultRommStaleTime, GameListFilterType } from "../shared/constants"; +import { DefaultRommStaleTime } from "../shared/constants"; +import { GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; export function gamesQueryOptions (filter?: GameListFilterType) { diff --git a/src/mainview/routes/collection.$source.$id.tsx b/src/mainview/routes/collection.$source.$id.tsx index 3b73d25..a08c164 100644 --- a/src/mainview/routes/collection.$source.$id.tsx +++ b/src/mainview/routes/collection.$source.$id.tsx @@ -6,7 +6,7 @@ import { AnimatedBackgroundContext } from '../scripts/contexts'; import { getCollectionQuery } from '@queries/romm'; import { zodValidator } from '@tanstack/zod-adapter'; import z from 'zod'; -import { GameListFilterType } from '@/shared/constants'; +import { GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; import { useLocalStorage } from 'usehooks-ts'; export const Route = createFileRoute('/collection/$source/$id')({ diff --git a/src/mainview/routes/game/$source.$id.tsx b/src/mainview/routes/game/$source.$id.tsx index 064deee..761e9ea 100644 --- a/src/mainview/routes/game/$source.$id.tsx +++ b/src/mainview/routes/game/$source.$id.tsx @@ -24,7 +24,7 @@ import Details from "@/mainview/components/game/Details"; import { AutoFocus } from "@/mainview/components/AutoFocus"; import SelectMenu from "@/mainview/components/SelectMenu"; import { IGDBIcon } from "@/mainview/scripts/brandIcons"; -import { FrontEndGameTypeDetailed } from "@/shared/types"; +import { FrontEndGameTypeDetailed } from "@simeonradivoev/gameflow-sdk/shared"; export const Route = createFileRoute("/game/$source/$id")({ loader: async ({ params, context }) => diff --git a/src/mainview/routes/game/update.$source.$id.tsx b/src/mainview/routes/game/update.$source.$id.tsx index 867112a..0a6ef83 100644 --- a/src/mainview/routes/game/update.$source.$id.tsx +++ b/src/mainview/routes/game/update.$source.$id.tsx @@ -1,15 +1,15 @@ import { AnimatedBackground } from '@/mainview/components/AnimatedBackground'; import { AutoFocus } from '@/mainview/components/AutoFocus'; import GameLookupElement from '@/mainview/components/game/GameLookup'; -import { HeaderUI, StickyHeaderUI } from '@/mainview/components/Header'; +import { HeaderUI } from '@/mainview/components/Header'; import { FloatingShortcuts } from '@/mainview/components/Shortcuts'; import { customUpdateMutation, gameInvalidationQuery, gameQuery } from '@/mainview/scripts/queries/romm'; import { GamePadButtonCode, useShortcuts } from '@/mainview/scripts/shortcuts'; import { HandleGoBack } from '@/mainview/scripts/utils'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; import { useMutation, useQuery } from '@tanstack/react-query'; -import { createFileRoute, useNavigate, useRouter } from '@tanstack/react-router'; -import { useEffect, useRef, useState } from 'react'; +import { createFileRoute, useRouter } from '@tanstack/react-router'; +import { useEffect, useState } from 'react'; import toast from 'react-hot-toast'; export const Route = createFileRoute('/game/update/$source/$id')({ diff --git a/src/mainview/routes/games.tsx b/src/mainview/routes/games.tsx index e6fd86e..cd1fe45 100644 --- a/src/mainview/routes/games.tsx +++ b/src/mainview/routes/games.tsx @@ -2,7 +2,7 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { CollectionsDetail } from '../components/CollectionsDetail'; import { zodValidator } from '@tanstack/zod-adapter'; import z from 'zod'; -import { GameListFilterType } from '@/shared/constants'; +import { GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; import { useSessionStorage } from 'usehooks-ts'; import HeaderSearchField from '../components/HeaderSearchField'; import { useEffect } from 'react'; diff --git a/src/mainview/routes/index.tsx b/src/mainview/routes/index.tsx index 9505d30..a9d33c1 100644 --- a/src/mainview/routes/index.tsx +++ b/src/mainview/routes/index.tsx @@ -50,7 +50,7 @@ import SelectMenu from "../components/SelectMenu"; import HeaderSearchField from "../components/HeaderSearchField"; import CardElement from "../components/CardElement"; import { Router } from ".."; -import { FrontEndId } from "@/shared/types"; +import { FrontEndId } from "@simeonradivoev/gameflow-sdk/shared"; export const Route = createFileRoute("/")({ component: ConsoleHomeUI, diff --git a/src/mainview/routes/platform.$source.$id.tsx b/src/mainview/routes/platform.$source.$id.tsx index 73d0265..f4df81d 100644 --- a/src/mainview/routes/platform.$source.$id.tsx +++ b/src/mainview/routes/platform.$source.$id.tsx @@ -1,7 +1,8 @@ import { createFileRoute, useRouter } from "@tanstack/react-router"; import { CollectionsDetail } from "../components/CollectionsDetail"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { GameListFilterType, RPC_URL } from "../../shared/constants"; +import { RPC_URL } from "../../shared/constants"; +import { GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; import { deletePlatformMutation, localPlatformFilter, platformQuery, updatePlatformMutation } from "@queries/romm"; import { zodValidator } from "@tanstack/zod-adapter"; import z from "zod"; diff --git a/src/mainview/routes/settings/accounts.tsx b/src/mainview/routes/settings/accounts.tsx index ce5586c..eacd432 100644 --- a/src/mainview/routes/settings/accounts.tsx +++ b/src/mainview/routes/settings/accounts.tsx @@ -13,7 +13,8 @@ import useEffect, useRef, } from "react"; -import { RommLoginDataSchema, RPC_URL } from "@shared/constants"; +import { RPC_URL } from "@shared/constants"; +import { RommLoginDataSchema } from '@simeonradivoev/gameflow-sdk/shared'; import toast from "react-hot-toast"; import { OptionSpace } from "../../components/options/OptionSpace"; import { useSettingsForm, useSettingsFormContext } from "../../components/options/SettingsAppForm"; diff --git a/src/mainview/routes/settings/directories.tsx b/src/mainview/routes/settings/directories.tsx index 4af256b..5adee26 100644 --- a/src/mainview/routes/settings/directories.tsx +++ b/src/mainview/routes/settings/directories.tsx @@ -13,10 +13,13 @@ import { systemApi } from '@/mainview/scripts/clientApi'; import useActiveControl from '@/mainview/scripts/gamepads'; import { changeDownloadsMutation } from '@queries/settings'; import { downloadDrivesQuery } from '@/mainview/scripts/queries/system'; -import { DownloadsDrive } from '@/shared/types'; +import { DownloadsDrive } from '@simeonradivoev/gameflow-sdk/shared'; +import { zodValidator } from '@tanstack/zod-adapter'; +import z from 'zod'; export const Route = createFileRoute('/settings/directories')({ component: RouteComponent, + validateSearch: zodValidator(z.object({ focus: z.string().optional() })) }); function DriveComponent (data: { drive: DownloadsDrive; downloadsSize: number; refetchDrives: () => void; }) diff --git a/src/mainview/routes/settings/emulators.tsx b/src/mainview/routes/settings/emulators.tsx index 7e3f381..9abddb4 100644 --- a/src/mainview/routes/settings/emulators.tsx +++ b/src/mainview/routes/settings/emulators.tsx @@ -8,7 +8,8 @@ import { Check, ChevronDown, FolderSearch, HardDrive, Plug, SearchAlert, Store, import { ContextDialog, ContextList, DialogEntry, OptionElement } from '../../components/ContextDialog'; import classNames from 'classnames'; import { twMerge } from 'tailwind-merge'; -import { RPC_URL, SettingsSchema } from '../../../shared/constants'; +import { RPC_URL } from '../../../shared/constants'; +import { SettingsSchema } from '@simeonradivoev/gameflow-sdk/shared'; import emulators from '@emulators'; import { FocusContext, setFocus, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; import { GamePadButtonCode, Shortcut, useShortcuts } from '@/mainview/scripts/shortcuts'; @@ -20,11 +21,14 @@ import { FOCUS_KEYS } from '@/mainview/scripts/types'; import { scrollIntoNearestParent, scrollIntoViewHandler, useDragScroll } from '@/mainview/scripts/utils'; import { SettingsOption } from '@/mainview/components/options/SettingsOption'; import { SettingsDropdown } from '@/mainview/components/options/SettingsDropdown'; -import { FrontEndEmulator } from '@/shared/types'; +import { FrontEndEmulator } from '@simeonradivoev/gameflow-sdk/shared'; +import { zodValidator } from '@tanstack/zod-adapter'; +import z from 'zod'; export const Route = createFileRoute('/settings/emulators')({ component: RouteComponent, pendingComponent: EmulatorsPending, + validateSearch: zodValidator(z.object({ focus: z.string().optional() })) }); function EmulatorsPending () diff --git a/src/mainview/routes/settings/interface.tsx b/src/mainview/routes/settings/interface.tsx index f1a42c8..8ef70d4 100644 --- a/src/mainview/routes/settings/interface.tsx +++ b/src/mainview/routes/settings/interface.tsx @@ -1,11 +1,15 @@ import { LocalOption } from '@/mainview/components/options/LocalOption'; -import { LocalSettingsSchema, settingRegistry } from '@/shared/constants'; +import { settingRegistry } from '@simeonradivoev/gameflow-sdk/shared'; +import { LocalSettingsSchema } from '@simeonradivoev/gameflow-sdk/shared'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; import { createFileRoute } from '@tanstack/react-router'; import { Terminal } from 'lucide-react'; +import { zodValidator } from '@tanstack/zod-adapter'; +import z from 'zod'; export const Route = createFileRoute('/settings/interface')({ component: RouteComponent, + validateSearch: zodValidator(z.object({ focus: z.string().optional() })) }); function RouteComponent () diff --git a/src/mainview/routes/settings/plugin.$source.tsx b/src/mainview/routes/settings/plugin.$source.tsx index 7263bea..c80b592 100644 --- a/src/mainview/routes/settings/plugin.$source.tsx +++ b/src/mainview/routes/settings/plugin.$source.tsx @@ -5,23 +5,25 @@ import { OptionDropdown } from '@/mainview/components/options/OptionDropdown'; import { OptionInput } from '@/mainview/components/options/OptionInput'; import { OptionSpace } from '@/mainview/components/options/OptionSpace'; import { RoundButton } from '@/mainview/components/RoundButton'; -import { getPluginDetailsQuery } from '@/mainview/scripts/queries/plugins'; +import { allPluginsFilter, getPluginDetailsQuery, updatePluginMutation } from '@/mainview/scripts/queries/plugins'; import { getPluginActionsQuery, getPluginSettingQuery, getPluginSettingsDefinitionQuery, pluginActionMutation, setPluginSettingMutation } from '@/mainview/scripts/queries/settings'; import { GamePadButtonCode, useShortcuts } from '@/mainview/scripts/shortcuts'; import { scrollIntoViewHandler } from '@/mainview/scripts/utils'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; +import { PluginUpdateCheck } from '@simeonradivoev/gameflow-sdk/shared'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { JSONSchema7 } from 'json-schema'; -import { ArrowLeft, CirclePlay, Settings2 } from 'lucide-react'; +import { ArrowLeft, ArrowRight, CircleFadingArrowUp, CirclePlay, Settings2 } from 'lucide-react'; import toast from 'react-hot-toast'; export const Route = createFileRoute('/settings/plugin/$source')({ component: RouteComponent, pendingComponent: Loading, async loader (ctx) { - const definitions = await ctx.context.queryClient.fetchQuery(getPluginSettingsDefinitionQuery(ctx.params.source)); - const actions = await ctx.context.queryClient.fetchQuery(getPluginActionsQuery(ctx.params.source)); + const source = decodeURIComponent(ctx.params.source); + const definitions = await ctx.context.queryClient.fetchQuery(getPluginSettingsDefinitionQuery(source)); + const actions = await ctx.context.queryClient.fetchQuery(getPluginActionsQuery(source)); await new Promise(resolve => setTimeout(resolve, 1000)); return { definitions, actions }; }, @@ -38,7 +40,8 @@ function Loading () function PluginAction (data: { id: string, title: string | undefined, description: string | undefined; action: string; reload: () => void; }) { - const { source } = Route.useParams(); + const { source: sourceRaw } = Route.useParams(); + const source = decodeURIComponent(sourceRaw); const action = useMutation({ ...pluginActionMutation(source, data.id), onSuccess (acitonData, variables, onMutateResult, context) @@ -67,7 +70,8 @@ function PluginAction (data: { id: string, title: string | undefined, descriptio function PluginOption (data: { name: string, title?: string, prop: JSONSchema7; }) { - const { source } = Route.useParams(); + const { source: sourceRaw } = Route.useParams(); + const source = decodeURIComponent(sourceRaw); const { data: value, refetch: refetchValue } = useQuery(getPluginSettingQuery(source, data.name)); const setValue = useMutation({ ...setPluginSettingMutation(source, data.name), @@ -108,12 +112,21 @@ function PluginOption (data: { name: string, title?: string, prop: JSONSchema7; ; } -function Settings () +function Settings (data: { update: PluginUpdateCheck | undefined; }) { const { definitions, actions } = Route.useLoaderData(); - const { source } = Route.useParams(); + const { source: sourceRaw } = Route.useParams(); + const source = decodeURIComponent(sourceRaw); const queryClient = useQueryClient(); - + const navigate = useNavigate(); + const update = useMutation({ + ...updatePluginMutation(source), + onSuccess (data, variables, onMutateResult, context) + { + context.client.invalidateQueries(allPluginsFilter); + navigate({ to: '/settings/plugin/$source', params: { source: encodeURIComponent(source) }, replace: true }); + }, + }); const handleReload = () => { queryClient.refetchQueries(getPluginSettingsDefinitionQuery(source)); @@ -121,7 +134,7 @@ function Settings () }; const { ref, focusKey } = useFocusable({ focusKey: 'plugin-settings', - focusable: (definitions?.properties && Object.keys(definitions?.properties).length > 0) || actions.length > 0 + focusable: (definitions?.properties && Object.keys(definitions?.properties).length > 0) || actions.length > 0 || !!data.update }); return
@@ -148,6 +161,15 @@ function Settings () })}
Actions
+ {!!data.update && +
Update
+
{data?.update?.current} {'>'} {data?.update?.new}
+
}> + + } {actions?.map(a => )} ; @@ -155,7 +177,8 @@ function Settings () function RouteComponent () { - const { source } = Route.useParams(); + const { source: sourceRaw } = Route.useParams(); + const source = decodeURIComponent(sourceRaw); const { ref, focusKey, focusSelf } = useFocusable({ focusKey: 'plugins' }); const { data } = useQuery(getPluginDetailsQuery(source)); @@ -167,17 +190,17 @@ function RouteComponent ()
-
+
- {data?.displayName} +
{data?.displayName}
+
{data?.version}
+ {!!data?.update &&
{data?.update.new}
}
    {data?.keywords?.map((k, i) =>
  • {k}
  • )}
{data?.description}
- - - +
; diff --git a/src/mainview/routes/settings/plugins.tsx b/src/mainview/routes/settings/plugins.tsx index c9476ba..c55fc8f 100644 --- a/src/mainview/routes/settings/plugins.tsx +++ b/src/mainview/routes/settings/plugins.tsx @@ -3,13 +3,13 @@ import { pluginCategoryIcons, pluginCategoryPriorities } from '@/mainview/compon import { OptionInput } from '@/mainview/components/options/OptionInput'; import { OptionSpace } from '@/mainview/components/options/OptionSpace'; import { RoundButton } from '@/mainview/components/RoundButton'; -import { enablePluginMutation, getAllPluginsQuery } from '@/mainview/scripts/queries/plugins'; +import { enablePluginMutation, getAllPluginsQuery, uninstallPluginMutation } from '@/mainview/scripts/queries/plugins'; import { GamePadButtonCode, Shortcut } from '@/mainview/scripts/shortcuts'; -import { FrontendPlugin } from '@/shared/types'; +import { FrontendPlugin } from '@simeonradivoev/gameflow-sdk/shared'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; import { useMutation, useQuery } from '@tanstack/react-query'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; -import { Eye, Puzzle, Search, Settings2 } from 'lucide-react'; +import { CircleFadingArrowUp, Eye, Puzzle, Settings2, Trash } from 'lucide-react'; export const Route = createFileRoute('/settings/plugins')({ component: RouteComponent, @@ -33,7 +33,9 @@ function Plugin (data: { }, }); - const handleDetails = () => navigate({ to: '/settings/plugin/$source', params: { source: data.plugin.name }, replace: true, viewTransition: { types: ['slide-up'] } }); + const uninstall = useMutation(uninstallPluginMutation(data.plugin.name)); + const handleUninstall = () => uninstall.mutate(); + const handleDetails = () => navigate({ to: '/settings/plugin/$source', params: { source: encodeURIComponent(data.plugin.name) }, replace: true, viewTransition: { types: ['slide-up'] } }); return : }
-
{data.plugin.displayName}
+
{data.plugin.displayName ?? data.plugin.name}
{data.plugin.name} ({data.plugin.version})
{data.plugin.hasSettings && } + {data.plugin.update &&
+ +
}
@@ -55,6 +60,7 @@ function Plugin (data: { >
{data.plugin.hasSettings ? : } + {data.plugin.canUninstall && {uninstall.isPending ? : }} {data.plugin.canDisable && data.setEnabled(!!v)} value={data.plugin.enabled} name={data.plugin.name} type="checkbox" />}
; diff --git a/src/mainview/routes/settings/update.tsx b/src/mainview/routes/settings/update.tsx index 905ada6..3f27639 100644 --- a/src/mainview/routes/settings/update.tsx +++ b/src/mainview/routes/settings/update.tsx @@ -3,7 +3,7 @@ import DotsLoading from '@/mainview/components/backgrounds/dots'; import { Button } from '@/mainview/components/options/Button'; import { checkUpdateMutation, hasUpdateQuery, updateMutation } from '@/mainview/scripts/queries/system'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { CircleFadingArrowUp, RefreshCcw } from 'lucide-react'; import { MarkdownAsync } from 'react-markdown'; diff --git a/src/mainview/routes/store/details.emulator.$id.tsx b/src/mainview/routes/store/details.emulator.$id.tsx index 60b3917..3383ea8 100644 --- a/src/mainview/routes/store/details.emulator.$id.tsx +++ b/src/mainview/routes/store/details.emulator.$id.tsx @@ -29,7 +29,7 @@ import FocusTooltip from "@/mainview/components/FocusTooltip"; import { AutoFocus } from "@/mainview/components/AutoFocus"; import { FilterUI } from "@/mainview/components/Filters"; import Markdown from "react-markdown"; -import { FrontEndEmulatorDetailed } from "@/shared/types"; +import { FrontEndEmulatorDetailed } from "@simeonradivoev/gameflow-sdk/shared"; export const Route = createFileRoute('/store/details/emulator/$id')({ component: RouteComponent, diff --git a/src/mainview/routes/store/details.plugin.$id.tsx b/src/mainview/routes/store/details.plugin.$id.tsx new file mode 100644 index 0000000..5e5168c --- /dev/null +++ b/src/mainview/routes/store/details.plugin.$id.tsx @@ -0,0 +1,161 @@ +import { AutoFocus } from '@/mainview/components/AutoFocus'; +import DotsLoading from '@/mainview/components/backgrounds/dots'; +import { StickyHeaderUI } from '@/mainview/components/Header'; +import LoadingScreen from '@/mainview/components/LoadingScreen'; +import { Button } from '@/mainview/components/options/Button'; +import { FloatingShortcuts } from '@/mainview/components/Shortcuts'; +import StatList, { StatEntry } from '@/mainview/components/StatList'; +import { installPluginMutation, pluginFilter, uninstallPluginMutation, updatePluginMutation } from '@/mainview/scripts/queries/plugins'; +import { pluginDetailsQuery } from '@/mainview/scripts/queries/store'; +import { GamePadButtonCode, useShortcuts } from '@/mainview/scripts/shortcuts'; +import { HandleGoBack } from '@/mainview/scripts/utils'; +import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; +import { QueryClient, useMutation } from '@tanstack/react-query'; +import { createFileRoute, useNavigate, useRouter } from '@tanstack/react-router'; +import { ArrowRight, CircleFadingArrowUp, Download, Settings, Trash } from 'lucide-react'; +import prettyBytes from 'pretty-bytes'; +import { Suspense } from 'react'; + +export const Route = createFileRoute('/store/details/plugin/$id')({ + component: RouteComponent, + pendingComponent: Loading, + async loader (ctx) + { + const id = decodeURIComponent(ctx.params.id); + const data = await ctx.context.queryClient.fetchQuery(pluginDetailsQuery(id)); + return { data }; + }, +}); + +function Loading () +{ + const { ref, focusSelf } = useFocusable({ focusKey: 'plugin-details' }); + return <> + + + ; +} + +function Details () +{ + const { id } = Route.useParams(); + const plugin = decodeURIComponent(id); + const { data } = Route.useLoaderData(); + const navigate = useNavigate(); + const handleRefresh = (client: QueryClient) => + { + client.invalidateQueries(pluginFilter(plugin)); + navigate({ to: '/store/details/plugin/$id', params: { id: encodeURIComponent(id) }, replace: true }); + }; + const update = useMutation({ + ...updatePluginMutation(plugin), + onSuccess (data, variables, onMutateResult, context) + { + handleRefresh(context.client); + }, + }); + const install = useMutation({ + ...installPluginMutation(plugin), + onSuccess (data, variables, onMutateResult, context) + { + handleRefresh(context.client); + }, + }); + const uninstall = useMutation({ + ...uninstallPluginMutation(plugin), + onSuccess (data, variables, onMutateResult, context) + { + handleRefresh(context.client); + }, + }); + + const stats: StatEntry[] = []; + if (data.devDependencies) + { + stats.push({ content: Object.keys(data.devDependencies), label: "Dev Dependecies" }); + } + if (data.dependencies) + { + stats.push({ content: Object.keys(data.dependencies), label: "Dependecies" }); + } + if (data.maintainers) + { + stats.push({ content: data.maintainers.map(m => m.name), label: "Maintainers" }); + } + if (data.dist) + { + stats.push({ content: prettyBytes(data.dist.unpackedSize), label: "Size" }); + } + if (data.license) + { + stats.push({ content: data.license, label: "License" }); + } + return <> + +
+
+
{data.name}
+
+
+ {data.update ? <> +
{data.update.from}
+ +
{data.version}
+ : +
{data.version}
} + +
+ by {data.author?.name ?? data._npmUser?.name}
+
+
+ {data.installed && <> + {!!data.update && } + + + + } + {!data.installed && } + +
+
+
Details
+
+
{data.description}
+ +
+
Keywords
+
+ {data.keywords.map(k =>
  • {k}
  • )} +
    + ; +} + +function RouteComponent () +{ + const router = useRouter(); + const { ref, focusKey, focusSelf } = useFocusable({ focusKey: 'plugin-details' }); + useShortcuts(focusKey, () => [{ + label: "Return", button: GamePadButtonCode.B, action (e) + { + HandleGoBack(router, e); + }, + }]); + return
    + + + }> +
    + + + + +
    ; +} diff --git a/src/mainview/routes/store/tab/games.tsx b/src/mainview/routes/store/tab/games.tsx index c36b824..21e059f 100644 --- a/src/mainview/routes/store/tab/games.tsx +++ b/src/mainview/routes/store/tab/games.tsx @@ -8,13 +8,13 @@ import LoadMoreButton from '@/mainview/components/LoadMoreButton'; import { storeGamesInfiniteQuery } from '@queries/store'; import InvalidStoreError from '@/mainview/components/store/InvalidStoreError'; import { CardList, GameMetaExtra } from '@/mainview/components/CardList'; -import { GameListFilterType, RPC_URL } from '@/shared/constants'; +import { RPC_URL } from '@/shared/constants'; +import { GameListFilterType, FrontEndGameType } from '@simeonradivoev/gameflow-sdk/shared'; import { useSessionStorage } from 'usehooks-ts'; import { zodValidator } from '@tanstack/zod-adapter'; import z from 'zod'; import SideFilters from '@/mainview/components/SideFilters'; import { gameFiltersQuery } from '@/mainview/scripts/queries/romm'; -import { FrontEndGameType } from '@/shared/types'; export const Route = createFileRoute('/store/tab/games')({ component: RouteComponent, diff --git a/src/mainview/routes/store/tab/index.tsx b/src/mainview/routes/store/tab/index.tsx index 08ca653..dcb53c8 100644 --- a/src/mainview/routes/store/tab/index.tsx +++ b/src/mainview/routes/store/tab/index.tsx @@ -16,7 +16,7 @@ import { useQuery } from '@tanstack/react-query'; import { autoEmulatorsQuery } from '@queries/settings'; import { storeEmulatorsRecommendedQuery, storeFeaturedGamesQuery } from '@queries/store'; import ImageWithFallbacks from '@/mainview/components/ImageWithFallbacks'; -import { FrontEndGameTypeDetailed } from '@/shared/types'; +import { FrontEndGameTypeDetailed } from '@simeonradivoev/gameflow-sdk/shared'; export const Route = createFileRoute('/store/tab/')({ component: RouteComponent diff --git a/src/mainview/routes/store/tab/plugins.tsx b/src/mainview/routes/store/tab/plugins.tsx new file mode 100644 index 0000000..db55628 --- /dev/null +++ b/src/mainview/routes/store/tab/plugins.tsx @@ -0,0 +1,151 @@ +import { allPluginsFilter, installPluginMutation, uninstallPluginMutation, updatePluginMutation } from '@/mainview/scripts/queries/plugins'; +import { pluginsQuery } from '@/mainview/scripts/queries/store'; +import { GamePadButtonCode, Shortcut, useShortcuts } from '@/mainview/scripts/shortcuts'; +import { FOCUS_KEYS } from '@/mainview/scripts/types'; +import { PluginEntryType } from '@simeonradivoev/gameflow-sdk/shared'; +import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; +import { QueryClient, useMutation, useQuery } from '@tanstack/react-query'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { zodValidator } from '@tanstack/zod-adapter'; +import { CircleFadingArrowUp, Dot, Download, HardDrive, Puzzle } from 'lucide-react'; +import prettyMilliseconds from 'pretty-ms'; +import { useSessionStorage } from 'usehooks-ts'; +import z from 'zod'; + +export const Route = createFileRoute('/store/tab/plugins')({ + component: RouteComponent, + validateSearch: zodValidator(z.object({ + search: z.string().optional() + })) +}); + +function PluginCard (data: { plugin: PluginEntryType; }) +{ + const navigate = useNavigate(); + const onAction = () => + { + navigate({ to: '/store/details/plugin/$id', params: { id: decodeURIComponent(data.plugin.package.name) } }); + }; + const { ref, focusKey } = useFocusable({ focusKey: FOCUS_KEYS.PLUGIN_ENTRY(data.plugin.package.sanitized_name), onEnterPress: onAction }); + const handleRefresh = (client: QueryClient) => + { + client.invalidateQueries(allPluginsFilter); + navigate({ to: '/store/tab/plugins', replace: true }); + }; + const update = useMutation({ + ...updatePluginMutation(data.plugin.package.name), + onSuccess (data, variables, onMutateResult, context) + { + handleRefresh(context.client); + }, + }); + const install = useMutation({ + ...installPluginMutation(data.plugin.package.name), + onSuccess (f, variables, onMutateResult, context) + { + handleRefresh(context.client); + } + }); + const uninstall = useMutation({ + ...uninstallPluginMutation(data.plugin.package.name), + onSuccess (f, variables, onMutateResult, context) + { + handleRefresh(context.client); + } + }); + useShortcuts(focusKey, () => + { + const shortcuts: Shortcut[] = [{ + label: "Details", button: GamePadButtonCode.A, action (e) + { + onAction(); + }, + }]; + + if (data.plugin.installed) + { + shortcuts.push({ + label: "Uninstall", + button: GamePadButtonCode.X, + action (e) + { + uninstall.mutate(); + }, + }); + + if (data.plugin.update) + { + shortcuts.push({ + label: "Update", + button: GamePadButtonCode.Y, + action (e) + { + update.mutate(); + }, + }); + } + + } else + { + shortcuts.push({ + label: "Install", + button: GamePadButtonCode.X, + action (e) + { + install.mutate(); + }, + }); + } + return shortcuts; + }, [data.plugin.installed, install.isPending, uninstall.isPending]); + return
    +
    +
    + {data.plugin.installed && } + {data.plugin.update && } + {data.plugin.package.name} + {(install.isPending || uninstall.isPending) && } +
    +
    {data.plugin.package.description}
    +
      {data.plugin.package.keywords.concat(...data.plugin.installed ? ["installed"] : []).map(k =>
    • {k}
    • )}
    +
      +
    • {data.plugin.package.publisher.username}
    • + +
    • {data.plugin.package.version}
    • + +
    • {prettyMilliseconds(new Date().getTime() - data.plugin.package.date.getTime(), { hideSeconds: true })}
    • + +
    • {data.plugin.package.license}
    • + {install.isPending && <> + +
    • installing
    • + } + {uninstall.isPending && <> + +
    • uninstalling
    • + } +
    +
    +
    +
    + + {data.plugin.downloads.monthly} +
    +
    +
    ; +} + +function RouteComponent () +{ + const [search] = useSessionStorage(`${Route.to}-search`, undefined); + const { data: plugins } = useQuery(pluginsQuery(search)); + const { ref, focusKey } = useFocusable({ focusKey: "plugins-store" }); + return
    + +
    {plugins?.total} Plugins
    +
    + {plugins?.objects.map((p, i) => )} +
    +
    +
    ; +} diff --git a/src/mainview/routes/store/tab/route.tsx b/src/mainview/routes/store/tab/route.tsx index b874445..05b4c1e 100644 --- a/src/mainview/routes/store/tab/route.tsx +++ b/src/mainview/routes/store/tab/route.tsx @@ -14,6 +14,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { useMatchRoute, useRouter } from '@tanstack/react-router'; import { createFileRoute, Outlet } from '@tanstack/react-router'; import { zodValidator } from '@tanstack/zod-adapter'; +import { Gamepad2, Home, Joystick, Puzzle } from 'lucide-react'; import { useRef } from 'react'; import { useSessionStorage } from 'usehooks-ts'; import z from 'zod'; @@ -93,9 +94,10 @@ function RouteComponent () const headerRef = useRef(null); const sentinelRef = useRef(null); const filters: Record = { - home: { label: "Home", selected: useIsSettings(''), }, - emulators: { label: "Emulators", selected: useIsSettings('emulators') }, - games: { label: "Games", selected: useIsSettings('games') } + home: { label: "Home", icon: , selected: useIsSettings(''), }, + emulators: { label: "Emulators", icon: , selected: useIsSettings('emulators') }, + games: { label: "Games", icon: , selected: useIsSettings('games') }, + plugins: { label: "Plugins", icon: , selected: useIsSettings('plugins') } }; const [search, setSearch] = useSessionStorage(`${router.history.location.pathname}-search`, undefined); const [, setGamesSearch] = useSessionStorage(`/store/tab/games-search`, undefined); diff --git a/src/mainview/scripts/contexts.ts b/src/mainview/scripts/contexts.ts index f54f0e2..3b33a01 100644 --- a/src/mainview/scripts/contexts.ts +++ b/src/mainview/scripts/contexts.ts @@ -1,8 +1,7 @@ -import { SystemInfoType } from "@/shared/constants"; +import { SystemInfoType, Drive } from '@simeonradivoev/gameflow-sdk/shared'; import { Direction, FocusDetails } from "@noriginmedia/norigin-spatial-navigation"; import { createContext } from "react"; import { Shortcut } from "./shortcuts"; -import { Drive } from "@/shared/types"; export const StoreContext = createContext({} as { showDetails: (type: 'emulator' | 'game', source: string, id: string, focusSource: string) => void; diff --git a/src/mainview/scripts/queries/plugins.ts b/src/mainview/scripts/queries/plugins.ts index 323b168..8e6e948 100644 --- a/src/mainview/scripts/queries/plugins.ts +++ b/src/mainview/scripts/queries/plugins.ts @@ -1,4 +1,4 @@ -import { mutationOptions, queryOptions } from "@tanstack/react-query"; +import { mutationOptions, QueryFilters, queryOptions } from "@tanstack/react-query"; import { pluginsApi } from "../clientApi"; export const getAllPluginsQuery = queryOptions({ @@ -14,7 +14,7 @@ export const getAllPluginsQuery = queryOptions({ export const getPluginDetailsQuery = (source: string) => queryOptions({ queryKey: ['plugins', source], queryFn: async () => { - const { data, error } = await pluginsApi.plugins({ id: source }).get(); + const { data, error } = await pluginsApi.plugins({ id: encodeURIComponent(source) }).get(); if (error) throw error; return data; } @@ -24,7 +24,51 @@ export const enablePluginMutation = mutationOptions({ mutationKey: ['plugin', 'enable'], mutationFn: async (vars: { id: string, enabled: boolean; }) => { - const { error } = await pluginsApi.plugins({ id: vars.id }).post({ enabled: vars.enabled }); + const { error } = await pluginsApi.plugins({ id: encodeURIComponent(vars.id) }).post({ enabled: vars.enabled }); if (error) throw error; } +}); + +export const installPluginMutation = (id: string) => mutationOptions({ + mutationKey: ['plugin', 'install', id], + mutationFn: async () => + { + const { data, error } = await pluginsApi.plugins.install.post({ id }); + if (error) throw error; + return data; + } +}); + +export const updatePluginMutation = (id: string) => mutationOptions({ + mutationKey: ['plugin', 'update', id], + mutationFn: async () => + { + const { data, error } = await pluginsApi.plugins.update.post({ id }); + if (error) throw error; + return data; + } +}); + +export const uninstallPluginMutation = (id: string) => mutationOptions({ + mutationKey: ['plugin', 'uninstall', id], + mutationFn: async () => + { + const { data, error } = await pluginsApi.plugins.uninstall.post({ id: id }); + if (error) throw error; + return data; + } +}); + +export const pluginFilter = (id: string): QueryFilters => ({ + predicate (query) + { + return query.queryKey.includes(id); + }, +}); + +export const allPluginsFilter: QueryFilters = ({ + predicate (query) + { + return query.queryKey.includes('plugin') || query.queryKey.includes('plugins'); + }, }); \ No newline at end of file diff --git a/src/mainview/scripts/queries/romm.ts b/src/mainview/scripts/queries/romm.ts index 4a7a4f6..63f8623 100644 --- a/src/mainview/scripts/queries/romm.ts +++ b/src/mainview/scripts/queries/romm.ts @@ -1,9 +1,9 @@ -import { DefaultRommStaleTime, GameListFilterType, RommLoginDataSchema } from "@/shared/constants"; +import { DefaultRommStaleTime } from "@/shared/constants"; +import { GameListFilterType, RommLoginDataSchema, FrontEndId } from '@simeonradivoev/gameflow-sdk/shared'; import { rommApi, settingsApi } from "../clientApi"; import { InvalidateQueryFilters, mutationOptions, QueryClient, QueryFilters, queryOptions } from "@tanstack/react-query"; import z from "zod"; import { statsApiStatsGetOptions } from "@/clients/romm/@tanstack/react-query.gen"; -import { FrontEndId } from "@/shared/types"; export const allGamesQuery = (filter?: GameListFilterType) => queryOptions({ queryKey: ['games', filter ?? 'all'], diff --git a/src/mainview/scripts/queries/settings.ts b/src/mainview/scripts/queries/settings.ts index e0f605e..03956af 100644 --- a/src/mainview/scripts/queries/settings.ts +++ b/src/mainview/scripts/queries/settings.ts @@ -138,7 +138,7 @@ export const getPluginSettingsDefinitionQuery = (source: string) => queryOptions queryKey: ['settings', source, 'definitions'], queryFn: async () => { - const { data: value, error } = await settingsApi.api.settings.definitions({ source }).get(); + const { data: value, error } = await settingsApi.api.settings.definitions({ source: encodeURIComponent(source) }).get(); if (error) throw error; return value; @@ -148,7 +148,7 @@ export const getPluginSettingQuery = (source: string, id: string) => queryOption queryKey: ["setting", source, id], queryFn: async () => { - const { data, error } = await settingsApi.api.settings({ source })({ id }).get(); + const { data, error } = await settingsApi.api.settings({ source: encodeURIComponent(source) })({ id: encodeURIComponent(id) }).get(); if (error) throw error; return data; @@ -158,7 +158,7 @@ export const setPluginSettingMutation = (source: string, id: string) => mutation mutationKey: ["setting", source, id], mutationFn: async (value: any) => { - const { data, error } = await settingsApi.api.settings({ source })({ id }).put({ value }); + const { data, error } = await settingsApi.api.settings({ source: encodeURIComponent(source) })({ id: encodeURIComponent(id) }).put({ value }); if (error) throw error; return data; @@ -167,7 +167,7 @@ export const setPluginSettingMutation = (source: string, id: string) => mutation export const getPluginActionsQuery = (source: string) => queryOptions({ queryKey: ['plugin', source, 'actions'], queryFn: async () => { - const { data, error } = await settingsApi.api.settings.actions({ source }).get(); + const { data, error } = await settingsApi.api.settings.actions({ source: encodeURIComponent(source) }).get(); if (error) throw error; return data; @@ -177,7 +177,7 @@ export const pluginActionMutation = (source: string, id: string) => mutationOpti mutationKey: ["plugin", source, "action"], mutationFn: async () => { - const { data, error, response } = await settingsApi.api.settings.actions({ source })({ id }).post(); + const { data, error, response } = await settingsApi.api.settings.actions({ source: encodeURIComponent(source) })({ id: encodeURIComponent(id) }).post(); if (error) throw error; return { data: data as any, response }; diff --git a/src/mainview/scripts/queries/store.ts b/src/mainview/scripts/queries/store.ts index 6347944..a428f40 100644 --- a/src/mainview/scripts/queries/store.ts +++ b/src/mainview/scripts/queries/store.ts @@ -1,8 +1,6 @@ import { infiniteQueryOptions, mutationOptions, queryOptions } from "@tanstack/react-query"; import { rommApi, storeApi } from "../clientApi"; -import { GameListFilterType } from "@/shared/constants"; -import { FrontEndGameType } from "@/shared/types"; - +import { GameListFilterType, FrontEndGameType } from '@simeonradivoev/gameflow-sdk/shared'; export const storeEmulatorsQuery = (filters: { search?: string; }) => queryOptions({ queryKey: ['store-emulators', filters], queryFn: async () => @@ -97,4 +95,22 @@ export const getUpdateInfoForEmulator = (id: string) => queryOptions({ if (error) throw error; return data; } +}); +export const pluginsQuery = (search?: string) => queryOptions({ + queryKey: ['plugins', 'store', search ?? 'all'], + queryFn: async () => + { + const { data, error } = await storeApi.api.store.plugins.get({ query: { search } }); + if (error) throw error; + return data; + } +}); +export const pluginDetailsQuery = (id: string) => queryOptions({ + queryKey: ['plugin', 'store', id], + queryFn: async () => + { + const { data, error } = await storeApi.api.store.plugin.get({ query: { plugin: id } }); + if (error) throw error; + return data; + } }); \ No newline at end of file diff --git a/src/mainview/scripts/types.ts b/src/mainview/scripts/types.ts index a192266..6ab944a 100644 --- a/src/mainview/scripts/types.ts +++ b/src/mainview/scripts/types.ts @@ -1,4 +1,4 @@ -import { FrontEndId } from "@/shared/types"; +import { FrontEndId } from "@simeonradivoev/gameflow-sdk/shared"; export const FOCUS_KEYS = { NAV_CATEGORIES: "NAV_CATEGORIES", @@ -14,4 +14,5 @@ export const FOCUS_KEYS = { GAME_CARD: (id: FrontEndId) => `GAME_${id.source}_${id.id}`, GAME_MATCH: (id: FrontEndId) => `GAME_${id.source}_${id.id}`, STATS_SECTION: "STATS_SECTION", + PLUGIN_ENTRY: (id: string) => `PLUGIN_${id}` } as const; \ No newline at end of file diff --git a/src/mainview/scripts/utils.ts b/src/mainview/scripts/utils.ts index 4859ddf..6635bd6 100644 --- a/src/mainview/scripts/utils.ts +++ b/src/mainview/scripts/utils.ts @@ -1,4 +1,4 @@ -import { LocalSettingsSchema, LocalSettingsType } from "@/shared/constants"; +import { LocalSettingsSchema, LocalSettingsType } from '@simeonradivoev/gameflow-sdk/shared'; import { DependencyList, RefObject, useEffect, useRef, useState } from "react"; import { useLocalStorage } from "usehooks-ts"; import { jobsApi, systemApi } from "./clientApi"; diff --git a/scripts/sdk/README.md b/src/packages/gameflow-sdk/README.md similarity index 53% rename from scripts/sdk/README.md rename to src/packages/gameflow-sdk/README.md index 7ac9193..8338233 100644 --- a/scripts/sdk/README.md +++ b/src/packages/gameflow-sdk/README.md @@ -13,3 +13,18 @@ The package must expose a main script gameflow will import and validate. It must For the plugin to show up in the UI for download. It must be published to NPM with the `gameflow-plugin` keyword. Gameflow uses bun to install plugins as packages from npmjs. Follow publishing instruction check the [NPM Docs](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry) + +## Dependencies + +Peer dependencies will not be installed when the run adds the plugin package. They are provided by gameflow. +All peer dependencies can be marked as external as gameflow provides it. There is a helper build script that does all that for you, to run it use. + +`bunx gameflow-build --entry=index.ts` + +supported arguments are +`--entry` the entry of the app to build +`--outdir` Where to build. Default is 'dist' +`--minify` Minify the code. Default is 'false' +`--sourcemap` Include a source map. Default is 'none' + +If you want to include dependencies that gameflow does not provide you have to bundle them in. Gameflow does not load dependencies for you. diff --git a/src/packages/gameflow-sdk/build.ts b/src/packages/gameflow-sdk/build.ts new file mode 100644 index 0000000..9f18b88 --- /dev/null +++ b/src/packages/gameflow-sdk/build.ts @@ -0,0 +1,27 @@ +#!/usr/bin/env bun + +import pkg from './package.json'; + +import { parseArgs } from "util"; + +const { values } = parseArgs({ + args: Bun.argv.slice(2), + options: { + outdir: { type: "string", default: "dist" }, + minify: { type: "boolean", default: false }, + sourcemap: { type: "string", default: "none" }, // "none" | "inline" | "external" + entry: { type: "string", default: "src/index.ts" }, + }, + allowPositionals: true, +}); + +await Bun.build({ + entrypoints: [values.entry], + outdir: values.outdir, + minify: values.minify, + sourcemap: values.sourcemap as any, + external: [...Object.keys(pkg.peerDependencies), pkg.name], + target: "bun", +}); + +console.log(`✅ Built to ${values.outdir}`); \ No newline at end of file diff --git a/src/bun/api/hooks/app.ts b/src/packages/gameflow-sdk/hooks/app.ts similarity index 88% rename from src/bun/api/hooks/app.ts rename to src/packages/gameflow-sdk/hooks/app.ts index bd549ca..d8fef7a 100644 --- a/src/bun/api/hooks/app.ts +++ b/src/packages/gameflow-sdk/hooks/app.ts @@ -3,7 +3,7 @@ import EmulatorHooks from "./emulators"; import GameHooks from "./games"; import StoreHooks from "./store"; -export default class GameflowHooks +export class GameflowHooks { games = new GameHooks(); emulators = new EmulatorHooks(); diff --git a/src/bun/api/hooks/auth.ts b/src/packages/gameflow-sdk/hooks/auth.ts similarity index 81% rename from src/bun/api/hooks/auth.ts rename to src/packages/gameflow-sdk/hooks/auth.ts index 992d91e..cb8dd1b 100644 --- a/src/bun/api/hooks/auth.ts +++ b/src/packages/gameflow-sdk/hooks/auth.ts @@ -1,5 +1,6 @@ -import { DownloadFileEntry } from "@/shared/types"; + import { AsyncSeriesHook } from "tapable"; +import { DownloadFileEntry } from "../shared"; export default class AuthHooks { diff --git a/src/bun/api/hooks/emulators.ts b/src/packages/gameflow-sdk/hooks/emulators.ts similarity index 83% rename from src/bun/api/hooks/emulators.ts rename to src/packages/gameflow-sdk/hooks/emulators.ts index 402bb21..768e56f 100644 --- a/src/bun/api/hooks/emulators.ts +++ b/src/packages/gameflow-sdk/hooks/emulators.ts @@ -1,5 +1,6 @@ -import { EmulatorPostInstallContext } from "@/bun/types/types"; -import { DownloadFileEntry, EmulatorSourceEntryType, EmulatorSystem } from "@/shared/types"; + +import { EmulatorPostInstallContextType } from "../index"; +import { DownloadFileEntry, EmulatorSourceEntryType, EmulatorSystem } from "../shared"; import { AsyncSeriesBailHook, AsyncSeriesHook } from "tapable"; export default class EmulatorHooks @@ -13,7 +14,7 @@ export default class EmulatorHooks /** * Triggered when emulator is downloaded or updated */ - emulatorPostInstall = new AsyncSeriesHook<[ctx: EmulatorPostInstallContext], { emulator: string; }>(['ctx']); + emulatorPostInstall = new AsyncSeriesHook<[ctx: EmulatorPostInstallContextType], { emulator: string; }>(['ctx']); findEmulatorSource = new AsyncSeriesHook<[ctx: { emulator: string; sources: EmulatorSourceEntryType[]; }]>(['ctx']); findEmulatorForSystem = new AsyncSeriesHook<[ctx: { system: string; emulators: string[]; }]>(['ctx']); @@ -24,7 +25,7 @@ export default class EmulatorHooks { return { ...tap, - fn: async (ctx: EmulatorPostInstallContext, ...rest: any[]) => + fn: async (ctx: EmulatorPostInstallContextType, ...rest: any[]) => { if (ctx.emulator === tap.emulator) { diff --git a/src/bun/api/hooks/games.ts b/src/packages/gameflow-sdk/hooks/games.ts similarity index 92% rename from src/bun/api/hooks/games.ts rename to src/packages/gameflow-sdk/hooks/games.ts index bb1f4bc..9083e47 100644 --- a/src/bun/api/hooks/games.ts +++ b/src/packages/gameflow-sdk/hooks/games.ts @@ -1,6 +1,6 @@ -import { EmulatorPackageType, GameListFilterType } from '@/shared/constants'; -import { CommandEntry, DownloadInfo, EmulatorSourceEntryType, EmulatorSupport, EmulatorSystem, FrontEndCollection, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeWithIds, FrontEndId, FrontEndPlatformType, GameLookup, SaveFileChange, SaveSlots } from '@/shared/types'; -import { SyncBailHook, AsyncSeriesHook, AsyncSeriesBailHook, Hook, AsyncSeriesWaterfallHook } from 'tapable'; + +import { EmulatorPackageType, GameListFilterType, CommandEntry, DownloadInfo, EmulatorSourceEntryType, EmulatorSupport, EmulatorSystem, FrontEndCollection, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeWithIds, FrontEndId, FrontEndPlatformType, GameLookup, SaveFileChange, SaveSlots } from '../shared'; +import { SyncBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } from 'tapable'; export default class GameHooks { diff --git a/src/bun/api/hooks/store.ts b/src/packages/gameflow-sdk/hooks/store.ts similarity index 86% rename from src/bun/api/hooks/store.ts rename to src/packages/gameflow-sdk/hooks/store.ts index b08cee5..c7f43e5 100644 --- a/src/bun/api/hooks/store.ts +++ b/src/packages/gameflow-sdk/hooks/store.ts @@ -1,5 +1,4 @@ -import { EmulatorDownloadInfoType } from "@/shared/constants"; -import { FrontEndEmulator, FrontEndEmulatorDetailed, FrontEndGameTypeDetailed } from "@/shared/types"; +import { FrontEndEmulator, FrontEndEmulatorDetailed, FrontEndGameTypeDetailed, EmulatorDownloadInfoType } from "../shared"; import { AsyncSeriesBailHook, AsyncSeriesHook } from "tapable"; export default class StoreHooks diff --git a/src/bun/types/types.schema.ts b/src/packages/gameflow-sdk/index.ts similarity index 72% rename from src/bun/types/types.schema.ts rename to src/packages/gameflow-sdk/index.ts index c4738fc..4149891 100644 --- a/src/bun/types/types.schema.ts +++ b/src/packages/gameflow-sdk/index.ts @@ -1,7 +1,20 @@ import z from "zod"; -import GameflowHooks from "../api/hooks/app"; -import Conf from "conf"; +import { GameflowHooks } from "./hooks/app"; +import { EmulatorDownloadInfoSchema, EmulatorPackageSchema, FrontendNotification, SettingsType } from "./shared"; import { $ZodRegistry } from "zod/v4/core"; +import Conf from "conf"; +import { EventEmitter } from 'node:events'; +import { TaskQueue } from "./task-queue"; + +export * from "./hooks/app"; +export * from "./task-queue"; + +export interface AppEventMap +{ + exitapp: []; + notification: [FrontendNotification]; + focus: []; +} export const PluginContextSchema = z.object({ hooks: z.instanceof(GameflowHooks) @@ -10,16 +23,22 @@ export const PluginContextSchema = z.object({ export const PluginLoadingContextSchema = z.object({ setProgress: z.function().input([z.number(), z.string()]).output(z.void()), config: z.instanceof(Conf).describe("Per plugin config. It will use the settings schema defined in the plugin class"), - zodRegistry: z.instanceof($ZodRegistry).describe("Used by the settings to register metadata for the UI") + zodRegistry: z.instanceof($ZodRegistry).describe("Used by the settings to register metadata for the UI"), + app: z.object({ + config: z.instanceof(Conf), + events: z.instanceof(EventEmitter), + taskQueue: z.instanceof(TaskQueue) + }) }).extend(PluginContextSchema.shape); export const PluginDescriptionSchema = z.object({ name: z.string(), - displayName: z.string(), + displayName: z.string().optional(), version: z.string(), - description: z.string(), + description: z.string().optional(), icon: z.url().optional().describe("Can be an external URL to an image or a data url"), keywords: z.array(z.string()).optional(), + peerDependencies: z.record(z.string(), z.string()).optional(), category: z.string().default("other"), main: z.string().describe("The main entry. It must export a default class implementing PluginType"), canDisable: z.boolean().default(true).optional().describe("Can the plugin be disabled or enabled by the user") @@ -42,16 +61,6 @@ export const PluginSchema = z.object({ }).or(z.record(z.string(), z.any()))).optional() }); -export type PluginType = Record> = Omit, "load" | 'settingsMigrations'> & { - load: (ctx: PluginLoadingContextType) => Promise; - settingsMigrations?: Record) => void>; -}; -export type PluginContextType = z.infer; -export type PluginLoadingContextType = Record> = z.infer & { - config: Conf; -}; -export type PluginDescriptionType = z.infer; - export const ActiveGameSchema = z.object({ process: z.any().optional(), gameId: z.object({ id: z.string(), source: z.string() }), @@ -60,4 +69,24 @@ export const ActiveGameSchema = z.object({ name: z.string(), command: z.object({ command: z.string().or(z.string().array()), startDir: z.string().optional() }) }); -export type ActiveGameType = z.infer; \ No newline at end of file + +export const EmulatorPostInstallContextSchema = z.object({ + emulator: z.string(), + emulatorPackage: EmulatorPackageSchema.optional(), + path: z.string(), + update: z.boolean(), + info: EmulatorDownloadInfoSchema, +}); + +export type ActiveGameType = z.infer; +export type PluginDescriptionType = z.infer; +export type PluginContextType = z.infer; +export type PluginLoadingContextType = Record> = z.infer & { + config: Conf; +}; +export type PluginType = Record> = Omit, "load" | 'settingsMigrations'> & { + load: (ctx: PluginLoadingContextType) => Promise; + settingsMigrations?: Record) => void>; +}; +export type EmulatorPostInstallContextType = z.infer; + diff --git a/src/packages/gameflow-sdk/package.json b/src/packages/gameflow-sdk/package.json new file mode 100644 index 0000000..7e51c16 --- /dev/null +++ b/src/packages/gameflow-sdk/package.json @@ -0,0 +1,51 @@ +{ + "name": "@simeonradivoev/gameflow-sdk", + "version": "1.5.3", + "types": "index.d.ts", + "description": "plugin SDK for the Gameflow Deck Launcher", + "exports": { + ".": "./index.ts", + "./shared": "./shared.ts" + }, + "bin": { + "gameflow-build": "build.ts" + }, + "peerDependencies": { + "7zip-bin": "^5.2.0", + "@auth/core": "^0.34.3", + "@elysiajs/cors": "^1.4.2", + "@elysiajs/eden": "^1.4.9", + "@jimp/wasm-webp": "^1.6.1", + "@phalcode/ts-igdb-client": "^1.0.26", + "cheerio": "^1.2.0", + "conf": "^15.1.0", + "drizzle-orm": "^0.45.2", + "elysia": "^1.4.28", + "fs-extra": "^11.3.5", + "get-folder-size": "^5.0.0", + "ini": "^6.0.0", + "jimp": "^1.6.1", + "mustache": "^4.2.0", + "node-7z": "^3.0.0", + "node-disk-info": "^1.3.0", + "node-downloader-helper": "^2.1.11", + "node-stream-zip": "^1.15.0", + "node-unrar-js": "^2.0.2", + "open": "^11.0.0", + "p-queue": "^9.2.0", + "pathe": "^2.0.3", + "slugify": "^1.6.9", + "smol-toml": "^1.6.1", + "systeminformation": "^5.31.5", + "tapable": "^2.3.3", + "tough-cookie": "^6.0.1", + "tough-cookie-file-store": "^3.3.0", + "unzip-stream": "^0.3.4", + "webview-bun": "^2.4.0", + "zod": "^4.4.3" + }, + "keywords": [ + "gameflow", + "sdk" + ] +} \ No newline at end of file diff --git a/scripts/sdk/sdk.tsconfig.json b/src/packages/gameflow-sdk/sdk.tsconfig.json similarity index 52% rename from scripts/sdk/sdk.tsconfig.json rename to src/packages/gameflow-sdk/sdk.tsconfig.json index 8da436e..707544a 100644 --- a/scripts/sdk/sdk.tsconfig.json +++ b/src/packages/gameflow-sdk/sdk.tsconfig.json @@ -17,26 +17,6 @@ "outDir": "../../dist-sdk", "types": [ "node" - ], - "paths": { - "@/*": [ - "../../src/*" - ], - "~/*": [ - "../../*" - ], - "@shared/*": [ - "../../src/shared/*" - ], - "@clients/*": [ - "../../src/clients/*" - ], - "@schema/*": [ - "../../src/bun/api/schema/*" - ], - "@queries/*": [ - "../../src/mainview/scripts/queries/*" - ] - } + ] } } \ No newline at end of file diff --git a/src/packages/gameflow-sdk/shared.ts b/src/packages/gameflow-sdk/shared.ts new file mode 100644 index 0000000..147db36 --- /dev/null +++ b/src/packages/gameflow-sdk/shared.ts @@ -0,0 +1,631 @@ +import * as z from "zod"; + +export const settingRegistry = z.registry<{ + dev?: boolean; +}>(); + +export const SettingsSchema = z.object({ + rommAddress: z.url().optional(), + rommUser: z.string().default('admin').optional(), + windowSize: z.object({ width: z.number(), height: z.number() }).optional(), + windowPosition: z.object({ x: z.number(), y: z.number() }).optional(), + downloadPath: z.string(), + launchInFullscreen: z.boolean().default(true), + disabledPlugins: z.array(z.string()).default([]), + emulatorResolution: z.enum(['720p', '1080p', '1440p', '4k']).default('720p'), + emulatorWidescreen: z.boolean().default(true) +}); export const LocalSettingsSchema = z.object({ + backgroundBlur: z.boolean().default(true).meta({ title: "Background Blur" }), + backgroundAnimation: z.boolean().default(true).meta({ title: "Background Animation" }), + theme: z.enum(['dark', 'light', 'auto']).default('auto').meta({ title: "Theme" }), + soundEffects: z.boolean().default(true).meta({ title: "Sounds" }), + soundEffectsVolume: z.number().min(0).max(100).default(50).meta({ title: "Sound Volume" }), + hapticsEffects: z.boolean().default(true).meta({ title: "Haptics" }), + showRouterDevOptions: z.boolean().default(false).meta({ title: "Show Router Options" }).register(settingRegistry, { dev: true }), + showQueryDevOptions: z.boolean().default(false).meta({ title: "Show Query Options" }).register(settingRegistry, { dev: true }), + useGameflowKeyboard: z.boolean().default(true).describe("Show the gameflow on screen keyboard when using a controller").meta({ title: "Use Gameflow Keyboard" }), + autoKeybaord: z.boolean().default(true).describe("Open on screen keybaord automatically").meta({ title: "Auto Keyboard" }) +}); +export const GameListFilterSchema = z.object({ + platform_source: z.string().optional(), + platform_slug: z.string().optional(), + platform_id: z.coerce.number().optional(), + collection_id: z.coerce.number().optional(), + collection_source: z.string().optional(), + limit: z.coerce.number().optional(), + search: z.string().optional(), + offset: z.coerce.number().optional(), + source: z.string().optional(), + localOnly: z.coerce.boolean().optional(), + orderBy: z.literal(['added', 'activity', 'name', 'release']).optional(), + age_ratings: z.union([z.string().array(), z.string().transform(v => [v])]).optional(), + genres: z.union([z.string().array(), z.string().transform(v => [v])]).optional(), + keywords: z.union([z.string().array(), z.string().transform(v => [v])]).optional(), +}); +export const DownloadSourceSchema = z.object({ + id: z.string(), + name: z.string() +}); +export const RommLoginDataSchema = z.object({ hostname: z.url(), username: z.string(), password: z.string() }); +export type GameListFilterType = z.infer; +export const DirSchema = z.object({ name: z.string(), parentPath: z.string(), isDirectory: z.boolean() }); +export type DirType = z.infer; +export const CustomEmulatorSchema = z.record(z.string(), z.string()); +export const GithubManifestSchema = z.object({ + sha: z.hash('sha1'), + url: z.url(), + tree: z.array(z.object({ + path: z.string(), + mode: z.string(), + type: z.enum(['blob', 'tree']), + sha: z.hash('sha1'), + url: z.url() + })) +}); +export const StoreGameSaveSchema = z.object({ + cwd: z.string(), + globs: z.string().array() +}); +export const StoreDownloadSchema = z.discriminatedUnion('type', [ + z.object({ + type: z.literal('direct'), + url: z.url(), + name: z.string().optional(), + system: z.string(), + main: z.string().optional(), + saves: z.record(z.string(), StoreGameSaveSchema).optional() + }), + z.object({ + type: z.literal("itch"), + path: z.string(), + name: z.string().optional(), + system: z.string(), + saves: z.record(z.string(), StoreGameSaveSchema).optional() + }) +]); +export const NewGameSchema = z.object({ + name: z.string(), + summary: z.string(), + genres: z.string().regex(/^$|^(\s*\S[^,]*)(\s*,\s*\S[^,]*)*\s*$/, { + message: "Must be a comma-separated list", + }) +}); +export const StoreGameSchema = z.object({ + name: z.string(), + description: z.string(), + version: z.string(), + homepage: z.string().optional(), + keywords: z.string().array().optional(), + genres: z.string().array().optional(), + companies: z.string().array().optional(), + screenshots: z.string().array().optional(), + covers: z.string().array().optional(), + igdb_id: z.number().optional(), + ra_id: z.number().optional(), + sgdb_id: z.number().optional(), + first_release_date: z.union([z.number(), z.date()]).optional(), + player_count: z.string().optional(), + saves: z.record(z.string(), z.record(z.string(), StoreGameSaveSchema)).optional(), + downloads: z.record(z.string(), StoreDownloadSchema) +}); +export const EmulatorPackageSchema = z.object({ + name: z.string(), + description: z.string(), + homepage: z.url(), + logo: z.url(), + type: z.enum(['emulator']), + os: z.array(z.enum(['darwin', 'linux', 'win32', 'android'])), + keywords: z.array(z.string()).optional(), + downloads: z.record(z.string(), z.array(z.discriminatedUnion('type', [ + z.object({ + type: z.literal(['github', 'gitlab']), + pattern: z.string(), + path: z.string(), + bin: z.string().optional() + }), + z.object({ + type: z.literal('direct'), + url: z.url(), + bin: z.string().optional() + }), + z.object({ + type: z.literal('scoop'), + url: z.url(), + bin: z.string().optional() + }) + ]))).optional(), + systems: z.array(z.string()), + bios: z.literal(["required", "optional"]).optional() +}); +export const ScoopPackageSchema = z.object({ + version: z.string(), + url: z.url().optional(), + description: z.string(), + bin: z.string().optional(), + architecture: z.record(z.string(), z.object({ + url: z.url(), + hash: z.string().optional(), + extract_dir: z.string().optional() + })).optional() +}); +export const SystemInfoSchema = z.object({ + battery: z.object({ + percent: z.number(), + isCharging: z.boolean(), + acConnected: z.boolean(), + hasBattery: z.boolean() + }), + wifiConnections: z.array(z.object({ signalLevel: z.number() })), + bluetoothDevices: z.array(z.object({ connected: z.boolean() })) +}); +export const GithubReleaseSchema = z.object({ + id: z.number(), + tag_name: z.string().optional(), + url: z.url(), + body: z.string(), + assets: z.array(z.object({ + name: z.string(), + browser_download_url: z.url(), + content_type: z.string().optional() + })) +}); +export const EmulatorDownloadInfoSchema = z.object({ + id: z.string(), + version: z.string().optional(), + url: z.url().optional(), + description: z.string().optional(), + downloadDate: z.coerce.date(), + type: z.string() +}); +export const PluginEntrySchema = z.object({ + downloads: z.object({ + monthly: z.number(), + weekly: z.number() + }), + searchScore: z.number(), + installed: z.boolean(), + update: z.object({ from: z.string() }).optional(), + package: z.object({ + name: z.string(), + keywords: z.string().array(), + version: z.string(), + description: z.string().optional(), + sanitized_name: z.string(), + license: z.string().optional(), + publisher: z.object({ + email: z.string(), + username: z.string(), + trustedPublisher: z.object({ + id: z.string(), + oidcConfigId: z.string() + }).optional() + }), + date: z.coerce.date(), + links: z.object({ + homepage: z.string().optional(), + repository: z.string().optional(), + bugs: z.string().optional(), + npm: z.url() + }) + }) +}); +export const PluginBunDetailsSchema = z.object({ + name: z.string(), + keywords: z.string().array(), + version: z.string(), + author: z.object({ name: z.string().optional() }).optional(), + license: z.string().optional(), + devDependencies: z.record(z.string(), z.string()).optional(), + dependencies: z.record(z.string(), z.string()).optional(), + maintainers: z.object({ name: z.string() }).array().optional(), + dist: z.object({ unpackedSize: z.number() }), + description: z.string().optional(), + _npmUser: z.object({ name: z.string() }).optional() +}); +export type EmulatorPackageType = z.infer; +export type StoreGameType = z.infer; +export type StoreDownloadType = z.infer; +export type SettingsType = z.infer; +export type LocalSettingsType = z.infer; +export const PlatformSchema = z.object({ slug: z.string() }); +export type SystemInfoType = z.infer; +export type EmulatorDownloadInfoType = z.infer; +export type DownloadSourceType = z.infer; +export type PluginEntryType = z.infer; +export type PluginBunDetailsType = z.infer; + +export interface SaveFileChange +{ + subPath: string | string[]; + isGlob?: true; + cwd: string; + shared: boolean; + fixedSize?: boolean; +} + +export type EmulatorSourceType = 'custom' | 'store' | 'registry' | 'system' | 'static' | 'embedded'; + +export interface EmulatorSourceEntryType +{ + binPath: string; + rootPath?: string; + type: EmulatorSourceType; + exists: boolean; +} + +export interface FrontEndEmulator +{ + name: string; + source: string; + logo: string; + systems: EmulatorSystem[]; + description?: string; + gameCount: number; + validSources: EmulatorSourceEntryType[]; + integrations: EmulatorSupport[]; +} + +export interface EmulatorSystem { id: string, romm_slug?: string, name: string, iconUrl: string; } + +export interface FrontEndEmulatorDetailedDownload +{ + name: string; + type: string | undefined; + version?: string; +} + +export interface FrontEndEmulatorDetailed extends FrontEndEmulator +{ + homepage: string; + description: string; + downloads: FrontEndEmulatorDetailedDownload[]; + keywords?: string[]; + screenshots: string[]; + biosRequirement?: "required" | "optional"; + bios?: string[]; + storeDownloadInfo?: { hasUpdate: boolean; version?: string, type: string; description?: string; }; +} + +export interface FrontEndGameTypeDetailedAchievement +{ + id: string; + title: string; + description?: string; + date?: Date; + date_hardcode?: Date; + badge_url?: string; + display_order: number; + type?: string; +} + +export interface FrontEndGameTypeDetailedEmulator extends FrontEndEmulator +{ + +} + +export interface FrontEndGameTypeDetailed extends Exclude +{ + summary: string | null; + fs_size_bytes: number | null; + missing: boolean; + local: boolean; + version?: string | null; + version_system?: string | null; + version_source?: string | null; + metadata: FrontEndGameMetadataDetailed, + emulators?: FrontEndGameTypeDetailedEmulator[], + achievements?: { + unlocked: number; + total: number; + entires: FrontEndGameTypeDetailedAchievement[]; + }; +}; + +export interface Drive +{ + parent: string | null; + device: string; + label: string; + mountPoint: string | null; + type: string; + size: number; + used: number; + isRemovable: boolean; + interfaceType: string | null; + hasWriteAccess: boolean; + hasReadAccess: boolean; +} + +export interface DownloadsDrive +{ + device: string; + label: string; + mountPoint: string | null; + isRemovable: boolean; + size: number; + used: number; + isCurrentlyUsed: boolean; + unusableReason: 'not_enough_space' | 'already_used' | null; +} + +export interface FrontendNotification +{ + title?: string; + message: string; + type: 'success' | 'error' | 'info' | 'custom'; + icon?: "save" | "upload" | "clock"; + duration?: number; +} + +export interface CommandEntry +{ + /** The ID of the command. Could be just an index or a string */ + id: string | number; + /** The front end label for the command. Mainly gotten from ES-DE list */ + label?: string; + /** Compiled command to be executed */ + command: string | string[]; + /** Environment variables */ + env?: Record, + /** The path the spawned process will start at */ + startDir?: string; + /** Is the command valid, for example does the executable exists */ + valid: boolean; + /** Run the command as shell. Defaults is true */ + shell?: boolean; + /** For what emulator is the command */ + emulator?: string; + /** Where the emulator came from */ + emulatorSource?: EmulatorSourceType; + /** Metadata for the command */ + metadata: { + romPath?: string; + emulatorBin?: string; + /** The root directory of the emulator */ + emulatorDir?: string; + }; +} + +export interface FrontEndId +{ + id: string; + source: string; +} + +// Stuff stored in the local sqlite metadata field +export interface LocalGameMetadata +{ + genres?: string[], + companies?: string[], + game_modes?: string[], + age_ratings?: string[]; + player_count?: string; + first_release_date?: number; + average_rating?: number; +} + +export interface FrontEndPlatformType +{ + id: FrontEndId; + slug: string; + name: string; + family_name?: string | null; + path_cover: string | null; + game_count: number; + updated_at: Date; + hasLocal: boolean; + paths_screenshots: string[]; +} + +export interface FrontEndGameTypeWithIds extends FrontEndGameType +{ + igdb_id: number | null; + ra_id: number | null; +} + +export interface FrontEndFilterSets +{ + age_ratings: Set, + player_counts: Set, + languages: Set, + companies: Set, + genres: Set; +} + +export interface FrontEndFilterLists +{ + age_ratings: string[], + player_counts: string[], + languages: string[], + companies: string[], + genres: string[]; +} + +export interface FrontEndGameMetadata +{ + first_release_date: Date | null; +} + +export interface FrontEndGameMetadataDetailed extends FrontEndGameMetadata +{ + genres: string[], + companies: string[], + game_modes: string[], + age_ratings: string[]; + player_count: string | null; + average_rating: number | null; +} + +export interface FrontEndGameType +{ + platform_display_name: string | null, + path_platform_cover: string | null; + id: FrontEndId, + source: string | null, + source_id: string | null, + path_fs: string | null, + path_covers: string[], + last_played: Date | null, + updated_at: Date, + metadata: FrontEndGameMetadata, + slug: string | null, + name: string | null, + platform_id: number | null, + platform_slug: string | null, + paths_screenshots: string[]; +}; + +export type GameStatusType = 'installed' | 'missing-emulator' | 'error' | 'install' | 'download' | 'extract' | 'playing' | 'queued'; + +export interface GameInstallProgress +{ + progress?: number; + status?: GameStatusType; + details?: string; + commands?: CommandEntry[]; + error?: any; +} + +export type JobStatus = 'completed' | 'error' | 'running' | 'queued' | 'aborted'; +export type GameInstallProgressEvent = 'refresh'; + +export interface FrontendPlugin +{ + name: string; + displayName?: string; + description?: string; + category: string; + enabled: boolean; + canDisable: boolean; + canUninstall: boolean; + source: PluginSourceType; + hasSettings: boolean; + version: string; + icon?: string; + update?: PluginUpdateCheck; +} + +export interface PluginUpdateCheck +{ + current: string; + new: string; +} + +export type PluginSourceType = "builtin" | "store"; + +export type KeysWithValueAssignableTo = { + [K in keyof T]: Exclude extends Value ? K : never; +}[keyof T]; + +export interface DownloadInfo +{ + id: string; + screenshotUrls: string[]; + coverUrl: string; + platform?: DownloadPlatform; + slug?: string; + path_fs?: string; + main_glob?: string; + summary?: string; + name: string; + last_played?: Date; + igdb_id?: number; + ra_id?: number; + source_id: string; + system_slug: string; + extract_path?: string; + metadata?: any; + files: DownloadFileEntry[]; + auth?: string; + version?: string; + version_source?: string; + version_system?: string; +} + +export interface DownloadPlatform +{ + id: string; + source: string; + igdb_id?: number; + igdb_slug?: string; + ra_id?: number; + moby_id?: number; + slug: string; + name: string; + /** Like Sony or Nintendo */ + family_name?: string; +} + +export interface DownloadFileEntry +{ + url: URL; + /** The path of the file, excluding the name */ + file_path: string; + /** Just the name of the file including the extension */ + file_name: string; + /** Checksum of the file */ + sha1?: string; + /** Size in bytes */ + size?: number; +} + +export interface LocalDownloadFileEntry extends DownloadFileEntry +{ + /** Exists on the file system */ + exists: boolean; + /** Matches the checksum */ + matches: boolean; +} + +export interface FrontEndCollection +{ + id: FrontEndId; + name: string; + description: string; + path_platform_cover: string | null; + game_count: number; +} + +export type EmulatorCapabilities = "saves" | "fullscreen" | "resolution" | "batch" | "states" | "config"; + +export interface EmulatorSupport +{ + id: string; + source?: EmulatorSourceEntryType; + supportLevel?: "partial" | "full"; + capabilities?: EmulatorCapabilities[]; +} + +export interface GameLookup +{ + source: string; + id: string; + coverUrl: string | null | undefined; + slug: string | null | undefined; + screenshotUrls: string[]; + name: string; + summary: string | null | undefined; + genres: string[]; + companies: string[]; + game_modes: string[]; + age_ratings: string[]; + player_count: string | undefined; + first_release_date: number | undefined; + average_rating: number | undefined; + keywords: string[]; + igdb_id: number | undefined; + platforms: { + id: number; + name?: string | null; + displayName: string; + slug: string; + }[]; +} + +export interface AutoSaveChange +{ + subPath: string; + cwd: string; +} + +export type SaveSlots = Record; diff --git a/src/bun/api/task-queue.ts b/src/packages/gameflow-sdk/task-queue.ts similarity index 99% rename from src/bun/api/task-queue.ts rename to src/packages/gameflow-sdk/task-queue.ts index 97e783d..b86aab6 100644 --- a/src/bun/api/task-queue.ts +++ b/src/packages/gameflow-sdk/task-queue.ts @@ -1,6 +1,7 @@ -import { JobStatus } from '@/shared/types'; + import EventEmitter from 'node:events'; import z from 'zod'; +import { JobStatus } from './shared'; export class TaskQueue { diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 5eb5fdf..3c9d776 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -1,5 +1,3 @@ -import * as z from 'zod'; - export const LOGIN_PORT = 5196; export const OAUTH_REDIRECT_PORT = 5194; export const SERVER_PORT = 5173; @@ -10,211 +8,6 @@ export const RPC_PORT = 8787; export const RPC_URL = (host: string) => `http://${host}:${RPC_PORT}`; export const EMULATORJS_URL = (host: string) => `http://${host}:${EMULATORJS_PORT}`; export const SOCKETS_URL = (host: string) => `ws://${host}:${RPC_PORT}`; -export const settingRegistry = z.registry<{ - dev?: boolean; -}>(); export const DefaultRommStaleTime = 60 * 1000; // A minute - -export const SettingsSchema = z.object({ - rommAddress: z.url().optional(), - rommUser: z.string().default('admin').optional(), - windowSize: z.object({ width: z.number(), height: z.number() }).optional(), - windowPosition: z.object({ x: z.number(), y: z.number() }).optional(), - downloadPath: z.string(), - launchInFullscreen: z.boolean().default(true), - disabledPlugins: z.array(z.string()).default([]), - emulatorResolution: z.enum(['720p', '1080p', '1440p', '4k']).default('720p'), - emulatorWidescreen: z.boolean().default(true) -}); - -export const LocalSettingsSchema = z.object({ - backgroundBlur: z.boolean().default(true).meta({ title: "Background Blur" }), - backgroundAnimation: z.boolean().default(true).meta({ title: "Background Animation" }), - theme: z.enum(['dark', 'light', 'auto']).default('auto').meta({ title: "Theme" }), - soundEffects: z.boolean().default(true).meta({ title: "Sounds" }), - soundEffectsVolume: z.number().min(0).max(100).default(50).meta({ title: "Sound Volume" }), - hapticsEffects: z.boolean().default(true).meta({ title: "Haptics" }), - showRouterDevOptions: z.boolean().default(false).meta({ title: "Show Router Options" }).register(settingRegistry, { dev: true }), - showQueryDevOptions: z.boolean().default(false).meta({ title: "Show Query Options" }).register(settingRegistry, { dev: true }), - useGameflowKeyboard: z.boolean().default(true).describe("Show the gameflow on screen keyboard when using a controller").meta({ title: "Use Gameflow Keyboard" }), - autoKeybaord: z.boolean().default(true).describe("Open on screen keybaord automatically").meta({ title: "Auto Keyboard" }) -}); - -export const GameListFilterSchema = z.object({ - platform_source: z.string().optional(), - platform_slug: z.string().optional(), - platform_id: z.coerce.number().optional(), - collection_id: z.coerce.number().optional(), - collection_source: z.string().optional(), - limit: z.coerce.number().optional(), - search: z.string().optional(), - offset: z.coerce.number().optional(), - source: z.string().optional(), - localOnly: z.coerce.boolean().optional(), - orderBy: z.literal(['added', 'activity', 'name', 'release']).optional(), - age_ratings: z.union([z.string().array(), z.string().transform(v => [v])]).optional(), - genres: z.union([z.string().array(), z.string().transform(v => [v])]).optional(), - keywords: z.union([z.string().array(), z.string().transform(v => [v])]).optional(), -}); - -export const DownloadSourceSchema = z.object({ - id: z.string(), - name: z.string() -}); - -export const RommLoginDataSchema = z.object({ hostname: z.url(), username: z.string(), password: z.string() }); - -export type GameListFilterType = z.infer; - -export const DirSchema = z.object({ name: z.string(), parentPath: z.string(), isDirectory: z.boolean() }); -export type DirType = z.infer; - -export const CustomEmulatorSchema = z.record(z.string(), z.string()); - -export const GithubManifestSchema = z.object({ - sha: z.hash('sha1'), - url: z.url(), - tree: z.array(z.object({ - path: z.string(), - mode: z.string(), - type: z.enum(['blob', 'tree']), - sha: z.hash('sha1'), - url: z.url() - })) -}); - -export const StoreGameSaveSchema = z.object({ - cwd: z.string(), - globs: z.string().array() -}); - -export const StoreDownloadSchema = z.discriminatedUnion('type', [ - z.object({ - type: z.literal('direct'), - url: z.url(), - name: z.string().optional(), - system: z.string(), - main: z.string().optional(), - saves: z.record(z.string(), StoreGameSaveSchema).optional() - }), - z.object({ - type: z.literal("itch"), - path: z.string(), - name: z.string().optional(), - system: z.string(), - saves: z.record(z.string(), StoreGameSaveSchema).optional() - }) -]); - -export const NewGameSchema = z.object({ - name: z.string(), - summary: z.string(), - genres: z.string().regex(/^$|^(\s*\S[^,]*)(\s*,\s*\S[^,]*)*\s*$/, { - message: "Must be a comma-separated list", - }) -}); - -export const StoreGameSchema = z.object({ - name: z.string(), - description: z.string(), - version: z.string(), - homepage: z.string().optional(), - keywords: z.string().array().optional(), - genres: z.string().array().optional(), - companies: z.string().array().optional(), - screenshots: z.string().array().optional(), - covers: z.string().array().optional(), - igdb_id: z.number().optional(), - ra_id: z.number().optional(), - sgdb_id: z.number().optional(), - first_release_date: z.union([z.number(), z.date()]).optional(), - player_count: z.string().optional(), - saves: z.record(z.string(), z.record(z.string(), StoreGameSaveSchema)).optional(), - downloads: z.record(z.string(), StoreDownloadSchema) -}); - -export const EmulatorPackageSchema = z.object({ - name: z.string(), - description: z.string(), - homepage: z.url(), - logo: z.url(), - type: z.enum(['emulator']), - os: z.array(z.enum(['darwin', 'linux', 'win32', 'android'])), - keywords: z.array(z.string()).optional(), - downloads: z.record(z.string(), z.array(z.discriminatedUnion('type', [ - z.object({ - type: z.literal(['github', 'gitlab']), - pattern: z.string(), - path: z.string(), - bin: z.string().optional() - }), - z.object({ - type: z.literal('direct'), - url: z.url(), - bin: z.string().optional() - }), - z.object({ - type: z.literal('scoop'), - url: z.url(), - bin: z.string().optional() - }) - ]))).optional(), - systems: z.array(z.string()), - bios: z.literal(["required", "optional"]).optional() -}); - -export const ScoopPackageSchema = z.object({ - version: z.string(), - url: z.url().optional(), - description: z.string(), - bin: z.string().optional(), - architecture: z.record(z.string(), z.object({ - url: z.url(), - hash: z.string().optional(), - extract_dir: z.string().optional() - })).optional() -}); - -export const SystemInfoSchema = z.object({ - battery: z.object({ - percent: z.number(), - isCharging: z.boolean(), - acConnected: z.boolean(), - hasBattery: z.boolean() - - }), - wifiConnections: z.array(z.object({ signalLevel: z.number() })), - bluetoothDevices: z.array(z.object({ connected: z.boolean() })) -}); - -export const GithubReleaseSchema = z.object({ - id: z.number(), - tag_name: z.string().optional(), - url: z.url(), - body: z.string(), - assets: z.array(z.object({ - name: z.string(), - browser_download_url: z.url(), - content_type: z.string().optional() - })) -}); - -export const EmulatorDownloadInfoSchema = z.object({ - id: z.string(), - version: z.string().optional(), - url: z.url().optional(), - description: z.string().optional(), - downloadDate: z.coerce.date(), - type: z.string() -}); - -export type EmulatorPackageType = z.infer; -export type StoreGameType = z.infer; -export type StoreDownloadType = z.infer; -export type SettingsType = z.infer; -export type LocalSettingsType = z.infer; -export const PlatformSchema = z.object({ slug: z.string() }); -export type SystemInfoType = z.infer; -export type EmulatorDownloadInfoType = z.infer; -export type DownloadSourceType = z.infer; +export const PluginRegistry = process.env.STORE_REGISTRY ?? "https://registry.npmjs.org"; \ No newline at end of file diff --git a/src/shared/types.schema.ts b/src/shared/types.schema.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/types.ts b/src/shared/types.ts index dc72a7a..e69de29 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,387 +0,0 @@ -export interface SaveFileChange -{ - subPath: string | string[]; - isGlob?: true; - cwd: string; - shared: boolean; - fixedSize?: boolean; -} - -export type EmulatorSourceType = 'custom' | 'store' | 'registry' | 'system' | 'static' | 'embedded'; - -export interface EmulatorSourceEntryType -{ - binPath: string; - rootPath?: string; - type: EmulatorSourceType; - exists: boolean; -} - -export interface FrontEndEmulator -{ - name: string; - source: string; - logo: string; - systems: EmulatorSystem[]; - description?: string; - gameCount: number; - validSources: EmulatorSourceEntryType[]; - integrations: EmulatorSupport[]; -} - -export interface EmulatorSystem { id: string, romm_slug?: string, name: string, iconUrl: string; } - -export interface FrontEndEmulatorDetailedDownload -{ - name: string; - type: string | undefined; - version?: string; -} - -export interface FrontEndEmulatorDetailed extends FrontEndEmulator -{ - homepage: string; - description: string; - downloads: FrontEndEmulatorDetailedDownload[]; - keywords?: string[]; - screenshots: string[]; - biosRequirement?: "required" | "optional"; - bios?: string[]; - storeDownloadInfo?: { hasUpdate: boolean; version?: string, type: string; description?: string; }; -} - -export interface FrontEndGameTypeDetailedAchievement -{ - id: string; - title: string; - description?: string; - date?: Date; - date_hardcode?: Date; - badge_url?: string; - display_order: number; - type?: string; -} - -export interface FrontEndGameTypeDetailedEmulator extends FrontEndEmulator -{ - -} - -export interface FrontEndGameTypeDetailed extends Exclude -{ - summary: string | null; - fs_size_bytes: number | null; - missing: boolean; - local: boolean; - version?: string | null; - version_system?: string | null; - version_source?: string | null; - metadata: FrontEndGameMetadataDetailed, - emulators?: FrontEndGameTypeDetailedEmulator[], - achievements?: { - unlocked: number; - total: number; - entires: FrontEndGameTypeDetailedAchievement[]; - }; -}; - -export interface Drive -{ - parent: string | null; - device: string; - label: string; - mountPoint: string | null; - type: string; - size: number; - used: number; - isRemovable: boolean; - interfaceType: string | null; - hasWriteAccess: boolean; - hasReadAccess: boolean; -} - -export interface DownloadsDrive -{ - device: string; - label: string; - mountPoint: string | null; - isRemovable: boolean; - size: number; - used: number; - isCurrentlyUsed: boolean; - unusableReason: 'not_enough_space' | 'already_used' | null; -} - -export interface FrontendNotification -{ - title?: string; - message: string; - type: 'success' | 'error' | 'info' | 'custom'; - icon?: "save" | "upload" | "clock"; - duration?: number; -} - -export interface CommandEntry -{ - /** The ID of the command. Could be just an index or a string */ - id: string | number; - /** The front end label for the command. Mainly gotten from ES-DE list */ - label?: string; - /** Compiled command to be executed */ - command: string | string[]; - /** Environment variables */ - env?: Record, - /** The path the spawned process will start at */ - startDir?: string; - /** Is the command valid, for example does the executable exists */ - valid: boolean; - /** Run the command as shell. Defaults is true */ - shell?: boolean; - /** For what emulator is the command */ - emulator?: string; - /** Where the emulator came from */ - emulatorSource?: EmulatorSourceType; - /** Metadata for the command */ - metadata: { - romPath?: string; - emulatorBin?: string; - /** The root directory of the emulator */ - emulatorDir?: string; - }; -} - -export interface FrontEndId -{ - id: string; - source: string; -} - -// Stuff stored in the local sqlite metadata field -export interface LocalGameMetadata -{ - genres?: string[], - companies?: string[], - game_modes?: string[], - age_ratings?: string[]; - player_count?: string; - first_release_date?: number; - average_rating?: number; -} - -export interface FrontEndPlatformType -{ - id: FrontEndId; - slug: string; - name: string; - family_name?: string | null; - path_cover: string | null; - game_count: number; - updated_at: Date; - hasLocal: boolean; - paths_screenshots: string[]; -} - -export interface FrontEndGameTypeWithIds extends FrontEndGameType -{ - igdb_id: number | null; - ra_id: number | null; -} - -export interface FrontEndFilterSets -{ - age_ratings: Set, - player_counts: Set, - languages: Set, - companies: Set, - genres: Set; -} - -export interface FrontEndFilterLists -{ - age_ratings: string[], - player_counts: string[], - languages: string[], - companies: string[], - genres: string[]; -} - -export interface FrontEndGameMetadata -{ - first_release_date: Date | null; -} - -export interface FrontEndGameMetadataDetailed extends FrontEndGameMetadata -{ - genres: string[], - companies: string[], - game_modes: string[], - age_ratings: string[]; - player_count: string | null; - average_rating: number | null; -} - -export interface FrontEndGameType -{ - platform_display_name: string | null, - path_platform_cover: string | null; - id: FrontEndId, - source: string | null, - source_id: string | null, - path_fs: string | null, - path_covers: string[], - last_played: Date | null, - updated_at: Date, - metadata: FrontEndGameMetadata, - slug: string | null, - name: string | null, - platform_id: number | null, - platform_slug: string | null, - paths_screenshots: string[]; -}; - -export type GameStatusType = 'installed' | 'missing-emulator' | 'error' | 'install' | 'download' | 'extract' | 'playing' | 'queued'; - -export interface GameInstallProgress -{ - progress?: number; - status?: GameStatusType; - details?: string; - commands?: CommandEntry[]; - error?: any; -} - -export type JobStatus = 'completed' | 'error' | 'running' | 'queued' | 'aborted'; -export type GameInstallProgressEvent = 'refresh'; - -export interface FrontendPlugin -{ - name: string; - displayName: string; - description: string; - category: string; - enabled: boolean; - canDisable: boolean; - source: PluginSourceType; - hasSettings: boolean; - version: string; - icon?: string; -} - -export type PluginSourceType = "builtin"; - -export type KeysWithValueAssignableTo = { - [K in keyof T]: Exclude extends Value ? K : never; -}[keyof T]; - -export interface DownloadInfo -{ - id: string; - screenshotUrls: string[]; - coverUrl: string; - platform?: DownloadPlatform; - slug?: string; - path_fs?: string; - main_glob?: string; - summary?: string; - name: string; - last_played?: Date; - igdb_id?: number; - ra_id?: number; - source_id: string; - system_slug: string; - extract_path?: string; - metadata?: any; - files: DownloadFileEntry[]; - auth?: string; - version?: string; - version_source?: string; - version_system?: string; -} - -export interface DownloadPlatform -{ - id: string; - source: string; - igdb_id?: number; - igdb_slug?: string; - ra_id?: number; - moby_id?: number; - slug: string; - name: string; - /** Like Sony or Nintendo */ - family_name?: string; -} - -export interface DownloadFileEntry -{ - url: URL; - /** The path of the file, excluding the name */ - file_path: string; - /** Just the name of the file including the extension */ - file_name: string; - /** Checksum of the file */ - sha1?: string; - /** Size in bytes */ - size?: number; -} - -export interface LocalDownloadFileEntry extends DownloadFileEntry -{ - /** Exists on the file system */ - exists: boolean; - /** Matches the checksum */ - matches: boolean; -} - -export interface FrontEndCollection -{ - id: FrontEndId; - name: string; - description: string; - path_platform_cover: string | null; - game_count: number; -} - -export type EmulatorCapabilities = "saves" | "fullscreen" | "resolution" | "batch" | "states" | "config"; - -export interface EmulatorSupport -{ - id: string; - source?: EmulatorSourceEntryType; - supportLevel?: "partial" | "full"; - capabilities?: EmulatorCapabilities[]; -} - -export interface GameLookup -{ - source: string; - id: string; - coverUrl: string | null | undefined; - slug: string | null | undefined; - screenshotUrls: string[]; - name: string; - summary: string | null | undefined; - genres: string[]; - companies: string[]; - game_modes: string[]; - age_ratings: string[]; - player_count: string | undefined; - first_release_date: number | undefined; - average_rating: number | undefined; - keywords: string[]; - igdb_id: number | undefined; - platforms: { - id: number; - name?: string | null; - displayName: string; - slug: string; - }[]; -} - -export interface AutoSaveChange -{ - subPath: string; - cwd: string; -} - -export type SaveSlots = Record; \ No newline at end of file diff --git a/src/tests/downloads.test.ts b/src/tests/downloads.test.ts index 6a58e55..7be5dd0 100644 --- a/src/tests/downloads.test.ts +++ b/src/tests/downloads.test.ts @@ -4,7 +4,7 @@ import * as app from '@/bun/api/app'; import fs from 'node:fs/promises'; import path from "node:path"; import AdmZip from "adm-zip"; -import { DownloadInfo } from '@/shared/types'; +import { DownloadInfo } from '@simeonradivoev/gameflow-sdk/shared'; describe("Download Tests", () => { diff --git a/tsconfig.json b/tsconfig.json index 7fb79f1..500e8f7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "noUnusedLocals": true, "noFallthroughCasesInSwitch": true, "paths": { + "@simeonradivoev/gameflow-sdk/*": ["./src/packages/gameflow-sdk/*"], "@/*": [ "./src/*" ], From 2e78ddf08e633b2d0897deff0f0b5e0bde084dd0 Mon Sep 17 00:00:00 2001 From: Simeon Radivoev Date: Sun, 10 May 2026 02:51:49 +0300 Subject: [PATCH 4/7] refactor: moved to commit-and-tag-version --- .versionrc | 18 ++++++++ bun.lock | 126 +++++++++++++++++++++++++++------------------------ package.json | 2 +- 3 files changed, 86 insertions(+), 60 deletions(-) create mode 100644 .versionrc diff --git a/.versionrc b/.versionrc new file mode 100644 index 0000000..fa5c461 --- /dev/null +++ b/.versionrc @@ -0,0 +1,18 @@ +{ + "packageFiles": [ + { + "filename": "package.json", + "type": "json" + } + ], + "bumpFiles": [ + { + "filename": "package.json", + "type": "json" + }, + { + "filename": "src/packages/gameflow-sdk/package.json", + "type": "json" + } + ] +} \ No newline at end of file diff --git a/bun.lock b/bun.lock index cd65d27..c4a2b79 100644 --- a/bun.lock +++ b/bun.lock @@ -75,6 +75,7 @@ "audiosprite": "^0.7.2", "babel-plugin-react-compiler": "^1.0.0", "classnames": "^2.5.1", + "commit-and-tag-version": "^12.7.3", "concurrently": "^9.2.1", "cross-env": "^10.1.0", "daisyui": "^5.5.19", @@ -91,7 +92,6 @@ "react-markdown": "^10.1.0", "react-qr-code": "^2.0.21", "sass-embedded": "^1.99.0", - "standard-version": "^9.5.0", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.4", "tailwindcss-animate": "^1.0.7", @@ -730,7 +730,7 @@ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], @@ -810,7 +810,7 @@ "chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], @@ -836,9 +836,9 @@ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + "color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="], @@ -852,6 +852,8 @@ "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "commit-and-tag-version": ["commit-and-tag-version@12.7.3", "", { "dependencies": { "chalk": "^2.4.2", "conventional-changelog": "4.0.0", "conventional-changelog-config-spec": "2.1.0", "conventional-changelog-conventionalcommits": "6.1.0", "conventional-recommended-bump": "7.0.1", "detect-indent": "^6.1.0", "detect-newline": "^3.1.0", "dotgitignore": "^2.1.0", "fast-xml-parser": "^5.5.6", "figures": "^3.2.0", "find-up": "^5.0.0", "git-semver-tags": "^5.0.1", "semver": "^7.7.2", "yaml": "^2.6.0", "yargs": "^17.7.2" }, "bin": { "commit-and-tag-version": "bin/cli.js" } }, "sha512-rbauuCDU98yEHMy/LrNNu8HLTuGv7C2kN/3GXC59L18aJGii0eiryCESb1SEHXNFem2/2ngWG/Pq6qaCqw3aCw=="], + "compare-func": ["compare-func@2.0.0", "", { "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], @@ -866,39 +868,39 @@ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - "conventional-changelog": ["conventional-changelog@3.1.25", "", { "dependencies": { "conventional-changelog-angular": "^5.0.12", "conventional-changelog-atom": "^2.0.8", "conventional-changelog-codemirror": "^2.0.8", "conventional-changelog-conventionalcommits": "^4.5.0", "conventional-changelog-core": "^4.2.1", "conventional-changelog-ember": "^2.0.9", "conventional-changelog-eslint": "^3.0.9", "conventional-changelog-express": "^2.0.6", "conventional-changelog-jquery": "^3.0.11", "conventional-changelog-jshint": "^2.0.9", "conventional-changelog-preset-loader": "^2.3.4" } }, "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ=="], + "conventional-changelog": ["conventional-changelog@4.0.0", "", { "dependencies": { "conventional-changelog-angular": "^6.0.0", "conventional-changelog-atom": "^3.0.0", "conventional-changelog-codemirror": "^3.0.0", "conventional-changelog-conventionalcommits": "^6.0.0", "conventional-changelog-core": "^5.0.0", "conventional-changelog-ember": "^3.0.0", "conventional-changelog-eslint": "^4.0.0", "conventional-changelog-express": "^3.0.0", "conventional-changelog-jquery": "^4.0.0", "conventional-changelog-jshint": "^3.0.0", "conventional-changelog-preset-loader": "^3.0.0" } }, "sha512-JbZjwE1PzxQCvm+HUTIr+pbSekS8qdOZzMakdFyPtdkEWwFvwEJYONzjgMm0txCb2yBcIcfKDmg8xtCKTdecNQ=="], - "conventional-changelog-angular": ["conventional-changelog-angular@5.0.13", "", { "dependencies": { "compare-func": "^2.0.0", "q": "^1.5.1" } }, "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA=="], + "conventional-changelog-angular": ["conventional-changelog-angular@6.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg=="], - "conventional-changelog-atom": ["conventional-changelog-atom@2.0.8", "", { "dependencies": { "q": "^1.5.1" } }, "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw=="], + "conventional-changelog-atom": ["conventional-changelog-atom@3.0.0", "", {}, "sha512-pnN5bWpH+iTUWU3FaYdw5lJmfWeqSyrUkG+wyHBI9tC1dLNnHkbAOg1SzTQ7zBqiFrfo55h40VsGXWMdopwc5g=="], - "conventional-changelog-codemirror": ["conventional-changelog-codemirror@2.0.8", "", { "dependencies": { "q": "^1.5.1" } }, "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw=="], + "conventional-changelog-codemirror": ["conventional-changelog-codemirror@3.0.0", "", {}, "sha512-wzchZt9HEaAZrenZAUUHMCFcuYzGoZ1wG/kTRMICxsnW5AXohYMRxnyecP9ob42Gvn5TilhC0q66AtTPRSNMfw=="], "conventional-changelog-config-spec": ["conventional-changelog-config-spec@2.1.0", "", {}, "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ=="], - "conventional-changelog-conventionalcommits": ["conventional-changelog-conventionalcommits@4.6.3", "", { "dependencies": { "compare-func": "^2.0.0", "lodash": "^4.17.15", "q": "^1.5.1" } }, "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g=="], + "conventional-changelog-conventionalcommits": ["conventional-changelog-conventionalcommits@6.1.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw=="], - "conventional-changelog-core": ["conventional-changelog-core@4.2.4", "", { "dependencies": { "add-stream": "^1.0.0", "conventional-changelog-writer": "^5.0.0", "conventional-commits-parser": "^3.2.0", "dateformat": "^3.0.0", "get-pkg-repo": "^4.0.0", "git-raw-commits": "^2.0.8", "git-remote-origin-url": "^2.0.0", "git-semver-tags": "^4.1.1", "lodash": "^4.17.15", "normalize-package-data": "^3.0.0", "q": "^1.5.1", "read-pkg": "^3.0.0", "read-pkg-up": "^3.0.0", "through2": "^4.0.0" } }, "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg=="], + "conventional-changelog-core": ["conventional-changelog-core@5.0.2", "", { "dependencies": { "add-stream": "^1.0.0", "conventional-changelog-writer": "^6.0.0", "conventional-commits-parser": "^4.0.0", "dateformat": "^3.0.3", "get-pkg-repo": "^4.2.1", "git-raw-commits": "^3.0.0", "git-remote-origin-url": "^2.0.0", "git-semver-tags": "^5.0.0", "normalize-package-data": "^3.0.3", "read-pkg": "^3.0.0", "read-pkg-up": "^3.0.0" } }, "sha512-RhQOcDweXNWvlRwUDCpaqXzbZemKPKncCWZG50Alth72WITVd6nhVk9MJ6w1k9PFNBcZ3YwkdkChE+8+ZwtUug=="], - "conventional-changelog-ember": ["conventional-changelog-ember@2.0.9", "", { "dependencies": { "q": "^1.5.1" } }, "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A=="], + "conventional-changelog-ember": ["conventional-changelog-ember@3.0.0", "", {}, "sha512-7PYthCoSxIS98vWhVcSphMYM322OxptpKAuHYdVspryI0ooLDehRXWeRWgN+zWSBXKl/pwdgAg8IpLNSM1/61A=="], - "conventional-changelog-eslint": ["conventional-changelog-eslint@3.0.9", "", { "dependencies": { "q": "^1.5.1" } }, "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA=="], + "conventional-changelog-eslint": ["conventional-changelog-eslint@4.0.0", "", {}, "sha512-nEZ9byP89hIU0dMx37JXQkE1IpMmqKtsaR24X7aM3L6Yy/uAtbb+ogqthuNYJkeO1HyvK7JsX84z8649hvp43Q=="], - "conventional-changelog-express": ["conventional-changelog-express@2.0.6", "", { "dependencies": { "q": "^1.5.1" } }, "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ=="], + "conventional-changelog-express": ["conventional-changelog-express@3.0.0", "", {}, "sha512-HqxihpUMfIuxvlPvC6HltA4ZktQEUan/v3XQ77+/zbu8No/fqK3rxSZaYeHYant7zRxQNIIli7S+qLS9tX9zQA=="], - "conventional-changelog-jquery": ["conventional-changelog-jquery@3.0.11", "", { "dependencies": { "q": "^1.5.1" } }, "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw=="], + "conventional-changelog-jquery": ["conventional-changelog-jquery@4.0.0", "", {}, "sha512-TTIN5CyzRMf8PUwyy4IOLmLV2DFmPtasKN+x7EQKzwSX8086XYwo+NeaeA3VUT8bvKaIy5z/JoWUvi7huUOgaw=="], - "conventional-changelog-jshint": ["conventional-changelog-jshint@2.0.9", "", { "dependencies": { "compare-func": "^2.0.0", "q": "^1.5.1" } }, "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA=="], + "conventional-changelog-jshint": ["conventional-changelog-jshint@3.0.0", "", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-bQof4byF4q+n+dwFRkJ/jGf9dCNUv4/kCDcjeCizBvfF81TeimPZBB6fT4HYbXgxxfxWXNl/i+J6T0nI4by6DA=="], - "conventional-changelog-preset-loader": ["conventional-changelog-preset-loader@2.3.4", "", {}, "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g=="], + "conventional-changelog-preset-loader": ["conventional-changelog-preset-loader@3.0.0", "", {}, "sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA=="], - "conventional-changelog-writer": ["conventional-changelog-writer@5.0.1", "", { "dependencies": { "conventional-commits-filter": "^2.0.7", "dateformat": "^3.0.0", "handlebars": "^4.7.7", "json-stringify-safe": "^5.0.1", "lodash": "^4.17.15", "meow": "^8.0.0", "semver": "^6.0.0", "split": "^1.0.0", "through2": "^4.0.0" }, "bin": { "conventional-changelog-writer": "cli.js" } }, "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ=="], + "conventional-changelog-writer": ["conventional-changelog-writer@6.0.1", "", { "dependencies": { "conventional-commits-filter": "^3.0.0", "dateformat": "^3.0.3", "handlebars": "^4.7.7", "json-stringify-safe": "^5.0.1", "meow": "^8.1.2", "semver": "^7.0.0", "split": "^1.0.1" }, "bin": { "conventional-changelog-writer": "cli.js" } }, "sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ=="], - "conventional-commits-filter": ["conventional-commits-filter@2.0.7", "", { "dependencies": { "lodash.ismatch": "^4.4.0", "modify-values": "^1.0.0" } }, "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA=="], + "conventional-commits-filter": ["conventional-commits-filter@3.0.0", "", { "dependencies": { "lodash.ismatch": "^4.4.0", "modify-values": "^1.0.1" } }, "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q=="], - "conventional-commits-parser": ["conventional-commits-parser@3.2.4", "", { "dependencies": { "JSONStream": "^1.0.4", "is-text-path": "^1.0.1", "lodash": "^4.17.15", "meow": "^8.0.0", "split2": "^3.0.0", "through2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.js" } }, "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q=="], + "conventional-commits-parser": ["conventional-commits-parser@4.0.0", "", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^1.0.1", "meow": "^8.1.2", "split2": "^3.2.2" }, "bin": { "conventional-commits-parser": "cli.js" } }, "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg=="], - "conventional-recommended-bump": ["conventional-recommended-bump@6.1.0", "", { "dependencies": { "concat-stream": "^2.0.0", "conventional-changelog-preset-loader": "^2.3.4", "conventional-commits-filter": "^2.0.7", "conventional-commits-parser": "^3.2.0", "git-raw-commits": "^2.0.8", "git-semver-tags": "^4.1.1", "meow": "^8.0.0", "q": "^1.5.1" }, "bin": { "conventional-recommended-bump": "cli.js" } }, "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw=="], + "conventional-recommended-bump": ["conventional-recommended-bump@7.0.1", "", { "dependencies": { "concat-stream": "^2.0.0", "conventional-changelog-preset-loader": "^3.0.0", "conventional-commits-filter": "^3.0.0", "conventional-commits-parser": "^4.0.0", "git-raw-commits": "^3.0.0", "git-semver-tags": "^5.0.0", "meow": "^8.1.2" }, "bin": { "conventional-recommended-bump": "cli.js" } }, "sha512-Ft79FF4SlOFvX4PkwFDRnaNiIVX7YbmqGU0RwccUaiGvgp3S0a8ipR2/Qxk31vclDNM+GSdJOVs2KrsUCjblVA=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], @@ -1100,11 +1102,11 @@ "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], - "git-raw-commits": ["git-raw-commits@2.0.11", "", { "dependencies": { "dargs": "^7.0.0", "lodash": "^4.17.15", "meow": "^8.0.0", "split2": "^3.0.0", "through2": "^4.0.0" }, "bin": { "git-raw-commits": "cli.js" } }, "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A=="], + "git-raw-commits": ["git-raw-commits@3.0.0", "", { "dependencies": { "dargs": "^7.0.0", "meow": "^8.1.2", "split2": "^3.2.2" }, "bin": { "git-raw-commits": "cli.js" } }, "sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw=="], "git-remote-origin-url": ["git-remote-origin-url@2.0.0", "", { "dependencies": { "gitconfiglocal": "^1.0.0", "pify": "^2.3.0" } }, "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw=="], - "git-semver-tags": ["git-semver-tags@4.1.1", "", { "dependencies": { "meow": "^8.0.0", "semver": "^6.0.0" }, "bin": { "git-semver-tags": "cli.js" } }, "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA=="], + "git-semver-tags": ["git-semver-tags@5.0.1", "", { "dependencies": { "meow": "^8.1.2", "semver": "^7.0.0" }, "bin": { "git-semver-tags": "cli.js" } }, "sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA=="], "gitconfiglocal": ["gitconfiglocal@1.0.0", "", { "dependencies": { "ini": "^1.3.2" } }, "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ=="], @@ -1122,7 +1124,7 @@ "gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="], - "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], + "handlebars": ["handlebars@4.7.9", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ=="], "hard-rejection": ["hard-rejection@2.1.0", "", {}, "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA=="], @@ -1180,7 +1182,7 @@ "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], @@ -1550,8 +1552,6 @@ "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], - "q": ["q@1.5.1", "", {}, "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw=="], - "qr.js": ["qr.js@0.0.0", "", {}, "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="], "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], @@ -1596,7 +1596,7 @@ "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], - "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], @@ -1712,8 +1712,6 @@ "stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="], - "standard-version": ["standard-version@9.5.0", "", { "dependencies": { "chalk": "^2.4.2", "conventional-changelog": "3.1.25", "conventional-changelog-config-spec": "2.1.0", "conventional-changelog-conventionalcommits": "4.6.3", "conventional-recommended-bump": "6.1.0", "detect-indent": "^6.0.0", "detect-newline": "^3.1.0", "dotgitignore": "^2.1.0", "figures": "^3.1.0", "find-up": "^5.0.0", "git-semver-tags": "^4.0.0", "semver": "^7.1.1", "stringify-package": "^1.0.1", "yargs": "^16.0.0" }, "bin": { "standard-version": "bin/cli.js" } }, "sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q=="], - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1722,8 +1720,6 @@ "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], - "stringify-package": ["stringify-package@1.0.1", "", {}, "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg=="], - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1774,7 +1770,7 @@ "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], - "through2": ["through2@4.0.2", "", { "dependencies": { "readable-stream": "3" } }, "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw=="], + "through2": ["through2@2.0.5", "", { "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ=="], "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], @@ -1918,6 +1914,8 @@ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "yaml": ["yaml@2.8.4", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog=="], + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], @@ -2036,11 +2034,11 @@ "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], - "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], "compare-func/dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], - "conventional-changelog-writer/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "concurrently/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], @@ -2054,12 +2052,8 @@ "engine.io/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "get-pkg-repo/through2": ["through2@2.0.5", "", { "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ=="], - "get-pkg-repo/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], - "git-semver-tags/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "gitconfiglocal/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], "glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], @@ -2074,8 +2068,12 @@ "http-proxy/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + "http-server/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="], + "is-core-module/hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + "load-json-file/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], "meow/read-pkg-up": ["read-pkg-up@7.0.1", "", { "dependencies": { "find-up": "^4.1.0", "read-pkg": "^5.2.0", "type-fest": "^0.8.1" } }, "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg=="], @@ -2110,16 +2108,14 @@ "sass/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "standard-version/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], - - "standard-version/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], - "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "through2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "tough-cookie-file-store/tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], "tsx/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], @@ -2132,6 +2128,10 @@ "winston/async": ["async@1.0.0", "", {}, "sha512-5mO7DX4CbJzp9zjaFXusQQ4tzKJARjNB1Ih1pVBi8wkbmXy/xzIDgEMXxWePLzt2OdFwaxfneIlT1nCiXubrPQ=="], + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "xml2js/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], "@ap0nia/eden/elysia/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], @@ -2200,18 +2200,26 @@ "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], + + "concurrently/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "concurrently/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "dotgitignore/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], - "get-pkg-repo/through2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "get-pkg-repo/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], "get-pkg-repo/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], "hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "http-server/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "http-server/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "meow/read-pkg-up/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "meow/read-pkg-up/read-pkg": ["read-pkg@5.2.0", "", { "dependencies": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^2.5.0", "parse-json": "^5.0.0", "type-fest": "^0.6.0" } }, "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg=="], @@ -2226,13 +2234,7 @@ "sass/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - "standard-version/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - - "standard-version/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], - - "standard-version/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], - - "standard-version/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + "through2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], @@ -2340,13 +2342,19 @@ "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "wrap-ansi-cjs/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + "@node-minify/core/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "concurrently/chalk/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + "dotgitignore/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], "dotgitignore/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], - "get-pkg-repo/through2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "http-server/chalk/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "meow/read-pkg-up/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], @@ -2360,24 +2368,24 @@ "read-pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], - "standard-version/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], + "wrap-ansi-cjs/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - "standard-version/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], + "wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "concurrently/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "dotgitignore/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "http-server/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "meow/read-pkg-up/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "meow/read-pkg-up/read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="], "meow/read-pkg-up/read-pkg/normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - "meow/read-pkg-up/read-pkg/parse-json/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], - "read-pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@1.3.0", "", { "dependencies": { "p-try": "^1.0.0" } }, "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q=="], - "standard-version/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], - "meow/read-pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], "read-pkg-up/find-up/locate-path/p-locate/p-limit/p-try": ["p-try@1.0.0", "", {}, "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww=="], diff --git a/package.json b/package.json index bf49ccc..b1b8c89 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "audiosprite": "^0.7.2", "babel-plugin-react-compiler": "^1.0.0", "classnames": "^2.5.1", + "commit-and-tag-version": "^12.7.3", "concurrently": "^9.2.1", "cross-env": "^10.1.0", "daisyui": "^5.5.19", @@ -142,7 +143,6 @@ "react-markdown": "^10.1.0", "react-qr-code": "^2.0.21", "sass-embedded": "^1.99.0", - "standard-version": "^9.5.0", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.4", "tailwindcss-animate": "^1.0.7", From 9a3e60562589a0ffb884374113dc1d731ab2f1bf Mon Sep 17 00:00:00 2001 From: Simeon Radivoev Date: Sun, 10 May 2026 02:53:05 +0300 Subject: [PATCH 5/7] chore(release): 1.6.0 --- CHANGELOG.md | 9 ++++++++- package.json | 6 +++--- src/packages/gameflow-sdk/package.json | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8334db8..0a70168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog -All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. + +## [1.6.0](https://github.com/simeonradivoev/gameflow-deck/compare/v1.5.0...v1.6.0) (2026-05-09) + + +### Features + +* Implemented public plugin system accessible from the store. ([38cb752](https://github.com/simeonradivoev/gameflow-deck/commit/38cb7525527b5ad4f6eb284cdad0001fd87eaf7e)) ## [1.5.0](https://github.com/simeonradivoev/gameflow-deck/compare/v1.4.0...v1.5.0) (2026-05-05) diff --git a/package.json b/package.json index b1b8c89..99a3e52 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "email": "work@simeonradivoev.com", "url": "https://simeonradivoev.com" }, - "version": "1.5.0", + "version": "1.6.0", "description": "Game Launcher", "icon": "./src/mainview/assets/icon.svg", "main": "./src/bun/index.ts", @@ -46,7 +46,7 @@ "flatpak:install": "bun run flatpak:build && flatpak --user install --reinstall \"$PWD/.config/flatpak/repo\" com.simeonradivoev.gameflow-deck", "build:prod:appimage": "bun run build:prod && bun run ./scripts/build-appimage.ts", "build:dev:appimage": "bun run build && bun run ./scripts/build-appimage.ts", - "version:generate": "standard-version --sign", + "version:generate": "commit-and-tag-version --sign", "package:Linux": "bun run build:prod:appimage", "package:Windows": "bun run build:prod", "download:chromium": "bun scripts/download-chromium.ts --out=./bin/chromium", @@ -153,4 +153,4 @@ "vite-static-assets-plugin": "^1.2.2", "vite-tsconfig-paths": "^6.1.1" } -} \ No newline at end of file +} diff --git a/src/packages/gameflow-sdk/package.json b/src/packages/gameflow-sdk/package.json index 7e51c16..09354fe 100644 --- a/src/packages/gameflow-sdk/package.json +++ b/src/packages/gameflow-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@simeonradivoev/gameflow-sdk", - "version": "1.5.3", + "version": "1.6.0", "types": "index.d.ts", "description": "plugin SDK for the Gameflow Deck Launcher", "exports": { @@ -48,4 +48,4 @@ "gameflow", "sdk" ] -} \ No newline at end of file +} From 9141fb35d48ae272e5ba73f28683d13ba5ca49a3 Mon Sep 17 00:00:00 2001 From: Simeon Radivoev Date: Fri, 15 May 2026 13:50:55 +0300 Subject: [PATCH 6/7] feat: Implemented link game importing feat: Implemented download page for downloading roms from various sources using plugins. Added support for internet archive external plugin. feat: Added tasks page to track running tasks/downloads feat: Added tanstack caching feat: Added quick play action Fixes #6 feat: Added quick emulator launch action fix: Made task queue only support 1 task per group and task ID should now be unique --- bun.lock | 102 +++---- package.json | 20 +- scripts/dev.ts | 7 + src/bun/api/controls/windows.ts | 1 - src/bun/api/games/games.ts | 66 ++++- src/bun/api/games/services/utils.ts | 38 ++- src/bun/api/jobs/bios-download-job.ts | 36 ++- src/bun/api/jobs/emulator-download-job.ts | 52 ++-- src/bun/api/jobs/import-job.ts | 77 ++++-- src/bun/api/jobs/install-job.ts | 158 ++--------- src/bun/api/jobs/jobs.ts | 95 ++++++- src/bun/api/jobs/launch-game-job.ts | 2 +- src/bun/api/jobs/login-job.ts | 2 +- src/bun/api/jobs/test-download-job.ts | 30 ++ .../com.simeonradivoev.gameflow.romm/romm.ts | 5 +- .../services.ts | 3 +- .../store.ts | 134 ++++++++- src/bun/api/settings/settings.ts | 6 + src/bun/api/system.ts | 8 + src/bun/utils.ts | 7 + src/bun/utils/downloader.ts | 22 +- src/mainview/components/AppCommunication.tsx | 32 ++- src/mainview/components/CardList.tsx | 34 ++- src/mainview/components/ContextDialog.tsx | 6 +- src/mainview/components/Filters.tsx | 2 +- src/mainview/components/GameList.tsx | 12 +- src/mainview/components/GamepadKeyboard.tsx | 4 - .../components/GlobalContextDialog.tsx | 28 ++ src/mainview/components/Header.tsx | 44 ++- src/mainview/components/HeaderSearchField.tsx | 4 +- src/mainview/components/RoundButton.tsx | 3 +- src/mainview/components/Screenshots.tsx | 7 +- src/mainview/components/SelectMenu.tsx | 4 +- src/mainview/components/SideFilters.tsx | 260 ++++++++++++------ .../components/game/ActionButtons.tsx | 6 +- src/mainview/components/game/MainActions.tsx | 155 +++++------ src/mainview/components/options/Button.tsx | 17 +- .../components/store/StoreEmulatorCard.tsx | 34 ++- src/mainview/gen/routeTree.gen.ts | 64 +++++ src/mainview/index.css | 1 + src/mainview/index.tsx | 42 ++- src/mainview/routes/__root.tsx | 9 +- src/mainview/routes/game/$source.$id.tsx | 4 +- src/mainview/routes/game/add.tsx | 39 ++- src/mainview/routes/games.tsx | 2 +- src/mainview/routes/index.tsx | 59 +++- src/mainview/routes/platform.$source.$id.tsx | 14 +- src/mainview/routes/settings/emulators.tsx | 3 +- src/mainview/routes/settings/route.tsx | 7 + src/mainview/routes/settings/tasks.tsx | 123 +++++++++ .../store/details.download.$source.$id.tsx | 129 +++++++++ .../routes/store/details.emulator.$id.tsx | 17 +- src/mainview/routes/store/tab/download.tsx | 109 ++++++++ src/mainview/routes/store/tab/emulators.tsx | 14 +- src/mainview/routes/store/tab/games.tsx | 5 +- src/mainview/routes/store/tab/plugins.tsx | 9 +- src/mainview/routes/store/tab/route.tsx | 3 +- src/mainview/scripts/contexts.ts | 12 +- src/mainview/scripts/queries/romm.ts | 36 ++- src/mainview/scripts/types.ts | 4 +- src/mainview/types.d.ts | 1 + src/packages/gameflow-sdk/hooks/app.ts | 37 +++ src/packages/gameflow-sdk/hooks/emulators.ts | 3 + src/packages/gameflow-sdk/hooks/games.ts | 50 +++- src/packages/gameflow-sdk/package.json | 10 +- src/packages/gameflow-sdk/shared.ts | 79 ++++++ src/packages/gameflow-sdk/task-queue.ts | 64 ++++- src/shared/types.schema.ts | 0 src/shared/types.ts | 0 src/shared/utils.ts | 10 +- 70 files changed, 1922 insertions(+), 560 deletions(-) create mode 100644 src/bun/api/jobs/test-download-job.ts create mode 100644 src/mainview/components/GlobalContextDialog.tsx create mode 100644 src/mainview/routes/settings/tasks.tsx create mode 100644 src/mainview/routes/store/details.download.$source.$id.tsx create mode 100644 src/mainview/routes/store/tab/download.tsx delete mode 100644 src/shared/types.schema.ts delete mode 100644 src/shared/types.ts diff --git a/bun.lock b/bun.lock index c4a2b79..5fbc781 100644 --- a/bun.lock +++ b/bun.lock @@ -25,13 +25,13 @@ "node-downloader-helper": "^2.1.11", "node-stream-zip": "^1.15.0", "node-unrar-js": "^2.0.2", - "npm-check-updates": "^22.1.1", + "npm-check-updates": "^22.2.0", "open": "^11.0.0", "p-queue": "^9.2.0", "pathe": "^2.0.3", "slugify": "^1.6.9", "smol-toml": "^1.6.1", - "systeminformation": "^5.31.5", + "systeminformation": "^5.31.6", "tapable": "^2.3.3", "tough-cookie": "^6.0.1", "tough-cookie-file-store": "^3.3.0", @@ -46,10 +46,11 @@ "@hey-api/openapi-ts": "^0.91.1", "@noriginmedia/norigin-spatial-navigation": "^3.1.0", "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.2.4", - "@tanstack/react-form": "^1.29.1", - "@tanstack/react-query": "^5.100.9", - "@tanstack/react-query-devtools": "^5.100.9", + "@tailwindcss/vite": "^4.3.0", + "@tanstack/react-form": "^1.32.0", + "@tanstack/react-query": "^5.100.10", + "@tanstack/react-query-devtools": "^5.100.10", + "@tanstack/react-query-persist-client": "^5.100.10", "@tanstack/react-router": "^1.169.2", "@tanstack/react-router-devtools": "^1.166.13", "@tanstack/react-router-ssr-query": "^1.166.12", @@ -82,6 +83,7 @@ "drizzle-kit": "^0.31.10", "eden-tanstack-query": "^0.0.9", "howler": "^2.2.4", + "idb-keyval": "^6.2.2", "lucide-react": "^0.563.0", "pretty-bytes": "^7.1.0", "pretty-ms": "^9.3.0", @@ -92,30 +94,26 @@ "react-markdown": "^10.1.0", "react-qr-code": "^2.0.21", "sass-embedded": "^1.99.0", - "tailwind-merge": "^3.5.0", - "tailwindcss": "^4.2.4", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.0", "tailwindcss-animate": "^1.0.7", "typescript": "^5.9.3", "usehooks-ts": "^3.1.1", "vite": "^7.3.3", - "vite-plugin-svg-icons-ng": "^1.9.0", + "vite-plugin-svg-icons-ng": "^1.9.1", "vite-static-assets-plugin": "^1.2.2", "vite-tsconfig-paths": "^6.1.1", }, }, "src/packages/gameflow-sdk": { "name": "@simeonradivoev/gameflow-sdk", - "version": "1.5.3", + "version": "1.6.0", "bin": { "gameflow-build": "build.ts", }, "peerDependencies": { "7zip-bin": "^5.2.0", "@auth/core": "^0.34.3", - "@elysiajs/cors": "^1.4.2", - "@elysiajs/eden": "^1.4.9", - "@jimp/wasm-webp": "^1.6.1", - "@phalcode/ts-igdb-client": "^1.0.26", "cheerio": "^1.2.0", "conf": "^15.1.0", "drizzle-orm": "^0.45.2", @@ -135,12 +133,8 @@ "pathe": "^2.0.3", "slugify": "^1.6.9", "smol-toml": "^1.6.1", - "systeminformation": "^5.31.5", "tapable": "^2.3.3", - "tough-cookie": "^6.0.1", - "tough-cookie-file-store": "^3.3.0", "unzip-stream": "^0.3.4", - "webview-bun": "^2.4.0", "zod": "^4.4.3", }, }, @@ -566,55 +560,59 @@ "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], - "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], + "@tailwindcss/node": ["@tailwindcss/node@4.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.21.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.0" } }, "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.0", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.0", "@tailwindcss/oxide-darwin-arm64": "4.3.0", "@tailwindcss/oxide-darwin-x64": "4.3.0", "@tailwindcss/oxide-freebsd-x64": "4.3.0", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", "@tailwindcss/oxide-linux-x64-musl": "4.3.0", "@tailwindcss/oxide-wasm32-wasi": "4.3.0", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.0", "", { "os": "android", "cpu": "arm64" }, "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0", "", { "os": "linux", "cpu": "arm" }, "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.4", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.0", "", { "dependencies": { "@emnapi/core": "^1.10.0", "@emnapi/runtime": "^1.10.0", "@emnapi/wasi-threads": "^1.2.1", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA=="], "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.2.4", "", { "dependencies": { "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "tailwindcss": "4.2.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.3.0", "", { "dependencies": { "@tailwindcss/node": "4.3.0", "@tailwindcss/oxide": "4.3.0", "tailwindcss": "4.3.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw=="], "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.3", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw=="], - "@tanstack/form-core": ["@tanstack/form-core@1.29.1", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.9.1" } }, "sha512-NIYPO36eEu7nSWvMpbFDQaBWyVtnH/C8fsZ3/XpJUT4uOWgmxsiUvHGbTbDNIQTXAKIkhwEl0sUrqBNn2SfUnw=="], + "@tanstack/form-core": ["@tanstack/form-core@1.32.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.9.1" } }, "sha512-Tn5VRDSjyqjmaet2tJMuEWDRFyrCaon03vxXPlSSaiSs6C/N7lCIwGCXJbZXEUq1kTj8jYN9qyXHbsz4LQHcow=="], "@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="], "@tanstack/pacer-lite": ["@tanstack/pacer-lite@0.1.1", "", {}, "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w=="], - "@tanstack/query-core": ["@tanstack/query-core@5.100.9", "", {}, "sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ=="], + "@tanstack/query-core": ["@tanstack/query-core@5.100.10", "", {}, "sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w=="], - "@tanstack/query-devtools": ["@tanstack/query-devtools@5.100.9", "", {}, "sha512-gqiptrTIhbK2PuCaPRHmWXfJG1NGYVFpAr0HqogEqiSBNB5xDz6fmesQt7w4WgMOqOQPnPHJ3ZDMuhDaXvNO8g=="], + "@tanstack/query-devtools": ["@tanstack/query-devtools@5.100.10", "", {}, "sha512-3DmJf25hDPus5IpVvp6ujXv6bKV2zPzI9vpbAmpJigsL/H6DPvPjmf7/Q9yVKEke//8fgeQ45abjgnLuyYxAiw=="], - "@tanstack/react-form": ["@tanstack/react-form@1.29.1", "", { "dependencies": { "@tanstack/form-core": "1.29.1", "@tanstack/react-store": "^0.9.1" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-hVHk4g0phd0HxRsv2ry6Xt8BqmalT55Q3cokhJBCC1St0hcGZhgwJJbohm9atao45BPG9e55DGvtbwExqZe35g=="], + "@tanstack/query-persist-client-core": ["@tanstack/query-persist-client-core@5.100.10", "", { "dependencies": { "@tanstack/query-core": "5.100.10" } }, "sha512-O9Pey40DhTTDBABS0bHr+KNL5/VMf6PrqjexS8WoDDtnkaoWM+y0MSe0V9E5W+BwvkjM33mB3aYcCxa175gZTQ=="], - "@tanstack/react-query": ["@tanstack/react-query@5.100.9", "", { "dependencies": { "@tanstack/query-core": "5.100.9" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A=="], + "@tanstack/react-form": ["@tanstack/react-form@1.32.0", "", { "dependencies": { "@tanstack/form-core": "1.32.0", "@tanstack/react-store": "^0.9.1" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-6WP5SQTA6/H9crCpvpq3ZppYWqtrdE5NjOy6ebABi6uAQPqhfTzrdjS9t40mCZCFtGI5585OhJV6zBP/KN2zcw=="], - "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.100.9", "", { "dependencies": { "@tanstack/query-devtools": "5.100.9" }, "peerDependencies": { "@tanstack/react-query": "^5.100.9", "react": "^18 || ^19" } }, "sha512-mM3slaVGXJmz+pOLgXdANj75ikgQCyudyl3kmFvm6brI1JyVeY/+IeD17uDHIvZrD8hfoO2sdZ54RFsHdYAuhA=="], + "@tanstack/react-query": ["@tanstack/react-query@5.100.10", "", { "dependencies": { "@tanstack/query-core": "5.100.10" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q=="], + + "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.100.10", "", { "dependencies": { "@tanstack/query-devtools": "5.100.10" }, "peerDependencies": { "@tanstack/react-query": "^5.100.10", "react": "^18 || ^19" } }, "sha512-zes0+o9ef5rAZXJ9f/SeaLs2nufJaeVkZkl/Or9NGrWVF41kL9Od9ED9nCwtQlgiF2VGtrzhEw5AU/igAO+aAg=="], + + "@tanstack/react-query-persist-client": ["@tanstack/react-query-persist-client@5.100.10", "", { "dependencies": { "@tanstack/query-persist-client-core": "5.100.10" }, "peerDependencies": { "@tanstack/react-query": "^5.100.10", "react": "^18 || ^19" } }, "sha512-EImacngLXYEtzlrIPf8IAqKN3foS7cmSj4GWqsHJvc7K+8fy2c3s7mdV8oTJeii/TvrzO4X9fcnXi6tUHMIOHA=="], "@tanstack/react-router": ["@tanstack/react-router@1.169.2", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.169.2", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-OJM7Kguc7ERnweaNRWsyWgIKcl3z23rD1B4jaxjzd9RGdnzpt2HfrWa9rggbT0Hfzhfo4D2ZmsfoTme035tniQ=="], @@ -658,7 +656,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -790,7 +788,7 @@ "buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="], - "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], @@ -1158,6 +1156,8 @@ "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "idb-keyval": ["idb-keyval@6.2.2", "", {}, "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="], @@ -1436,7 +1436,7 @@ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], - "npm-check-updates": ["npm-check-updates@22.1.1", "", { "bin": { "npm-check-updates": "build/cli.js", "ncu": "build/cli.js" } }, "sha512-uWSxJW25dy5ZM4SdLsi0VBgPSJlo7u+jARQ6Xql+85YYCoqXU2ZaympAZ6237/oybCq/I4nXddE9S9BTwBfBXA=="], + "npm-check-updates": ["npm-check-updates@22.2.0", "", { "bin": { "npm-check-updates": "build/cli.js", "ncu": "build/cli.js" } }, "sha512-kaxgbkGkCOtoSrsUXShgcEiEfrRPqmOGk6Yeya+5hoNptblu9vuE8/PLABUSJz+IeNgKJBFxcC3UrBYmKsB8iA=="], "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], @@ -1752,13 +1752,13 @@ "sync-message-port": ["sync-message-port@1.2.0", "", {}, "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg=="], - "systeminformation": ["systeminformation@5.31.5", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-5SyLdip4/3alxD4Kh+63bUQTJmu7YMfYQTC+koZy7X73HgNqZSD2P4wOZQWtUncvPvcEmnfIjCoygN4MRoEejQ=="], + "systeminformation": ["systeminformation@5.31.6", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-Uv2b2uGGM6ns+26czgW2cYRabYdnswM0ddSOOlryHOaelzsmDSet1iM/NT7VOYxW8x/BW+HkY+b1Ve2pLTSGSA=="], "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], - "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], + "tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="], - "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], + "tailwindcss": ["tailwindcss@4.3.0", "", {}, "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q=="], "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="], @@ -1866,7 +1866,7 @@ "vite": ["vite@7.3.3", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA=="], - "vite-plugin-svg-icons-ng": ["vite-plugin-svg-icons-ng@1.9.0", "", { "dependencies": { "svg-icon-baker": "2.0.1", "tinyglobby": "^0.2.16" }, "peerDependencies": { "vite": ">=5.0.0" } }, "sha512-vIyinFqjR5gEJiDt1MTFGewAJnwyB7tkZ9fjKQ9m9Wa7XmxTTAcj8h1l3C4zA02K6y/4ZuPYCLzHLovoUPDW6w=="], + "vite-plugin-svg-icons-ng": ["vite-plugin-svg-icons-ng@1.9.1", "", { "dependencies": { "svg-icon-baker": "2.0.1", "tinyglobby": "^0.2.16" }, "peerDependencies": { "vite": ">=5.0.0" } }, "sha512-g00nlit2havo0VRxpLiPkeJfMYt0DL/RO8X5HHop72rbMEZB5H1Fk7qXLWbTIO2/PkwJ8zSq0+h28ItaE1YQHQ=="], "vite-static-assets-plugin": ["vite-static-assets-plugin@1.2.2", "", { "dependencies": { "chalk": "^5.4.1", "chokidar": "^3.5.3", "minimatch": "^10.0.1" }, "peerDependencies": { "typescript": "^5.0.0", "vite": "^6.2.0" } }, "sha512-0mzHsxFa46Np5AixQcdWYLVH6eJxeok7qL7tXmxYavg/Uo0e5z+J6gavJ0TJ6dmJSe2Z+gwmDb64bCCZfg+gqA=="], @@ -1998,13 +1998,15 @@ "@node-minify/core/mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + "@tailwindcss/node/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], diff --git a/package.json b/package.json index 99a3e52..d770d67 100644 --- a/package.json +++ b/package.json @@ -76,13 +76,13 @@ "node-downloader-helper": "^2.1.11", "node-stream-zip": "^1.15.0", "node-unrar-js": "^2.0.2", - "npm-check-updates": "^22.1.1", + "npm-check-updates": "^22.2.0", "open": "^11.0.0", "p-queue": "^9.2.0", "pathe": "^2.0.3", "slugify": "^1.6.9", "smol-toml": "^1.6.1", - "systeminformation": "^5.31.5", + "systeminformation": "^5.31.6", "tapable": "^2.3.3", "tough-cookie": "^6.0.1", "tough-cookie-file-store": "^3.3.0", @@ -97,10 +97,11 @@ "@hey-api/openapi-ts": "^0.91.1", "@noriginmedia/norigin-spatial-navigation": "^3.1.0", "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.2.4", - "@tanstack/react-form": "^1.29.1", - "@tanstack/react-query": "^5.100.9", - "@tanstack/react-query-devtools": "^5.100.9", + "@tailwindcss/vite": "^4.3.0", + "@tanstack/react-form": "^1.32.0", + "@tanstack/react-query": "^5.100.10", + "@tanstack/react-query-devtools": "^5.100.10", + "@tanstack/react-query-persist-client": "^5.100.10", "@tanstack/react-router": "^1.169.2", "@tanstack/react-router-devtools": "^1.166.13", "@tanstack/react-router-ssr-query": "^1.166.12", @@ -133,6 +134,7 @@ "drizzle-kit": "^0.31.10", "eden-tanstack-query": "^0.0.9", "howler": "^2.2.4", + "idb-keyval": "^6.2.2", "lucide-react": "^0.563.0", "pretty-bytes": "^7.1.0", "pretty-ms": "^9.3.0", @@ -143,13 +145,13 @@ "react-markdown": "^10.1.0", "react-qr-code": "^2.0.21", "sass-embedded": "^1.99.0", - "tailwind-merge": "^3.5.0", - "tailwindcss": "^4.2.4", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.0", "tailwindcss-animate": "^1.0.7", "typescript": "^5.9.3", "usehooks-ts": "^3.1.1", "vite": "^7.3.3", - "vite-plugin-svg-icons-ng": "^1.9.0", + "vite-plugin-svg-icons-ng": "^1.9.1", "vite-static-assets-plugin": "^1.2.2", "vite-tsconfig-paths": "^6.1.1" } diff --git a/scripts/dev.ts b/scripts/dev.ts index ffc843c..1331f36 100644 --- a/scripts/dev.ts +++ b/scripts/dev.ts @@ -85,6 +85,13 @@ watch("./src/bun", { recursive: true }, (event, filename) => restart(); }); +watch("./src/packages", { recursive: true }, (event, filename) => +{ + if (restarting) return; + console.log(`[watcher] ${event}: ${filename} — restarting...`); + restart(); +}); + let server: Bun.Subprocess | undefined = spawnServer(); if (!process.env.HEADLESS) { diff --git a/src/bun/api/controls/windows.ts b/src/bun/api/controls/windows.ts index 40fc7d9..2621c26 100644 --- a/src/bun/api/controls/windows.ts +++ b/src/bun/api/controls/windows.ts @@ -72,7 +72,6 @@ export class GamepadWindows implements IGamepadBackend private index: number; private buffer = new ArrayBuffer(16); private view = new DataView(this.buffer); - private prevButtons = 0; private currButtons = 0; constructor(index = 0) { this.index = index; } diff --git a/src/bun/api/games/games.ts b/src/bun/api/games/games.ts index 3c2575c..f06f71f 100644 --- a/src/bun/api/games/games.ts +++ b/src/bun/api/games/games.ts @@ -5,7 +5,7 @@ import z from "zod"; import * as schema from "@schema/app"; import fs from "node:fs/promises"; import { SERVER_URL } from "@shared/constants"; -import { GameListFilterSchema } from '@simeonradivoev/gameflow-sdk/shared'; +import { CommandEntry, DownloadLookupEntry, DownloadsLookupFilterValues, GameListFilterSchema } from '@simeonradivoev/gameflow-sdk/shared'; import { InstallJob } from "../jobs/install-job"; import path from "node:path"; import { convertLocalToFrontend, getLocalGameMatch, getSourceGameDetailed } from "./services/utils"; @@ -512,7 +512,25 @@ export default new Elysia() await plugins.hooks.games.gameLookup.promise(matches, { source, id }); return Array.from(matches.values()).flatMap(m => m); }) - .post('/game/:source/:id/play', async ({ params: { id, source }, body, set }) => + .get('/game/:source/:id/commands', async ({ params: { id, source }, set }) => + { + const validCommands = await getValidLaunchCommandsForGame(source, id); + if (validCommands instanceof Error) + { + return errorToResponse(validCommands, set); + } + return validCommands as { + commands: CommandEntry[]; + gameId: FrontEndId; + source?: string; + sourceId?: string; + } | undefined; + }, { + response: z.object({ + commands: z.custom().array() + }) + }) + .post('/game/:source/:id/play', async ({ params: { id, source }, body: { command_id }, set }) => { const validCommands = await getValidLaunchCommandsForGame(source, id); if (validCommands) @@ -525,7 +543,7 @@ export default new Elysia() { try { - const validCommand = body.command_id ? validCommands.commands.find(c => c.id === body.command_id) : validCommands.commands[0]; + const validCommand = command_id ? validCommands.commands.find(c => c.id === command_id) : validCommands.commands[0]; if (validCommand) { // launch command waits for the game to exit, we don't want that. @@ -676,7 +694,10 @@ export default new Elysia() .post('/add/custom', async ({ body: { source, id, platformId, gamePath } }) => { if (taskQueue.hasActiveOfType(ImportJob)) return status("Conflict", "Import Job Already Running"); - const data = await taskQueue.enqueue(ImportJob.id, new ImportJob(source, id, gamePath, platformId), true); + const data = await taskQueue.enqueue(ImportJob.query({ source, id }), new ImportJob(source, id, gamePath, platformId), { + throwOnCancel: true + + }); return { source: 'local', id: data.localId }; }, { body: z.object({ @@ -685,4 +706,41 @@ export default new Elysia() gamePath: z.string(), platformId: z.number() }) + }).get('/downloads/lookup', async ({ query: { search, page, rows, orderBy, sortDirection, source } }) => + { + const matches = new Map(); + await plugins.hooks.games.downloadsLookup.promise(matches, { search, page, rows, orderBy, sortDirection, source }); + const allValues = Array.from(matches.values()); + return { hadMatchers: matches.size > 0, matches: allValues.flatMap(m => m.items), totalCount: allValues.reduce((p, c) => p + c.count, 0) }; + }, { + query: z.object({ + search: z.string().optional(), + page: z.coerce.number().optional(), + rows: z.coerce.number().optional(), + orderBy: z.string().optional(), + sortDirection: z.literal(["desc", "asc"]).optional(), + source: z.string().optional() + }) + }).get('/download/lookup/:source/:id', async ({ params: { source, id } }) => + { + const match = await plugins.hooks.games.downloadLookup.promise({ source, id }); + if (!match) return status("Not Found"); + return match; + }).get('/download/file/info', async ({ query: { file_url } }) => + { + const response = await fetch(file_url, { method: "HEAD" }); + if (!response.ok) return status('Internal Server Error', response.statusText); + return { size: Number(response.headers.get('content-length')), content_type: response.headers.get('content-type') }; + }, { + query: z.object({ file_url: z.url() }) + }).get('/download/lookup/filters', async () => + { + const filters: DownloadsLookupFilterValues = { + source: [], + orderBy: [] + }; + + await plugins.hooks.games.downloadsLookupFilters.promise({ filters }); + + return filters; }); \ No newline at end of file diff --git a/src/bun/api/games/services/utils.ts b/src/bun/api/games/services/utils.ts index aaac97b..9bef2f4 100644 --- a/src/bun/api/games/services/utils.ts +++ b/src/bun/api/games/services/utils.ts @@ -8,7 +8,7 @@ import { RPC_URL } from "@shared/constants"; import { hashFile } from "@/bun/utils"; import { host } from "@/bun/utils/host"; import * as emulatorSchema from "@schema/emulators"; -import { DownloadFileEntry, FrontEndGameType, FrontEndGameTypeDetailed, GameLookup, LocalDownloadFileEntry, LocalGameMetadata } from "@simeonradivoev/gameflow-sdk/shared"; +import { DownloadFileEntry, FrontEndGameType, FrontEndGameTypeDetailed, GameLookup, LocalDownloadFileEntry, LocalGameMetadata, ProgressStats } from "@simeonradivoev/gameflow-sdk/shared"; export async function calculateSize (installPath: string | null) { @@ -467,4 +467,40 @@ export async function createLocalGame (info: { }); return id; +} + +export async function downloadGame (ctx: { + downloads: DownloadFileEntry[], + auth?: string, + id: string, + abortSignal?: AbortSignal, + setProgress?: (progress: number, state: "download" | "extract", info: Partial>) => void, + extract_path?: string; + path_fs?: string; + +}): Promise +{ + const downloadedFiles = await plugins.hooks.downloadFiles.promise({ + id: ctx.id, + auth: ctx.auth, + files: ctx.downloads, + downloadPath: config.get('downloadPath'), + abortSignal: ctx.abortSignal, + updateProgress: (stats) => ctx.setProgress?.(stats.progress, 'download', stats) + }); + + if (!downloadedFiles) + { + return; + } + + const finalFiles = await plugins.hooks.postDownloadFiles.promise({ + files: downloadedFiles.files, + source: downloadedFiles.source, + extract_path: ctx.extract_path, + downloadPath: config.get('downloadPath'), + path_fs: ctx.path_fs + }) ?? downloadedFiles.files; + + return finalFiles; } \ No newline at end of file diff --git a/src/bun/api/jobs/bios-download-job.ts b/src/bun/api/jobs/bios-download-job.ts index 64537c1..7a4edba 100644 --- a/src/bun/api/jobs/bios-download-job.ts +++ b/src/bun/api/jobs/bios-download-job.ts @@ -1,35 +1,44 @@ -import z from "zod"; -import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk/task-queue"; import { config, plugins } from "../app"; import { simulateProgress } from "@/bun/utils"; import { Downloader } from "@/bun/utils/downloader"; import path from 'node:path'; import { ensureDir } from "fs-extra"; import { buildStoreFrontendEmulatorSystems, getStoreEmulatorPackage } from "../store/services/gamesService"; +import { DownloadJobData } from "@simeonradivoev/gameflow-sdk/shared"; -export class BiosDownloadJob implements IJob, "download"> +interface BiosDownloadJobData extends DownloadJobData +{ + emulator: string; +} + +export class BiosDownloadJob implements IJob { static id = "bios-download-job" as const; - static dataSchema = z.object({ emulator: z.string() }); static query = (q: { id: string; }) => `${BiosDownloadJob.id}-${q.id}`; group: string = "bios-download"; - emulator: string; + data: BiosDownloadJobData; dryRun: boolean; constructor(emulator: string, init?: { dryRun?: boolean; }) { - this.emulator = emulator; + this.data = { + emulator, + name: "Download Emulator Bios" + }; this.dryRun = init?.dryRun ?? false; } - async start (context: JobContext, "download">, z.infer, "download">) + async start (context: JobContext, BiosDownloadJobData, "download">) { - const emulator = await getStoreEmulatorPackage(this.emulator); + const emulator = await getStoreEmulatorPackage(this.data.emulator); if (!emulator) throw new Error("Could Not Find Emulator"); + this.data.name = `${emulator.name} Bios`; + this.data.preview_url = emulator.logo; const systems = await buildStoreFrontendEmulatorSystems(emulator); - const biosFolder = path.join(config.get('downloadPath'), "bios", this.emulator); + const biosFolder = path.join(config.get('downloadPath'), "bios", this.data.emulator); await ensureDir(biosFolder); - const files = await plugins.hooks.emulators.fetchBiosDownload.promise({ emulator: this.emulator, systems, biosFolder }); + const files = await plugins.hooks.emulators.fetchBiosDownload.promise({ emulator: this.data.emulator, systems, biosFolder }); if (!files) throw new Error("Could not find source to download from"); @@ -45,9 +54,12 @@ export class BiosDownloadJob implements IJob { context.setProgress(stats.progress, "download"); + this.data.downloaded = stats.downloaded; + this.data.speed = stats.speed; + this.data.total = stats.total; }, }); @@ -57,6 +69,6 @@ export class BiosDownloadJob implements IJob, EmulatorDownloadStates> +interface EmulatorDownloadJobData extends DownloadJobData +{ + emulator: string; +} + +export class EmulatorDownloadJob implements IJob { static id = "download-emulator" as const; - static dataSchema = z.object({ emulator: z.string() }); - emulator: string; downloadSource: string; emulatorPackage?: EmulatorPackageType; dryRun: boolean; isUpdate: boolean; + data: EmulatorDownloadJobData = { + name: "Download Emulator", + emulator: "" + }; constructor(emulator: string, downloadSource: string, init?: { dryRun?: boolean; isUpdate?: boolean; }) { - this.emulator = emulator; + this.data.emulator = emulator; this.downloadSource = downloadSource; this.dryRun = init?.dryRun ?? false; this.isUpdate = init?.isUpdate ?? false; } - async start (context: JobContext, EmulatorDownloadStates>) + async start (context: JobContext) { - this.emulatorPackage = await getStoreEmulatorPackage(this.emulator); + this.emulatorPackage = await getStoreEmulatorPackage(this.data.emulator); if (!this.emulatorPackage) throw new Error("Emulator not found"); + this.data.name = this.emulatorPackage.name; + this.data.preview_url = this.emulatorPackage.logo; const { url, info } = await getEmulatorDownload(this.emulatorPackage, this.downloadSource); - const emulatorsFolder = getEmulatorPath(this.emulator); + const emulatorsFolder = getEmulatorPath(this.data.emulator); if (this.dryRun) { @@ -49,29 +57,33 @@ export class EmulatorDownloadJob implements IJob { context.setProgress(stats.progress, 'download'); + this.data.total = stats.total; + this.data.downloaded = stats.downloaded; + this.data.speed = stats.speed; }, }); const destinationPaths = await downloader.start(); + context.abortSignal.throwIfAborted(); if (destinationPaths) { - const isArchive = destinationPaths[0].endsWith('.7z') || destinationPaths[0].endsWith('.zip') || destinationPaths[0].endsWith('.tar'); + const archive = isArchive(destinationPaths[0]); const isAppImage = destinationPaths[0].endsWith(".AppImage"); - if (!isArchive && !isAppImage) + if (!archive && !isAppImage) { throw new Error("Invalid Download Type"); } - if (isArchive) + if (archive) { if (destinationPaths[0]) { @@ -120,10 +132,10 @@ export class EmulatorDownloadJob implements IJob e.type === 'store')?.binPath ?? emulatorsFolder, info, @@ -136,7 +148,7 @@ export class EmulatorDownloadJob implements IJob, string> +interface ImportJobData extends DownloadJobData +{ + localId: number | null; +} + +export class ImportJob implements IJob { static id = "import-job" as const; - static dataSchema = z.object({ localId: z.number().nullable() }); + static query = (q: { source: string; id: string; }) => `${ImportJob.id}-${q.source}-${q.id}`; + data: ImportJobData = { + localId: null, + name: "Import Game" + }; group?: 'import-job'; gamePath: string; source: string; id: string; platformId: number; - localId: number | null = null; constructor(source: string, id: string, gamePath: string, platformId: number) { @@ -25,18 +36,20 @@ export class ImportJob implements IJob, str this.platformId = platformId; } - exposeData (): z.infer + exposeData () { - return { localId: this.localId }; + return this.data; } - async start (context: JobContext, string>, z.infer, string>): Promise + async start (context: JobContext, ImportJobData, string>): Promise { const matchesMap = new Map(); await plugins.hooks.games.gameLookup.promise(matchesMap, { source: this.source, id: this.id }); const matches = matchesMap.values().next().value; if (!matches || matches.length <= 0) throw Error("Could not Find Game"); const match = matches[0]; + this.data.name = match.name; + this.data.preview_url = match.coverUrl; let cover: Buffer | undefined = undefined; let coverType: string | undefined = undefined; @@ -50,24 +63,56 @@ export class ImportJob implements IJob, str } } + const platformMatch = match.platforms.find(p => p.id === this.platformId); + + const finalFiles: string[] = []; + + if (isUrl(this.gamePath)) + { + const archive = isArchive(this.gamePath); + const downloadedFiles = await downloadGame({ + downloads: [{ + file_path: this.id, + file_name: basename(this.gamePath), + url: new URL(this.gamePath) + }], + extract_path: archive ? '.tmp' : undefined, + path_fs: path.join('roms', platformMatch?.slug ?? this.source, this.id), + abortSignal: context.abortSignal, + id: `game-${this.source}-${this.id}`, + setProgress: (progress, state, info) => + { + context.setProgress(progress, state); + this.data.speed = info.speed; + this.data.total = info.total; + this.data.downloaded = info.downloaded; + }, + }); + + if (downloadedFiles) + finalFiles.push(...downloadedFiles); + } else + { + finalFiles.push(this.gamePath); + } + const localSearchFilters: any[] = []; if (match.igdb_id) localSearchFilters.push(eq(schema.games.igdb_id, match.igdb_id)); if (match.slug) localSearchFilters.push(eq(schema.games.slug, match.slug)); localSearchFilters.push(eq(schema.games.name, match.name)); - localSearchFilters.push(eq(schema.games.path_fs, this.gamePath)); + localSearchFilters.push(inArray(schema.games.path_fs, finalFiles)); const existingLocalGame = await db.query.games.findFirst({ where: or(...localSearchFilters) }); + context.abortSignal.throwIfAborted(); if (existingLocalGame) throw new Error("Game Already Exists"); - const platformMatch = match.platforms.find(p => p.id === this.platformId); - - this.localId = await createLocalGame({ + this.data.localId = await createLocalGame({ name: match.name, system_slug: platformMatch?.slug, source: undefined, source_id: undefined, slug: match.slug, - path_fs: this.gamePath, + path_fs: finalFiles[0], summary: match.summary, igdb_id: match.igdb_id, ra_id: undefined, diff --git a/src/bun/api/jobs/install-job.ts b/src/bun/api/jobs/install-job.ts index a9433d4..3d3c867 100644 --- a/src/bun/api/jobs/install-job.ts +++ b/src/bun/api/jobs/install-job.ts @@ -1,17 +1,12 @@ -import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk/task-queue"; import fs from 'node:fs/promises'; import path from 'node:path'; import { config, events, plugins } from "../app"; import { simulateProgress } from "@/bun/utils"; -import { Downloader } from "@/bun/utils/downloader"; -import Seven from 'node-7z'; import z from "zod"; -import { checkFiles, createLocalGame } from "../games/services/utils"; -import { ensureDir, move } from "fs-extra"; -import { path7za } from "7zip-bin"; -import StreamZip from 'node-stream-zip'; -import { which } from "bun"; -import { DownloadInfo } from "@simeonradivoev/gameflow-sdk/shared"; +import { checkFiles, createLocalGame, downloadGame } from "../games/services/utils"; +import { ensureDir } from "fs-extra"; +import { DownloadInfo, DownloadJobData } from "@simeonradivoev/gameflow-sdk/shared"; interface JobConfig { @@ -22,7 +17,7 @@ interface JobConfig export type InstallJobStates = 'download' | 'extract'; -export class InstallJob implements IJob +export class InstallJob implements IJob { static id = "install-job" as const; static query = (q: { source: string; id: string; }) => `${InstallJob.id}-${q.source}-${q.id}`; @@ -34,6 +29,9 @@ export class InstallJob implements IJob public localGameId?: number; public group = InstallJob.id; public localPath?: string; + data: DownloadJobData = { + name: "Install Game" + }; constructor(id: string, source: string, config?: JobConfig) { @@ -42,7 +40,7 @@ export class InstallJob implements IJob this.source = source; } - public async start (cx: JobContext) + public async start (cx: JobContext) { cx.setProgress(0, 'download'); await fs.mkdir(config.get('downloadPath'), { recursive: true }); @@ -58,131 +56,31 @@ export class InstallJob implements IJob if (!info) throw new Error(`Could not find downloader for source ${this.source}`); - const files = await checkFiles(info.files, !!info.extract_path); + this.data.name = info.name; + this.data.preview_url = info.coverUrl; + const files = await checkFiles(info.files, !!info.extract_path); if (this.config?.dryDownload !== true && files.some(f => !f.exists || !f.matches)) { - const headers: Record = {}; - if (info.auth) - headers['Authorization'] = info.auth; - const downloader = new Downloader(`game-${this.source}-${this.gameId}`, - files.filter(f => !f.exists || !f.matches), - config.get('downloadPath'), + const downloadedFiles = await downloadGame({ + downloads: files.filter(f => !f.exists || !f.matches), + extract_path: info.extract_path, + path_fs: info.path_fs, + abortSignal: cx.abortSignal, + auth: info.auth, + id: `game-${this.source}-${this.gameId}`, + setProgress: (process, state, info) => { - signal: cx.abortSignal, - headers, - onProgress (stats) - { - cx.setProgress(stats.progress, 'download'); - }, - }); + cx.setProgress(process, state); + this.data.downloaded = info.downloaded; + this.data.speed = info.speed; + this.data.total = info.total; + }, + }); - const downloadedFiles = await downloader.start(); - if (!downloadedFiles) - { - return; - } - - if (info.extract_path && downloadedFiles) - { - let progress = 0; - const progressDelta = 1 / downloadedFiles.length; - const extractPath = path.join(config.get('downloadPath'), info.path_fs ?? '', info.extract_path); - - for (const filePath of downloadedFiles) - { - await new Promise(async (resolve, reject) => - { - let sevenZipPath = process.env.ZIP7_PATH ?? path7za; - - if (filePath.endsWith('.rar')) - { - let newPath: string | undefined; - if (process.platform === 'win32' && await fs.exists("C:\\Program Files\\7-Zip\\7z.exe")) - { - newPath = "C:\\Program Files\\7-Zip\\7z.exe"; - } else - { - newPath = which('7z') ?? undefined; - } - - if (!newPath) - { - await fs.rm(filePath); - reject(new Error("No RAR Support")); - return; - } - - sevenZipPath = newPath; - } - - let rejected = false; - const seven = Seven.extractFull(filePath, extractPath, { $bin: sevenZipPath, $progress: true }); - seven.on('progress', p => - { - cx.setProgress(progress + p.percent * progressDelta, "extract"); - }); - seven.on('error', e => - { - reject(e); - rejected = true; - }); - seven.on('end', async () => - { - if (rejected) return; - await fs.rm(filePath); - resolve(true); - }); - }).catch(async e => - { - if (filePath.endsWith('.zip')) - { - cx.setProgress(0, "extract"); - console.error(e); - console.warn("Could not extract", filePath, "with 7zip trying zip extractor"); - await ensureDir(extractPath); - const zip = new StreamZip.async({ file: filePath }); - let entryCount = await zip.entriesCount; - let entryCounter = entryCount; - zip.on('extract', (entry, outPath) => - { - entryCounter--; - cx.setProgress(progress + (1 - (entryCounter / entryCount)) * 100 * progressDelta, "extract"); - }); - const count = await zip.extract(null, extractPath); - console.log(`Extracted ${count} entries`); - await zip.close(); - await fs.rm(filePath); - } else - { - throw e; - } - }); - - progress += progressDelta * 100; - } - - // check if 1 root folder we need to get rid of - const contents = await fs.readdir(extractPath); - if (contents.length === 1) - { - const stat = await fs.stat(path.join(extractPath, contents[0])); - if (stat.isDirectory()) - { - console.log("Found 1 root folder, using that instead"); - const tmpGameFolder = `${extractPath} (1)`; - await move(path.join(extractPath, contents[0]), tmpGameFolder, { overwrite: true }); - await move(tmpGameFolder, extractPath, { overwrite: true }); - } - } - - finalFiles.push(extractPath); - - } else - { + if (downloadedFiles) finalFiles.push(...downloadedFiles); - } } if (this.config?.dryDownload === true && info.extract_path) @@ -193,7 +91,7 @@ export class InstallJob implements IJob const coverResponse = await fetch(info.coverUrl); const cover = Buffer.from(await coverResponse.arrayBuffer()); - if (cx.abortSignal.aborted) return; + cx.abortSignal.throwIfAborted(); this.localGameId = await createLocalGame({ cover, diff --git a/src/bun/api/jobs/jobs.ts b/src/bun/api/jobs/jobs.ts index f75605c..e7e20a2 100644 --- a/src/bun/api/jobs/jobs.ts +++ b/src/bun/api/jobs/jobs.ts @@ -6,19 +6,21 @@ import TwitchLoginJob from "./twitch-login-job"; import UpdateStoreJob from "./update-store"; import { EmulatorDownloadJob } from "./emulator-download-job"; import { getErrorMessage } from "@/bun/utils"; -import { IJob } from "../../../packages/gameflow-sdk/task-queue"; +import { BaseEvent, IJob } from "@simeonradivoev/gameflow-sdk/task-queue"; import { LaunchGameJob } from "./launch-game-job"; import { BiosDownloadJob } from "./bios-download-job"; import { InstallJob } from "./install-job"; import ReloadPluginsJob from "./reload-plugins-job"; +import { FrontEndJob } from "@simeonradivoev/gameflow-sdk/shared"; function registerJob< const Path extends string, - const Schema extends z.ZodTypeAny, - const Query extends z.ZodTypeAny, + Schema, const States extends string, - T extends IJob, States> -> (_job: { id: Path; dataSchema: Schema; query?: (q: any) => string; } & (new (...args: any[]) => T)) +> (_job: { + id: Path; + query?: (q: any) => string; +} & (new (...args: any[]) => IJob)) { return new Elysia().ws(_job.id, { body: z.discriminatedUnion('type', [ @@ -30,9 +32,9 @@ function registerJob< type: z.literal(['data', 'started', 'progress']), state: z.string().optional(), progress: z.number(), - data: _job.dataSchema + data: z.custom() }), - z.object({ type: z.literal(['completed', 'ended']), data: _job.dataSchema }), + z.object({ type: z.literal(['completed', 'ended']), data: z.custom() }), z.object({ type: z.literal('waiting') }), z.object({ type: z.literal('error'), error: z.string() }) ]), @@ -42,7 +44,7 @@ function registerJob< const job = taskQueue.findJob(jobId, _job); if (job) { - ws.send({ type: 'data', state: job.state, progress: job.progress, data: job.job.exposeData?.() }); + ws.send({ type: 'data', state: job.state, progress: job.progress, data: job.job.exposeData?.() as Schema }); } else { ws.send({ type: 'waiting' }); @@ -102,6 +104,83 @@ function registerJob< } export const jobs = new Elysia({ prefix: '/api/jobs' }) + .ws('/list', { + response: z.discriminatedUnion('type', [ + z.object({ type: z.literal("allJobs"), active: z.custom().array(), queued: z.custom().array() }), + z.object({ type: z.literal("started"), job: z.custom() }), + z.object({ type: z.literal("progress"), job: z.custom() }), + z.object({ type: z.literal("queued"), job: z.custom() }), + z.object({ type: z.literal("aborted"), id: z.string() }), + z.object({ type: z.literal("ended"), id: z.string() }), + ]), + body: z.discriminatedUnion('type', [ + z.object({ type: z.literal("cancel"), id: z.string() }) + ]), + message (ws, message) + { + switch (message.type) + { + case "cancel": + taskQueue.cancelJob(message.id); + break; + } + }, + open (ws) + { + ws.send({ + type: 'allJobs', + active: taskQueue.getActiveJobs().map(j => + { + const job: FrontEndJob = { + id: j.id, + data: j.job.exposeData?.(), + progress: j.progress, + state: j.state, + status: j.status + }; + + return job; + }), + queued: taskQueue.getQueuedJobs()?.map(j => + { + const job: FrontEndJob = { + id: j.id, + data: j.job.exposeData?.(), + progress: j.progress, + state: j.state, + status: j.status + }; + + return job; + }) ?? [] + }); + + (ws.data as any).dispose = [taskQueue.on('started', (e: BaseEvent) => + { + ws.send({ type: "started", job: { id: e.id, data: e.job.job.exposeData?.(), progress: e.job.progress, state: e.job.state, status: e.job.status } }); + }), + taskQueue.on('progress', (e: BaseEvent) => + { + ws.send({ type: "progress", job: { id: e.id, data: e.job.job.exposeData?.(), progress: e.job.progress, state: e.job.state, status: e.job.status } }); + }), + taskQueue.on('queued', (e: BaseEvent) => + { + ws.send({ type: "queued", job: { id: e.id, data: e.job.job.exposeData?.(), progress: e.job.progress, state: e.job.state, status: e.job.status } }); + }), + taskQueue.on('abort', (e: BaseEvent) => + { + ws.send({ type: "aborted", id: e.id }); + }), + taskQueue.on('ended', (e: BaseEvent) => + { + ws.send({ type: "ended", id: e.id }); + })]; + }, + close (ws, code, reason) + { + (ws.data as any).dispose.forEach((d: any) => d()); + }, + }) .use(registerJob(LaunchGameJob)) .use(registerJob(LoginJob)) .use(registerJob(TwitchLoginJob)) diff --git a/src/bun/api/jobs/launch-game-job.ts b/src/bun/api/jobs/launch-game-job.ts index f5072e9..3ce0e83 100644 --- a/src/bun/api/jobs/launch-game-job.ts +++ b/src/bun/api/jobs/launch-game-job.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk/task-queue"; import { ActiveGameSchema, ActiveGameType } from "@simeonradivoev/gameflow-sdk"; import { config, db, events, plugins } from "../app"; import * as appSchema from "@schema/app"; diff --git a/src/bun/api/jobs/login-job.ts b/src/bun/api/jobs/login-job.ts index dd112ad..fb5d69a 100644 --- a/src/bun/api/jobs/login-job.ts +++ b/src/bun/api/jobs/login-job.ts @@ -1,5 +1,5 @@ import Elysia, { status } from "elysia"; -import { IJob, JobContext } from "../../../packages/gameflow-sdk/task-queue"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk/task-queue"; import { LOGIN_PORT, SERVER_URL } from "@/shared/constants"; import { host, localIp } from "@/bun/utils/host"; import cors from "@elysiajs/cors"; diff --git a/src/bun/api/jobs/test-download-job.ts b/src/bun/api/jobs/test-download-job.ts new file mode 100644 index 0000000..313b00b --- /dev/null +++ b/src/bun/api/jobs/test-download-job.ts @@ -0,0 +1,30 @@ +import { DownloadJobData } from "@simeonradivoev/gameflow-sdk/shared"; +import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk/task-queue"; +import { sleep } from "bun"; + +export class TestDownloadJob implements IJob +{ + data: DownloadJobData = { + speed: 1686, + downloaded: 0, + total: 6615841, + name: "Test Download Job" + }; + + group = "test-download"; + + async start (context: JobContext, DownloadJobData, string>): Promise + { + for (let i = 0; i < 10; i++) + { + await sleep(1000); + context.setProgress(i / 10 * 100, 'download'); + if (context.abortSignal.aborted) return; + } + } + exposeData (): DownloadJobData + { + return this.data; + } + +} \ No newline at end of file diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts index 93c6fbe..2e63269 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts @@ -6,7 +6,7 @@ import { DetailedRomSchema, getCollectionApiCollectionsIdGet, getCollectionsApiC import { config, events } from "@/bun/api/app"; import path from 'node:path'; import fs from 'node:fs/promises'; -import { hashFile, isSteamDeckGameMode } from "@/bun/utils"; +import { hashFile, isArchive, isSteamDeckGameMode } from "@/bun/utils"; import { CACHE_KEYS, getOrCached } from "@/bun/api/cache"; import secrets from "@/bun/api/secrets"; import { getAuthToken } from "@/clients/romm/core/auth.gen"; @@ -254,8 +254,7 @@ export default class RommIntegration implements PluginType let path_fs = path.join(rom.fs_path, rom.fs_name); if (files.length === 1) { - const name = files[0].file_name.toLocaleLowerCase(); - if (name.endsWith('.zip') || name.endsWith('.7z') || name.endsWith('.rar')) + if (isArchive(files[0].file_name)) { extract_path = '.'; path_fs = path.join(rom.fs_path, rom.slug ?? rom.fs_name_no_ext); diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts index 17c5f12..4935dd7 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts @@ -12,6 +12,7 @@ import mustache from "mustache"; import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService"; import fs from "node:fs/promises"; import { CommandEntry, EmulatorSourceEntryType, EmulatorSystem, FrontEndEmulator, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailed, SaveFileChange, EmulatorDownloadInfoType, StoreDownloadType, StoreGameType, EmulatorPackageType, EmulatorDownloadInfoSchema, StoreGameSchema } from "@simeonradivoev/gameflow-sdk/shared"; +import { isUrl } from "@/shared/utils"; export async function getStoreGames (gamesManifest: any[], filter?: { limit?: number; offset?: number; }) { @@ -39,7 +40,7 @@ export async function getStoreGame (id: string) function convertStoreMediaToPath (c: string) { - if (c.startsWith('http')) + if (isUrl(c)) { return `/api/romm/image?url=${encodeURIComponent(c)}`; } else diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts index 9b514a2..118eb11 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts @@ -2,7 +2,7 @@ import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-s import desc from './package.json'; import path, { } from 'node:path'; import { buildStoreFrontendEmulatorSystems, getAllStoreEmulatorPackages, getStoreEmulatorPackage, getStoreFolder } from "@/bun/api/store/services/gamesService"; -import { Glob, pathToFileURL } from "bun"; +import { Glob, pathToFileURL, which } from "bun"; import { and, eq } from "drizzle-orm"; import * as emulatorSchema from '@schema/emulators'; @@ -13,6 +13,12 @@ import UpdateStoreJob from "@/bun/api/jobs/update-store"; import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService"; import { buildFilters, buildLaunchCommand, buildSaves, convertStoreEmulatorToFrontend, convertStoreToFrontend, convertStoreToFrontendDetailed, getExistingStoreEmulatorDownload, getShuffledStoreGames, getStoreGame, getValidDownloads } from "./services"; import { DownloadInfo, FrontEndEmulatorDetailed, FrontEndGameTypeWithIds } from "@simeonradivoev/gameflow-sdk/shared"; +import { isUrl } from "@/shared/utils"; +import { Downloader } from "@/bun/utils/downloader"; +import { ensureDir, move } from "fs-extra"; +import StreamZip from "node-stream-zip"; +import { path7za } from "7zip-bin"; +import Seven from 'node-7z'; export default class RommIntegration implements PluginType { @@ -295,7 +301,7 @@ export default class RommIntegration implements PluginType const info: DownloadInfo = { id: validDownload.id, - coverUrl: game.covers?.[0] ? game.covers[0].startsWith('http') ? game.covers[0] : pathToFileURL(path.join(getStoreFolder(), game.covers[0])).href : "", + coverUrl: game.covers?.[0] ? isUrl(game.covers[0]) ? game.covers[0] : pathToFileURL(path.join(getStoreFolder(), game.covers[0])).href : "", screenshotUrls: game.screenshots ?? [], files: [{ url: new URL(validDownload.url), @@ -325,5 +331,129 @@ export default class RommIntegration implements PluginType return info; }); }); + + ctx.hooks.downloadFiles.tapPromise(desc.name, async ({ id, files, downloadPath, abortSignal, auth, updateProgress }) => + { + const headers: Record = {}; + if (auth) + headers['Authorization'] = auth; + const downloader = new Downloader(id, + files, + downloadPath, + { + signal: abortSignal, + headers, + onProgress: updateProgress, + }); + + const downloadedFiles = await downloader.start(); + if (downloadedFiles) + { + return { source: desc.name, files: downloadedFiles }; + } + }); + + ctx.hooks.postDownloadFiles.tapPromise(desc.name, async ({ files, extract_path, source, downloadPath, path_fs }) => + { + if (extract_path && files && source === desc.name) + { + let progress = 0; + const progressDelta = 1 / files.length; + const extractPath = path.join(downloadPath, path_fs ?? '', extract_path); + + for (const filePath of files) + { + await new Promise(async (resolve, reject) => + { + let sevenZipPath = process.env.ZIP7_PATH ?? path7za; + + if (filePath.endsWith('.rar')) + { + let newPath: string | undefined; + if (process.platform === 'win32' && await fs.exists("C:\\Program Files\\7-Zip\\7z.exe")) + { + newPath = "C:\\Program Files\\7-Zip\\7z.exe"; + } else + { + newPath = which('7z') ?? undefined; + } + + if (!newPath) + { + await fs.rm(filePath); + reject(new Error("No RAR Support")); + return; + } + + sevenZipPath = newPath; + } + + let rejected = false; + const seven = Seven.extractFull(filePath, extractPath, { $bin: sevenZipPath, $progress: true }); + seven.on('progress', p => + { + ctx.setProgress?.(progress + p.percent * progressDelta, "extract", { + speed: 0, + total: 0, + downloaded: 0 + }); + }); + seven.on('error', e => + { + reject(e); + rejected = true; + }); + seven.on('end', async () => + { + if (rejected) return; + await fs.rm(filePath); + resolve(true); + }); + }).catch(async e => + { + if (filePath.endsWith('.zip')) + { + ctx.setProgress?.(0, "extract", {}); + console.error(e); + console.warn("Could not extract", filePath, "with 7zip trying zip extractor"); + await ensureDir(extractPath); + const zip = new StreamZip.async({ file: filePath }); + let entryCount = await zip.entriesCount; + let entryCounter = entryCount; + zip.on('extract', (entry, outPath) => + { + entryCounter--; + ctx.setProgress?.(progress + (1 - (entryCounter / entryCount)) * 100 * progressDelta, "extract", {}); + }); + const count = await zip.extract(null, extractPath); + console.log(`Extracted ${count} entries`); + await zip.close(); + await fs.rm(filePath); + } else + { + throw e; + } + }); + + progress += progressDelta * 100; + } + + // check if 1 root folder we need to get rid of + const contents = await fs.readdir(extractPath); + if (contents.length === 1) + { + const stat = await fs.stat(path.join(extractPath, contents[0])); + if (stat.isDirectory()) + { + console.log("Found 1 root folder, using that instead"); + const tmpGameFolder = `${extractPath} (1)`; + await move(path.join(extractPath, contents[0]), tmpGameFolder, { overwrite: true }); + await move(tmpGameFolder, extractPath, { overwrite: true }); + } + } + + return [extractPath]; + } + }); } } \ No newline at end of file diff --git a/src/bun/api/settings/settings.ts b/src/bun/api/settings/settings.ts index e4e2da1..ebd5b91 100644 --- a/src/bun/api/settings/settings.ts +++ b/src/bun/api/settings/settings.ts @@ -10,6 +10,8 @@ import { getRelevantEmulators } from "./services"; import type { JSONSchema7 } from "json-schema"; import ReloadPluginsJob from "../jobs/reload-plugins-job"; import { pluginZodRegistry } from "../plugins/plugin-manager"; +import { TestDownloadJob } from "../jobs/test-download-job"; +import { randomUUIDv7 } from "bun"; export const settings = new Elysia({ prefix: '/api/settings' }) .get('/emulators/automatic', async () => @@ -112,6 +114,10 @@ export const settings = new Elysia({ prefix: '/api/settings' }) { return { value: plugins.plugins[decodeURIComponent(source)].config?.get(decodeURIComponent(id)) }; }) + .post('/test/download', async () => + { + taskQueue.enqueue(randomUUIDv7(), new TestDownloadJob()); + }) .put('/:source/:id', async ({ params: { source, id }, body: { value } }) => { const plugin = plugins.plugins[decodeURIComponent(source)]; diff --git a/src/bun/api/system.ts b/src/bun/api/system.ts index 5408a6d..2124144 100644 --- a/src/bun/api/system.ts +++ b/src/bun/api/system.ts @@ -86,6 +86,7 @@ export const system = new Elysia({ prefix: '/api/system' }) z.object({ type: z.literal('info'), data: SystemInfoSchema }), z.object({ type: z.literal('focus') }), z.object({ type: z.literal('loading'), progress: z.number(), state: z.string().optional() }), + z.object({ type: z.literal('activeTask'), progress: z.number().nullable() }), z.object({ type: z.literal('loaded') }), ]), async open (ws) @@ -94,6 +95,8 @@ export const system = new Elysia({ prefix: '/api/system' }) if (existingLoading) ws.send({ type: 'loading', progress: existingLoading.progress, state: existingLoading.state }); else ws.send({ type: 'loaded' }); + ws.send({ type: 'activeTask', progress: taskQueue.getActiveJobs()[0]?.progress }); + const startInfo = async () => { const battery = await si.battery(); @@ -116,6 +119,8 @@ export const system = new Elysia({ prefix: '/api/system' }) dispose.push(taskQueue.on('progress', e => { + ws.send({ type: 'activeTask', progress: e.progress }); + if (e.id === ReloadPluginsJob.id) { ws.send({ type: "loading", progress: e.progress, state: e.state }); @@ -127,6 +132,8 @@ export const system = new Elysia({ prefix: '/api/system' }) })); dispose.push(taskQueue.on('started', e => { + ws.send({ type: 'activeTask', progress: 0 }); + if (e.id === ReloadPluginsJob.id) ws.send({ type: "loading", progress: e.job.progress, state: e.job.state }); else if (e.id === SelfUpdateJob.id) @@ -134,6 +141,7 @@ export const system = new Elysia({ prefix: '/api/system' }) })); dispose.push(taskQueue.on('ended', e => { + ws.send({ type: 'activeTask', progress: null }); if (e.id !== ReloadPluginsJob.id && e.id !== SelfUpdateJob.id) return; ws.send({ type: "loaded" }); })); diff --git a/src/bun/utils.ts b/src/bun/utils.ts index fe44ad2..a3868e4 100644 --- a/src/bun/utils.ts +++ b/src/bun/utils.ts @@ -5,6 +5,8 @@ import { config } from './api/app'; import fs from 'node:fs/promises'; import packageDef from '~/package.json'; +const archiveRegex = /.(zip|rar|7zip|7z|tar|tar.gz)$/i; + export function checkRunning (pid: number) { try @@ -178,4 +180,9 @@ export async function moveAllFiles (srcDir: string, destDir: string) export function getAppVersion () { return process.env.VERSION_OVERRIDE ?? packageDef.version; +} + +export function isArchive (path: string) +{ + return archiveRegex.test(path); } \ No newline at end of file diff --git a/src/bun/utils/downloader.ts b/src/bun/utils/downloader.ts index f0f30ca..920e7c8 100644 --- a/src/bun/utils/downloader.ts +++ b/src/bun/utils/downloader.ts @@ -5,12 +5,7 @@ import fs from 'node:fs/promises'; import { createWriteStream } from "node:fs"; import { config, jar } from "../api/app"; import { moveAllFiles } from "../utils"; -import { DownloadFileEntry } from "@simeonradivoev/gameflow-sdk/shared"; - -export interface ProgressStats -{ - progress: number; -} +import { DownloadFileEntry, ProgressStats } from "@simeonradivoev/gameflow-sdk/shared"; interface TmpDownloadMetadata { @@ -32,6 +27,7 @@ export class Downloader id: string; tmpPath: string; tmpPathMeta: string; + downloadSpeed: number = 0; /** * @@ -163,10 +159,7 @@ export class Downloader }); const totalBytes = totalSize || Number(res.headers.get("content-length")) || 0; - if (totalSize <= 0) - bytesReceived = 0; - else - bytesReceived += start; + bytesReceived += start; const reader = res.body!.getReader(); @@ -181,10 +174,11 @@ export class Downloader if (totalBytes > 0 && this.onProgress) { const percent = (bytesReceived / totalBytes) * 100; - - if (Date.now() - lastUpdate > 100) + const timeDelta = Date.now() - lastUpdate; + if (timeDelta > 100) { - this.onProgress({ progress: percent }); + this.downloadSpeed = this.downloadSpeed * 0.8 + Math.round(value.length / (timeDelta / 1000)) * 0.2; + this.onProgress({ progress: percent, downloaded: bytesReceived, total: totalBytes, speed: this.downloadSpeed }); lastUpdate = Date.now(); } } @@ -194,7 +188,7 @@ export class Downloader if (this.signal.reason === 'cancel') { console.log("Canceling Download and cleaning up files"); - await fs.rm(this.tmpPath, { recursive: true }); + await fs.rm(this.tmpPath, { recursive: true, maxRetries: 3, retryDelay: 3 }); await fs.rm(this.tmpPathMeta); return; } diff --git a/src/mainview/components/AppCommunication.tsx b/src/mainview/components/AppCommunication.tsx index 3dec17a..df727ad 100644 --- a/src/mainview/components/AppCommunication.tsx +++ b/src/mainview/components/AppCommunication.tsx @@ -1,13 +1,14 @@ import { useEffect, useRef, useState } from "react"; -import { SystemInfoContext } from "../scripts/contexts"; +import { AppContext, SystemInfoContext } from "../scripts/contexts"; import { systemApi } from "../scripts/clientApi"; -import { SystemInfoType } from '@simeonradivoev/gameflow-sdk/shared'; +import { AppInfoContext, SystemInfoType } from '@simeonradivoev/gameflow-sdk/shared'; import LoadingScreen from "./LoadingScreen"; import { GamepadKeyboard } from "./GamepadKeyboard"; export default function AppCommunication (data: { children: any; }) { const [systemInfo, setSystemInfo] = useState(); + const [appContext, setAppContext] = useState({} as AppInfoContext); const [loadingInfo, setLoadingInfo] = useState(undefined); const [loading, setLoading] = useState(true); const loadingProgressBarRef = useRef(null); @@ -25,6 +26,9 @@ export default function AppCommunication (data: { children: any; }) case "focus": window.focus(); break; + case "activeTask": + setAppContext(c => ({ ...c, activeTaskProgress: data.progress })); + break; case "loading": setLoadingInfo(data.state); if (loadingProgressBarRef.current) @@ -45,17 +49,19 @@ export default function AppCommunication (data: { children: any; }) }, []); return - {loading ? - -
    -
    - - {loadingInfo} + + {loading ? + +
    +
    + + {loadingInfo} +
    +
    - -
    - - : data.children} - + + : data.children} + + ; } \ No newline at end of file diff --git a/src/mainview/components/CardList.tsx b/src/mainview/components/CardList.tsx index d05ce7b..8511374 100644 --- a/src/mainview/components/CardList.tsx +++ b/src/mainview/components/CardList.tsx @@ -6,7 +6,7 @@ import import CardElement, { GameCardParams } from "./CardElement"; import { JSX } from "react"; import { twMerge } from "tailwind-merge"; -import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts"; +import { GamePadButtonCode, Shortcut, useShortcuts } from "../scripts/shortcuts"; import { oneShot } from "../scripts/audio/audio"; export interface GameMetaExtra extends GameMeta @@ -16,7 +16,7 @@ export interface GameMetaExtra extends GameMeta focusKey: string; } -function LocalCardElement (data: { game: GameMetaExtra, i: number; } & FocusParams & InteractParams) +function LocalCardElement (data: { game: GameMetaExtra, i: number; onQuickAction?: (ctx: InteractParamsArgs) => void; } & FocusParams & InteractParams) { let preview: GameCardParams['preview'] = data.game.preview; if (!preview && data.game.previewUrls) @@ -31,7 +31,28 @@ function LocalCardElement (data: { game: GameMetaExtra, i: number; } & FocusPara oneShot('click'); }; - useShortcuts(data.game.focusKey, () => [{ label: "Details", button: GamePadButtonCode.A, action: event => handleAction({ event, focusKey: data.game.focusKey }) }]); + const handleAltAction = (ctx: InteractParamsArgs) => + { + data.game.onQuickAction?.(); + data.onQuickAction?.({ event, focusKey: data.game.focusKey }); + oneShot('click'); + }; + + useShortcuts(data.game.focusKey, () => + { + const options: Shortcut[] = [{ + label: "Details", + button: GamePadButtonCode.A, + action: event => handleAction({ event, focusKey: data.game.focusKey }) + }]; + + if (data.onQuickAction || data.game.onQuickAction) + { + options.push({ label: "Play", button: GamePadButtonCode.X, action: event => handleAltAction({ event, focusKey: data.game.focusKey }) }); + } + + return options; + }, [data.onQuickAction, data.game.onQuickAction, data.game.focusKey]); return ( {data.games.map((g, i) => data.onSelectGame?.(g.id)} i={i} />)} + key={g.id} + onFocus={data.onFocus} + game={g} + onAction={() => data.onSelectGame?.(g.id)} + i={i} + />)} {data.finalElement} diff --git a/src/mainview/components/ContextDialog.tsx b/src/mainview/components/ContextDialog.tsx index 353f429..54babea 100644 --- a/src/mainview/components/ContextDialog.tsx +++ b/src/mainview/components/ContextDialog.tsx @@ -64,7 +64,7 @@ export function OptionElement (data: DialogEntry & { onFocus?: () => void; class className={ twMerge("flex cursor-pointer sm:text-sm md:text-base group-focusable scroll-m-4")}> -
    @@ -166,7 +166,7 @@ export function ContextDialog (data: { }] : [], [data.open]); return @@ -174,7 +174,7 @@ export function ContextDialog (data: {
    -
      +
        {!!data.rootFocusKey && (data.showShortcuts ?? true) &&
      • } diff --git a/src/mainview/components/GameList.tsx b/src/mainview/components/GameList.tsx index 80c4944..1075a9f 100644 --- a/src/mainview/components/GameList.tsx +++ b/src/mainview/components/GameList.tsx @@ -2,13 +2,15 @@ import { useSuspenseQuery } from "@tanstack/react-query"; import { GameMetaExtra, CardList } from "./CardList"; import { DefaultRommStaleTime, RPC_URL } from "@shared/constants"; import { GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; -import { useNavigate } from "@tanstack/react-router"; +import { useNavigate, useRouter } from "@tanstack/react-router"; import { HardDrive } from "lucide-react"; import { JSX, useContext } from "react"; import { useLocalSetting } from "../scripts/utils"; import { AnimatedBackgroundContext } from "../scripts/contexts"; import { allGamesQuery } from "@queries/romm"; import { FrontEndGameType, FrontEndId } from "@simeonradivoev/gameflow-sdk/shared"; +import { isUrl } from "@/shared/utils"; +import { FOCUS_KEYS } from "../scripts/types"; export interface GameListParams extends FocusParams { @@ -17,6 +19,7 @@ export interface GameListParams extends FocusParams grid?: boolean, setBackground?: (url: string) => void; onGameSelect?: (id: FrontEndId, source: string | null, sourceId: string | null) => void; + onQuickAction?: (id: FrontEndId, source: string | null, sourceId: string | null) => void; focus?: string; className?: string; finalElement?: JSX.Element | JSX.Element[]; @@ -97,7 +100,7 @@ export function GameList (data: GameListParams) const previewUrls = g.path_covers.map(c => { - const url = c.startsWith("http") ? new URL(c) : new URL(`${RPC_URL(__HOST__)}${c}`); + const url = isUrl(c) ? new URL(c) : new URL(`${RPC_URL(__HOST__)}${c}`); url.searchParams.delete('ts'); return url; }); @@ -105,13 +108,13 @@ export function GameList (data: GameListParams) let platformUrl: URL | undefined = undefined; if (g.path_platform_cover) { - platformUrl = g.path_platform_cover.startsWith("http") ? new URL(g.path_platform_cover) : new URL(`${RPC_URL(__HOST__)}${g.path_platform_cover}`); + platformUrl = isUrl(g.path_platform_cover) ? new URL(g.path_platform_cover) : new URL(`${RPC_URL(__HOST__)}${g.path_platform_cover}`); platformUrl.searchParams.set('width', "64"); } return { id: `${g.id.source}@${g.id.id}`, - focusKey: `${data.id}-${g.id.source}@${g.id.id}`, + focusKey: FOCUS_KEYS.GAME_LIST_CARD(data.id, g.id), title: g.name ?? "", subtitle: (
        @@ -122,6 +125,7 @@ export function GameList (data: GameListParams) previewUrls: previewUrls, badges: badges, onSelect: () => data.onGameSelect ? data.onGameSelect(g.id, g.source, g.source_id) : handleDefaultSelect(g), + onQuickAction: data.onQuickAction ? () => data.onQuickAction?.(g.id, g.source, g.source_id) : undefined, onFocus: () => handleFocus(g.id, g.source, g.source_id) } satisfies GameMetaExtra; }, diff --git a/src/mainview/components/GamepadKeyboard.tsx b/src/mainview/components/GamepadKeyboard.tsx index 37e533b..75005a1 100644 --- a/src/mainview/components/GamepadKeyboard.tsx +++ b/src/mainview/components/GamepadKeyboard.tsx @@ -387,10 +387,6 @@ export function GamepadKeyboard () const magnitudeSqr = (x * x) + (y * y); const magnitude = Math.sqrt(magnitudeSqr); - const elementPos = keyIndex < 0 ? undefined : elements[side].positions[keyIndex]; - //const lerpX = (element?.left ?? 0); - //const lerpY = (element?.top ?? 0); - const size = 12; circle.style.left = `calc(50% + ${50 * x}% - 16px)`; circle.style.top = `calc(50% + ${50 * y}% - 16px)`; circle.style.opacity = `${1 - Math.pow(magnitude, 2)}`; diff --git a/src/mainview/components/GlobalContextDialog.tsx b/src/mainview/components/GlobalContextDialog.tsx new file mode 100644 index 0000000..0fcc23d --- /dev/null +++ b/src/mainview/components/GlobalContextDialog.tsx @@ -0,0 +1,28 @@ +import { useState } from "react"; +import { GlobalDialogContext } from "../scripts/contexts"; +import { useContextDialog } from "./ContextDialog"; + +export default function GlobalContextDialog (data: { children: any; }) +{ + const [currentContext, setCurrentContext] = useState(undefined); + const [preferredChildFocusKey, setPreferredChildFocusKey] = useState(undefined); + const [onCloseCallback, setOnCloseCallback] = useState<(() => void) | undefined>(undefined); + + const { dialog, setOpen } = useContextDialog('global-context-dialog', { + content: currentContext, + onClose: onCloseCallback, + preferredChildFocusKey: preferredChildFocusKey + }); + return + {data.children} + {dialog} + ; +} \ No newline at end of file diff --git a/src/mainview/components/Header.tsx b/src/mainview/components/Header.tsx index 932dada..d38ef5b 100644 --- a/src/mainview/components/Header.tsx +++ b/src/mainview/components/Header.tsx @@ -29,10 +29,11 @@ import { twMerge } from "tailwind-merge"; import { TwitchIcon } from "../scripts/brandIcons"; import { rommLoggedInQuery } from "../scripts/queries/romm"; import { twitchLoginVerificationQuery } from "../scripts/queries/settings"; -import { SystemInfoContext } from "../scripts/contexts"; +import { AppContext, SystemInfoContext } from "../scripts/contexts"; import { useNavigate, useRouter } from "@tanstack/react-router"; import { oneShot } from "../scripts/audio/audio"; import { hasUpdateQuery } from "../scripts/queries/system"; +import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts"; function HeaderAvatar (data: { id: string; @@ -73,6 +74,7 @@ export interface HeaderButton external?: boolean; action?: () => void; className?: string; + shortcutLabel?: string; } export interface HeaderAccount @@ -111,14 +113,22 @@ function NotificationStatus () function ClockStatus () { - const ref = useRef(null); + const navigate = useNavigate(); + const app = useContext(AppContext); + const refClock = useRef(null); + const activeTaskProgress = app.activeTaskProgress; + const handleTaskClick = () => + { + navigate({ to: '/settings/tasks' }); + }; + const { ref, focusKey } = useFocusable({ focusKey: 'tasks-indicator', focusable: !!activeTaskProgress, onEnterPress: handleTaskClick }); useEffect(() => { function update () { - if (ref.current) + if (refClock.current) { - ref.current.textContent = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + refClock.current.textContent = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } } @@ -142,7 +152,16 @@ function ClockStatus () return () => clearTimeout(timeout); }, []); - return
        ; + useShortcuts(focusKey, () => [{ + label: "Downloads", button: GamePadButtonCode.A, action (e) + { + handleTaskClick(); + }, + }]); + + return
        + + {activeTaskProgress ?
        : }
        ; } function BluetoothStatus () @@ -288,6 +307,7 @@ export function HeaderStatusBar (data: { buttons?: HeaderButton[]; buttonElement {data.buttonElements} {data.buttons?.map(b => {data.title} - , id: "header-settings-btn", action: goToSettings, external: true }]} /> + , + id: "header-settings-btn", + action: goToSettings, + external: true, + shortcutLabel: "Settings" + } + ]} /> diff --git a/src/mainview/components/HeaderSearchField.tsx b/src/mainview/components/HeaderSearchField.tsx index 823af58..198c552 100644 --- a/src/mainview/components/HeaderSearchField.tsx +++ b/src/mainview/components/HeaderSearchField.tsx @@ -96,10 +96,10 @@ export default function HeaderSearchField (data: { isFocusBoundary: data.compact && showInput }); - return
        + return
        {(!data.compact || showInput) && } - {data.compact && !showInput && setShowInput(true)} className="header-icon sm:size-10 md:size-14" id={`${data.id}-field`} >} + {data.compact && !showInput && setShowInput(true)} className="header-icon sm:size-10 md:size-14" id={`${data.id}-field`} >}
        ; } \ No newline at end of file diff --git a/src/mainview/components/RoundButton.tsx b/src/mainview/components/RoundButton.tsx index 386723b..01f9017 100644 --- a/src/mainview/components/RoundButton.tsx +++ b/src/mainview/components/RoundButton.tsx @@ -9,10 +9,11 @@ export function RoundButton (data: { external?: boolean; style?: ButtonStyle; cssStyle?: CSSProperties; + shortcutLabel?: string; } & InteractParams & FocusParams) { return ( - diff --git a/src/mainview/components/Screenshots.tsx b/src/mainview/components/Screenshots.tsx index 42d76d3..e65a965 100644 --- a/src/mainview/components/Screenshots.tsx +++ b/src/mainview/components/Screenshots.tsx @@ -8,6 +8,7 @@ import Carousel from "./Carousel"; import { ContextDialog } from "./ContextDialog"; import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts"; import { twMerge } from "tailwind-merge"; +import { isUrl } from "@/shared/utils"; function Screenshot (data: { path: string; index: number; setFocused?: (index: number) => void; } & InteractParams) { @@ -21,8 +22,9 @@ function Screenshot (data: { path: string; index: number; setFocused?: (index: n scrollIntoNearestParent(ref.current, { behavior: details.instant ? 'instant' : 'smooth' }); } }); 4096; + const url = isUrl(data.path) ? data.path : `${RPC_URL(__HOST__)}${data.path}`; return
        - focusSelf({ nativeEvent: e.nativeEvent })} src={`${RPC_URL(__HOST__)}${data.path}`} loading="lazy" decoding="async" /> + focusSelf({ nativeEvent: e.nativeEvent })} src={url} loading="lazy" decoding="async" />
        data.onAction?.({ event: e.nativeEvent, focusKey })}>
        ; } @@ -59,8 +61,9 @@ function Preview (data: { id: string; screenshots?: string[]; preview: number; s } } ], [data.preview, focusKey, data.screenshots?.length ?? 0]); + const url = isUrl(data.screenshots?.[data.preview]) ? data.screenshots?.[data.preview] : `${RPC_URL(__HOST__)}${data.screenshots?.[data.preview]}`; - return ; + return ; } export default function Screenshots (data: { screenshots?: string[]; className?: string; } & FocusParams) diff --git a/src/mainview/components/SelectMenu.tsx b/src/mainview/components/SelectMenu.tsx index fa10743..42d21c8 100644 --- a/src/mainview/components/SelectMenu.tsx +++ b/src/mainview/components/SelectMenu.tsx @@ -2,7 +2,7 @@ import { ContextList, DialogEntry, useContextDialog } from "./ContextDialog"; import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts"; import { useMatchRoute, useNavigate, useRouter } from "@tanstack/react-router"; import { getCurrentFocusKey } from "@noriginmedia/norigin-spatial-navigation"; -import { DoorOpen, Gamepad2, Puzzle, RefreshCcw, Settings, Store } from "lucide-react"; +import { DoorOpen, Gamepad2, Home, Puzzle, RefreshCcw, Settings, Store } from "lucide-react"; import { systemApi } from "../scripts/clientApi"; import { FOCUS_KEYS } from "../scripts/types"; @@ -15,7 +15,7 @@ export default function SelectMenu (data: { rootFocusKey: string; }) const options: DialogEntry[] = [ { content: "Home", - icon: , + icon: , action (ctx) { setOpen(false); diff --git a/src/mainview/components/SideFilters.tsx b/src/mainview/components/SideFilters.tsx index 6f99336..930bf2b 100644 --- a/src/mainview/components/SideFilters.tsx +++ b/src/mainview/components/SideFilters.tsx @@ -1,25 +1,25 @@ -import { GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; +import { DownloadsLookupFilter, DownloadsLookupFilterValues, GameListFilterType } from '@simeonradivoev/gameflow-sdk/shared'; import { RoundButton } from "./RoundButton"; import classNames from "classnames"; import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts"; import { useFocusable, FocusContext } from "@noriginmedia/norigin-spatial-navigation"; -import { ArrowDownAz, ClockArrowDown, CalendarArrowDown, Rocket, HardDrive, SortDesc, User, Drama, FunnelX, Store } from "lucide-react"; +import { ArrowDownAz, ClockArrowDown, CalendarArrowDown, Rocket, HardDrive, SortDesc, User, Drama, FunnelX, Store, ArrowUpDown, ArrowDown, ArrowUp } from "lucide-react"; import { sourceIconMap } from "./Constants"; -import { useContextDialog, ContextList, DialogEntry } from "./ContextDialog"; +import { ContextList, DialogEntry } from "./ContextDialog"; import { FrontEndFilterLists } from "@simeonradivoev/gameflow-sdk/shared"; +import { useContext } from 'react'; +import { GlobalDialogContext } from '../scripts/contexts'; function FilterButton (data: { id: string, filters?: GameListFilterType, tooltip: string, icon: any; - dialog: { - setToggle: (focNewSourceFocusKey?: string | undefined) => void; - }; + dialog: (focNewSourceFocusKey: string) => void; isActive: boolean; }) { - const handleAction = () => data.dialog.setToggle(data.id); + const handleAction = () => data.dialog(data.id); useShortcuts(data.id, () => [{ label: data.tooltip, action: handleAction, button: GamePadButtonCode.A }]); return
        ; } +export function SideDownloadFilters (data: { + id: string, + filters?: DownloadsLookupFilter; + setLocalFilter: (filter: DownloadsLookupFilter) => void, + localFilter: DownloadsLookupFilter, + filterValues: DownloadsLookupFilterValues | undefined; +}) +{ + + const { ref, focusKey } = useFocusable({ focusKey: data.id }); + const globalDialog = useContext(GlobalDialogContext); + const orderByDialog = (focusKey: string) => globalDialog.openContext({ + content: ({ + content: o, + selected: data.localFilter.orderBy === o, + id: `sort-by-${o}`, + type: 'primary', + action (ctx) + { + data.setLocalFilter({ ...data.localFilter, orderBy: o }); + ctx.close(); + }, + }))} />, + preferredChildFocusKey: `sort-by-${data.localFilter.orderBy}` + }, focusKey); + + const orderDirectionDialog = (focusKey: string) => globalDialog.openContext({ + content: }, { label: 'desc', icon: }] + .map(o => ({ + content: o.label, + selected: data.localFilter.sortDirection === o.label, + icon: o.icon, + id: `sort-direction-${o.label}`, + type: 'primary', + action (ctx) + { + data.setLocalFilter({ ...data.localFilter, sortDirection: o.label as any }); + ctx.close(); + }, + })) + } />, + preferredChildFocusKey: `sort-direction-${data.localFilter.orderBy}` + }, focusKey); + + const sourceFilterDialog = (focusKey: string) => globalDialog.openContext({ + content: (o => ({ + content: o, + icon: sourceIconMap[o], + selected: data.localFilter.source === o, + id: `source-filter-${o}`, + type: 'primary', + action (ctx) + { + if (ctx.selected) data.setLocalFilter({ ...data.localFilter, source: undefined }); + else data.setLocalFilter({ ...data.localFilter, source: o }); + ctx.close(); + }, + }))} />, + preferredChildFocusKey: `source-filter-${data.localFilter.source}` + }, focusKey); + + return
        + + } /> + } /> + + {!data.filters?.source && + } /> + } + + {Object.values(data.localFilter).some(v => v !== undefined) && + <> +
        + data.setLocalFilter({})} className='p-3 drop-shadow-md!' > + + } +
        +
        ; +} + export default function SideFilters (data: { id: string, filters?: GameListFilterType; @@ -42,96 +125,107 @@ export default function SideFilters (data: { { const { ref, focusKey } = useFocusable({ focusKey: data.id }); + const globalDialog = useContext(GlobalDialogContext); - const orderByDialog = useContextDialog('order-by-dialog', { - content: }, - { stat: "activity", icon: }, - { stat: "added", icon: }, - { stat: "release", icon: }, - ] satisfies { stat: GameListFilterType['orderBy'], icon?: any; }[]) - .map(o => ({ - content: o.stat, - icon: o.icon, - selected: data.localFilter.orderBy === o.stat, - id: `sort-by-${o.stat}`, + const openSourceDialog = (focusKey: string) => + { + globalDialog.openContext({ + content: (o => ({ + content: o, + icon: sourceIconMap[o], + selected: data.localFilter.source === o, + id: `source-filter-${o}`, + type: 'primary', + action (ctx) + { + if (ctx.selected) data.setLocalFilter({ ...data.localFilter, source: undefined }); + else data.setLocalFilter({ ...data.localFilter, source: o }); + ctx.close(); + }, + })).concat({ + content: "Local Only", + icon: , + selected: data.localFilter.localOnly === true, + id: `source-filter-local`, + type: 'primary', + action (ctx) + { + if (ctx.selected) data.setLocalFilter({ ...data.localFilter, localOnly: undefined }); + else data.setLocalFilter({ ...data.localFilter, localOnly: true }); + ctx.close(); + }, + })} />, preferredChildFocusKey: `source-filter-${data.localFilter.source}` + }, focusKey); + }; + + const openGenreDialog = (focusKey: string) => + { + globalDialog.openContext({ + content: ({ + content: g, + selected: data.localFilter.genres?.includes(g), + id: `genre-filter-${g}`, type: 'primary', action (ctx) { - data.setLocalFilter({ ...data.localFilter, orderBy: o.stat }); + if (ctx.selected) data.setLocalFilter({ ...data.localFilter, genres: [...data.localFilter.genres?.filter(genre => genre !== g) ?? []] }); + else data.setLocalFilter({ ...data.localFilter, genres: [...data.localFilter.genres ?? [], g] }); ctx.close(); }, - }))} />, - preferredChildFocusKey: `sort-by-${data.localFilter.orderBy}` - }); + }))} /> + }, focusKey); + }; - const sourceFilterDialog = useContextDialog('source-filter-dialog', { - content: (o => ({ - content: o, - icon: sourceIconMap[o], - selected: data.localFilter.source === o, - id: `source-filter-${o}`, + const openSortingDialog = (focusKey: string) => + { + globalDialog.openContext({ + content: }, + { stat: "activity", icon: }, + { stat: "added", icon: }, + { stat: "release", icon: }, + ] satisfies { stat: GameListFilterType['orderBy'], icon?: any; }[]) + .map(o => ({ + content: o.stat, + icon: o.icon, + selected: data.localFilter.orderBy === o.stat, + id: `sort-by-${o.stat}`, + type: 'primary', + action (ctx) + { + data.setLocalFilter({ ...data.localFilter, orderBy: o.stat }); + ctx.close(); + }, + }))} />, preferredChildFocusKey: `sort-by-${data.localFilter.orderBy}` + }, focusKey); + }; + + const openAgeRatingDialog = (focusKey: string) => + { + globalDialog.openContext({ + content: ({ + content: a, + selected: data.localFilter.age_ratings?.includes(a), + id: `age-rating-filter-${a}`, type: 'primary', action (ctx) { - if (ctx.selected) data.setLocalFilter({ ...data.localFilter, source: undefined }); - else data.setLocalFilter({ ...data.localFilter, source: o }); + if (ctx.selected) data.setLocalFilter({ ...data.localFilter, age_ratings: [...data.localFilter.age_ratings?.filter(age => age !== a) ?? []] }); + else data.setLocalFilter({ ...data.localFilter, age_ratings: [...data.localFilter.age_ratings ?? [], a] }); ctx.close(); }, - })).concat({ - content: "Local Only", - icon: , - selected: data.localFilter.localOnly === true, - id: `source-filter-local`, - type: 'primary', - action (ctx) - { - if (ctx.selected) data.setLocalFilter({ ...data.localFilter, localOnly: undefined }); - else data.setLocalFilter({ ...data.localFilter, localOnly: true }); - ctx.close(); - }, - })} />, - preferredChildFocusKey: `source-filter-${data.localFilter.source}` - }); - - const genreFilterDialog = useContextDialog('genre-filter-dialog', { - content: ({ - content: g, - selected: data.localFilter.genres?.includes(g), - id: `genre-filter-${g}`, - type: 'primary', - action (ctx) - { - if (ctx.selected) data.setLocalFilter({ ...data.localFilter, genres: [...data.localFilter.genres?.filter(genre => genre !== g) ?? []] }); - else data.setLocalFilter({ ...data.localFilter, genres: [...data.localFilter.genres ?? [], g] }); - ctx.close(); - }, - }))} /> - }); - - const ageRatingFilterDialog = useContextDialog('age-rating-filter-dialog', { - content: ({ - content: a, - selected: data.localFilter.age_ratings?.includes(a), - id: `age-rating-filter-${a}`, - type: 'primary', - action (ctx) - { - if (ctx.selected) data.setLocalFilter({ ...data.localFilter, age_ratings: [...data.localFilter.age_ratings?.filter(age => age !== a) ?? []] }); - else data.setLocalFilter({ ...data.localFilter, age_ratings: [...data.localFilter.age_ratings ?? [], a] }); - ctx.close(); - }, - }))} /> - }); + }))} /> + }, focusKey); + }; return
        - } /> - 0} icon={} /> - 0} icon={} /> + } /> + 0} icon={} /> + 0} icon={} /> {!data.filters?.source && - } /> + } /> } {Object.values(data.localFilter).some(v => v !== undefined) && <> @@ -139,10 +233,6 @@ export default function SideFilters (data: { data.setLocalFilter({})} className='p-3 drop-shadow-md!' > } - {orderByDialog.dialog} - {sourceFilterDialog.dialog} - {genreFilterDialog.dialog} - {ageRatingFilterDialog.dialog}
        ; } \ No newline at end of file diff --git a/src/mainview/components/game/ActionButtons.tsx b/src/mainview/components/game/ActionButtons.tsx index 1a60c93..d37ea00 100644 --- a/src/mainview/components/game/ActionButtons.tsx +++ b/src/mainview/components/game/ActionButtons.tsx @@ -30,7 +30,11 @@ function AchievementsInfo (data: { game: FrontEndGameTypeDetailed; } & InteractP ; } -export default function ActionButtons (data: { game?: FrontEndGameTypeDetailed, source: string, id: string; }) +export default function ActionButtons (data: { + game?: FrontEndGameTypeDetailed, + source: string, + id: string; +}) { const [, setDetailsSection] = useLocalStorage('details-section', 'screenshots'); const navigate = useNavigate(); diff --git a/src/mainview/components/game/MainActions.tsx b/src/mainview/components/game/MainActions.tsx index 20bb27b..6f772af 100644 --- a/src/mainview/components/game/MainActions.tsx +++ b/src/mainview/components/game/MainActions.tsx @@ -1,21 +1,20 @@ import { rommApi } from "@/mainview/scripts/clientApi"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { JSX, useEffect, useRef, useState } from "react"; +import { JSX, useContext, useEffect, useRef, useState } from "react"; import { getErrorMessage } from "react-error-boundary"; import toast from "react-hot-toast"; import { useLocalStorage } from "usehooks-ts"; -import { ContextList, DialogEntry, useContextDialog } from "../ContextDialog"; +import { ContextList, DialogEntry } from "../ContextDialog"; import { Clock, Crosshair, Download, EllipsisVertical, Import, PackageOpen, Play, TriangleAlert } from "lucide-react"; import { gameInvalidationQuery, installMutation, playMutation } from "@/mainview/scripts/queries/romm"; import ActionButton from "./ActionButton"; -import { useRouter } from "@tanstack/react-router"; +import { useNavigate, UseNavigateResult, useRouter } from "@tanstack/react-router"; import { GamePadButtonCode, Shortcut, useShortcuts } from "@/mainview/scripts/shortcuts"; import { CommandEntry, FrontEndGameTypeDetailed, DownloadSourceType } from "@simeonradivoev/gameflow-sdk/shared"; +import { GlobalDialogContext } from "@/mainview/scripts/contexts"; -export default function MainActions (data: { game?: FrontEndGameTypeDetailed, source: string, id: string; }) +export function usePlayMutation (navigate: UseNavigateResult) { - const installMut = useMutation(installMutation(data.source, data.id)); - const router = useRouter(); const playMut = useMutation({ ...playMutation, onError (error) { @@ -23,9 +22,36 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so }, onSuccess (data, { source, id }, onMutateResult, context) { - router.navigate({ to: '/launcher/$source/$id', params: { source: source, id: id } }); + navigate({ to: '/launcher/$source/$id', params: { source: source, id: id } }); }, }); + + return playMut; +} + +export function playGame (source: string, id: string, cmd: CommandEntry, navigate: UseNavigateResult, playMutation: (options: { source: string, id: string, command_id: string | number; }) => void) +{ + if (cmd.emulator === 'EMULATORJS') + { + const params = new URLSearchParams(Array.isArray(cmd.command) ? cmd.command[0] : cmd.command); + navigate({ to: '/embedded/$source/$id', params: { source: source, id: id }, search: Object.fromEntries(params.entries()) }); + } else + { + playMutation({ source: source, id: id, command_id: cmd.id }); + } +} + +export default function MainActions (data: { + game?: FrontEndGameTypeDetailed, + source: string, + id: string; +}) +{ + const installMut = useMutation(installMutation(data.source, data.id)); + const router = useRouter(); + + const navigate = useNavigate(); + const globalDialog = useContext(GlobalDialogContext); const ws = useRef<{ send: (data: string) => void; }>(undefined); const [progress, setProgress] = useState(undefined); const [status, setStatus] = useState(undefined); @@ -42,7 +68,7 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so if (preferredCommand && c.id !== preferredCommand) return false; return true; }); - + const playMut = usePlayMutation(navigate); useEffect(() => { const sub = rommApi.api.romm.status({ source: data.source })({ id: data.id }).subscribe(); @@ -99,32 +125,33 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so } const showProgress = progress !== null && !!progressIcon; - useEffect(() => - { - if (showProgress) return; - showInstallOptions(false); - }, [showProgress]); - const handlePlay = (cmd?: CommandEntry) => - { - if (!cmd) return; - if (cmd.emulator === 'EMULATORJS') - { - const params = new URLSearchParams(Array.isArray(cmd.command) ? cmd.command[0] : cmd.command); - router.navigate({ to: '/embedded/$source/$id', params: { source: data.source, id: data.id }, search: Object.fromEntries(params.entries()) }); - } else - { - playMut.mutate({ source: data.source, id: data.id, command_id: cmd.id }); - } - }; + let mainButton: any | undefined = undefined; let showAllCommandsAction: ((focusKey: string) => void) | undefined; let mainAction: () => void; if (status === 'installed') { - if (validCommands.length > 1) showAllCommandsAction = (focusKey) => showAllCommands(true, focusKey); - mainAction = () => handlePlay(validDefaultCommand); + if (validCommands.length > 1) showAllCommandsAction = (focusKey) => globalDialog.openContext({ + content: + { + const commands: DialogEntry = { + id: String(c.id), + content: c.label ?? "", + type: 'primary', + selected: preferredCommand !== undefined ? preferredCommand === c.id : i === 0, + action (ctx) + { + setPreferredCommand(c.id); + playGame(data.source, data.id, c, navigate, playMut.mutate); + }, + }; + return commands; + })} />, + preferredChildFocusKey: String(preferredCommand) + }, focusKey); + mainAction = () => validDefaultCommand ? playGame(data.source, data.id, validDefaultCommand, navigate, playMut.mutate) : undefined; mainButton =
        1) { - showInstallSource(true, 'mainAction'); + globalDialog.openContext({ + content: ({ + content: s.name, + action (ctx) + { + installMut.mutate({ downloadId: s.id }); + ctx.close(); + }, + type: 'primary', + id: s.id + } satisfies DialogEntry)) ?? []} /> + }, 'mainAction'); } else { installMut.mutate({}); @@ -222,55 +260,21 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so return shortcuts; }, [showAllCommandsAction, mainAction]); - const { dialog: allCommandDialog, setOpen: showAllCommands } = useContextDialog('all-commands-dialog', { - content: - { - const commands: DialogEntry = { - id: String(c.id), - content: c.label ?? "", - type: 'primary', - selected: preferredCommand !== undefined ? preferredCommand === c.id : i === 0, - action (ctx) - { - setPreferredCommand(c.id); - handlePlay(c); - }, - }; - return commands; - })} />, - preferredChildFocusKey: String(preferredCommand) - }); - - const { dialog: installOptionsDialog, setOpen: showInstallOptions } = useContextDialog('install-options-dialog', { - content: - }); - - const { dialog: installSourcesDialog, setOpen: showInstallSource } = useContextDialog('install-source-dialog', { - content: ({ - content: s.name, - action (ctx) - { - installMut.mutate({ downloadId: s.id }); - ctx.close(); - }, - type: 'primary', - id: s.id - } satisfies DialogEntry)) ?? []} /> - }); - return
        {mainButton}
        - {showProgress && showInstallOptions(true, "progress")} key="progress" square tooltip={details} type="base" id="progress" > + {showProgress && globalDialog.openContext({ + content: + }, "progress")} key="progress" square tooltip={details} type="base" id="progress" >
        {progressIcon} @@ -278,8 +282,5 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so
        } - {installSourcesDialog} - {installOptionsDialog} - {allCommandDialog}
        ; } \ No newline at end of file diff --git a/src/mainview/components/options/Button.tsx b/src/mainview/components/options/Button.tsx index de07bdf..e131123 100644 --- a/src/mainview/components/options/Button.tsx +++ b/src/mainview/components/options/Button.tsx @@ -12,7 +12,7 @@ import { oneShot } from "@/mainview/scripts/audio/audio"; export type ButtonStyle = 'base' | 'accent' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'; const styles = { - base: 'dark:bg-base-200 light:bg-base-300 text-base-content active:not-disabled:bg-base-300! active:not-disabled:text-base-content! active:not-disabled:ring-offset-base-content', + base: 'dark:bg-base-200 light:bg-base-100 text-base-content active:not-disabled:bg-base-300! active:not-disabled:text-base-content! active:not-disabled:ring-offset-base-content', accent: "bg-accent text-accent-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:ring-offset-accent", primary: "bg-primary text-primary-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:not-disabled:ring-offset-primary", secondary: "bg-secondary text-secondary-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:not-disabled:ring-offset-secondary", @@ -22,6 +22,17 @@ const styles = { error: "bg-error text-error-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:not-disabled:ring-offset-error", }; +const externalStyles = { + base: '', + accent: "focusable-accent", + primary: "focusable-primary", + secondary: "focusable-secondary", + info: "focusable-info", + success: "focusable-success", + warning: "focusable-warning", + error: "focusable-error", +}; + export function Button (data: { id: string, children?: any, @@ -64,9 +75,9 @@ export function Button (data: { className={twMerge("flex items-center justify-center px-4 py-2 disabled:bg-base-200/40 disabled:text-base-content/40 not-disabled:cursor-pointer rounded-3xl md:text-lg not-control-mouse:focused:drop-shadow-lg border border-base-content/5 not-control-mouse:focused:bg-base-content not-control-mouse:focused:text-base-100 control-mouse:hover:not-disabled:bg-base-content control-mouse:hover:not-disabled:text-base-100 active:not-disabled:transition-none active:not-disabled:ring-offset-4", styles[data.style ?? 'base'], focused ? data.focusClassName : undefined, + data.external ? `focusable focusable-hover ${externalStyles[data.style as keyof typeof externalStyles]}` : '', classNames({ - "btn-accent": focused, - "focusable focusable-primary focusable-hover": data.external + "btn-accent": focused }, data.className))} type={data.type ?? 'button'} > diff --git a/src/mainview/components/store/StoreEmulatorCard.tsx b/src/mainview/components/store/StoreEmulatorCard.tsx index 8645a01..3479579 100644 --- a/src/mainview/components/store/StoreEmulatorCard.tsx +++ b/src/mainview/components/store/StoreEmulatorCard.tsx @@ -6,11 +6,15 @@ import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts"; import { CircleFadingArrowUp, FileQuestion, IceCream2, Package, Store, WandSparkles } from "lucide-react"; import { FOCUS_KEYS } from "@/mainview/scripts/types"; import { FlatpackIcon } from "@/mainview/scripts/brandIcons"; -import { JSX } from "react"; +import { JSX, useContext } from "react"; import { oneShot } from "@/mainview/scripts/audio/audio"; import { useQuery } from "@tanstack/react-query"; import { getUpdateInfoForEmulator } from "@/mainview/scripts/queries/store"; import { FrontEndEmulator } from "@simeonradivoev/gameflow-sdk/shared"; +import { rommApi } from "@/mainview/scripts/clientApi"; +import { useNavigate } from "@tanstack/react-router"; +import { GlobalDialogContext } from "@/mainview/scripts/contexts"; +import { ContextList, DialogEntry } from "../ContextDialog"; export const emulatorStatusIcons: Record = { store: , @@ -28,6 +32,7 @@ export function StoreEmulatorCard (data: { className?: string; }) { + const navigate = useNavigate(); const handleSelect = () => { data.onSelect?.(data.emulator.name, focusKey); @@ -45,7 +50,32 @@ export function StoreEmulatorCard (data: { const { data: updateInfo } = useQuery(getUpdateInfoForEmulator(data.emulator.name)); - useShortcuts(focusKey, () => [{ button: GamePadButtonCode.A, label: "Details", action: handleSelect }], [handleSelect]); + const globalDialogContext = useContext(GlobalDialogContext); + useShortcuts(focusKey, () => [{ + button: GamePadButtonCode.A, + label: "Details", + action: handleSelect + + }, { + button: GamePadButtonCode.Y, + label: "Launch Emulator", + action: e => + { + const entries: DialogEntry[] = data.emulator.validSources.filter(s => s.exists).map(s => ({ + content: `Launch: ${s.type}`, + type: 'primary', + icon: emulatorStatusIcons[s.type], + action (ctx) + { + if (!data.emulator) return; + rommApi.api.romm.game({ source: 'emulator' })({ id: data.emulator.name }).play.post({ command_id: s.type }); + ctx.close(); + navigate({ to: '/launcher/$source/$id', params: { source: 'emulator', id: data.emulator.name } }); + }, id: `open-${s.type}` + } satisfies DialogEntry)); + globalDialogContext.openContext({ content: }, focusKey); + } + }], [handleSelect]); return (
        SettingsRouteRoute, } as any) +const SettingsTasksRoute = SettingsTasksRouteImport.update({ + id: '/tasks', + path: '/tasks', + getParentRoute: () => SettingsRouteRoute, +} as any) const SettingsPluginsRoute = SettingsPluginsRouteImport.update({ id: '/plugins', path: '/plugins', @@ -115,6 +123,11 @@ const StoreTabEmulatorsRoute = StoreTabEmulatorsRouteImport.update({ path: '/emulators', getParentRoute: () => StoreTabRouteRoute, } as any) +const StoreTabDownloadRoute = StoreTabDownloadRouteImport.update({ + id: '/download', + path: '/download', + getParentRoute: () => StoreTabRouteRoute, +} as any) const SettingsPluginSourceRoute = SettingsPluginSourceRouteImport.update({ id: '/plugin/$source', path: '/plugin/$source', @@ -160,6 +173,12 @@ const GameUpdateSourceIdRoute = GameUpdateSourceIdRouteImport.update({ path: '/game/update/$source/$id', getParentRoute: () => rootRouteImport, } as any) +const StoreDetailsDownloadSourceIdRoute = + StoreDetailsDownloadSourceIdRouteImport.update({ + id: '/store/details/download/$source/$id', + path: '/store/details/download/$source/$id', + getParentRoute: () => rootRouteImport, + } as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -173,6 +192,7 @@ export interface FileRoutesByFullPath { '/settings/emulators': typeof SettingsEmulatorsRoute '/settings/interface': typeof SettingsInterfaceRoute '/settings/plugins': typeof SettingsPluginsRoute + '/settings/tasks': typeof SettingsTasksRoute '/settings/update': typeof SettingsUpdateRoute '/collection/$source/$id': typeof CollectionSourceIdRoute '/embedded/$source/$id': typeof EmbeddedSourceIdRoute @@ -180,6 +200,7 @@ export interface FileRoutesByFullPath { '/launcher/$source/$id': typeof LauncherSourceIdRoute '/platform/$source/$id': typeof PlatformSourceIdRoute '/settings/plugin/$source': typeof SettingsPluginSourceRoute + '/store/tab/download': typeof StoreTabDownloadRoute '/store/tab/emulators': typeof StoreTabEmulatorsRoute '/store/tab/games': typeof StoreTabGamesRoute '/store/tab/plugins': typeof StoreTabPluginsRoute @@ -187,6 +208,7 @@ export interface FileRoutesByFullPath { '/game/update/$source/$id': typeof GameUpdateSourceIdRoute '/store/details/emulator/$id': typeof StoreDetailsEmulatorIdRoute '/store/details/plugin/$id': typeof StoreDetailsPluginIdRoute + '/store/details/download/$source/$id': typeof StoreDetailsDownloadSourceIdRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -199,6 +221,7 @@ export interface FileRoutesByTo { '/settings/emulators': typeof SettingsEmulatorsRoute '/settings/interface': typeof SettingsInterfaceRoute '/settings/plugins': typeof SettingsPluginsRoute + '/settings/tasks': typeof SettingsTasksRoute '/settings/update': typeof SettingsUpdateRoute '/collection/$source/$id': typeof CollectionSourceIdRoute '/embedded/$source/$id': typeof EmbeddedSourceIdRoute @@ -206,6 +229,7 @@ export interface FileRoutesByTo { '/launcher/$source/$id': typeof LauncherSourceIdRoute '/platform/$source/$id': typeof PlatformSourceIdRoute '/settings/plugin/$source': typeof SettingsPluginSourceRoute + '/store/tab/download': typeof StoreTabDownloadRoute '/store/tab/emulators': typeof StoreTabEmulatorsRoute '/store/tab/games': typeof StoreTabGamesRoute '/store/tab/plugins': typeof StoreTabPluginsRoute @@ -213,6 +237,7 @@ export interface FileRoutesByTo { '/game/update/$source/$id': typeof GameUpdateSourceIdRoute '/store/details/emulator/$id': typeof StoreDetailsEmulatorIdRoute '/store/details/plugin/$id': typeof StoreDetailsPluginIdRoute + '/store/details/download/$source/$id': typeof StoreDetailsDownloadSourceIdRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -227,6 +252,7 @@ export interface FileRoutesById { '/settings/emulators': typeof SettingsEmulatorsRoute '/settings/interface': typeof SettingsInterfaceRoute '/settings/plugins': typeof SettingsPluginsRoute + '/settings/tasks': typeof SettingsTasksRoute '/settings/update': typeof SettingsUpdateRoute '/collection/$source/$id': typeof CollectionSourceIdRoute '/embedded/$source/$id': typeof EmbeddedSourceIdRoute @@ -234,6 +260,7 @@ export interface FileRoutesById { '/launcher/$source/$id': typeof LauncherSourceIdRoute '/platform/$source/$id': typeof PlatformSourceIdRoute '/settings/plugin/$source': typeof SettingsPluginSourceRoute + '/store/tab/download': typeof StoreTabDownloadRoute '/store/tab/emulators': typeof StoreTabEmulatorsRoute '/store/tab/games': typeof StoreTabGamesRoute '/store/tab/plugins': typeof StoreTabPluginsRoute @@ -241,6 +268,7 @@ export interface FileRoutesById { '/game/update/$source/$id': typeof GameUpdateSourceIdRoute '/store/details/emulator/$id': typeof StoreDetailsEmulatorIdRoute '/store/details/plugin/$id': typeof StoreDetailsPluginIdRoute + '/store/details/download/$source/$id': typeof StoreDetailsDownloadSourceIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -256,6 +284,7 @@ export interface FileRouteTypes { | '/settings/emulators' | '/settings/interface' | '/settings/plugins' + | '/settings/tasks' | '/settings/update' | '/collection/$source/$id' | '/embedded/$source/$id' @@ -263,6 +292,7 @@ export interface FileRouteTypes { | '/launcher/$source/$id' | '/platform/$source/$id' | '/settings/plugin/$source' + | '/store/tab/download' | '/store/tab/emulators' | '/store/tab/games' | '/store/tab/plugins' @@ -270,6 +300,7 @@ export interface FileRouteTypes { | '/game/update/$source/$id' | '/store/details/emulator/$id' | '/store/details/plugin/$id' + | '/store/details/download/$source/$id' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -282,6 +313,7 @@ export interface FileRouteTypes { | '/settings/emulators' | '/settings/interface' | '/settings/plugins' + | '/settings/tasks' | '/settings/update' | '/collection/$source/$id' | '/embedded/$source/$id' @@ -289,6 +321,7 @@ export interface FileRouteTypes { | '/launcher/$source/$id' | '/platform/$source/$id' | '/settings/plugin/$source' + | '/store/tab/download' | '/store/tab/emulators' | '/store/tab/games' | '/store/tab/plugins' @@ -296,6 +329,7 @@ export interface FileRouteTypes { | '/game/update/$source/$id' | '/store/details/emulator/$id' | '/store/details/plugin/$id' + | '/store/details/download/$source/$id' id: | '__root__' | '/' @@ -309,6 +343,7 @@ export interface FileRouteTypes { | '/settings/emulators' | '/settings/interface' | '/settings/plugins' + | '/settings/tasks' | '/settings/update' | '/collection/$source/$id' | '/embedded/$source/$id' @@ -316,6 +351,7 @@ export interface FileRouteTypes { | '/launcher/$source/$id' | '/platform/$source/$id' | '/settings/plugin/$source' + | '/store/tab/download' | '/store/tab/emulators' | '/store/tab/games' | '/store/tab/plugins' @@ -323,6 +359,7 @@ export interface FileRouteTypes { | '/game/update/$source/$id' | '/store/details/emulator/$id' | '/store/details/plugin/$id' + | '/store/details/download/$source/$id' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -339,6 +376,7 @@ export interface RootRouteChildren { GameUpdateSourceIdRoute: typeof GameUpdateSourceIdRoute StoreDetailsEmulatorIdRoute: typeof StoreDetailsEmulatorIdRoute StoreDetailsPluginIdRoute: typeof StoreDetailsPluginIdRoute + StoreDetailsDownloadSourceIdRoute: typeof StoreDetailsDownloadSourceIdRoute } declare module '@tanstack/react-router' { @@ -371,6 +409,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SettingsUpdateRouteImport parentRoute: typeof SettingsRouteRoute } + '/settings/tasks': { + id: '/settings/tasks' + path: '/tasks' + fullPath: '/settings/tasks' + preLoaderRoute: typeof SettingsTasksRouteImport + parentRoute: typeof SettingsRouteRoute + } '/settings/plugins': { id: '/settings/plugins' path: '/plugins' @@ -455,6 +500,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof StoreTabEmulatorsRouteImport parentRoute: typeof StoreTabRouteRoute } + '/store/tab/download': { + id: '/store/tab/download' + path: '/download' + fullPath: '/store/tab/download' + preLoaderRoute: typeof StoreTabDownloadRouteImport + parentRoute: typeof StoreTabRouteRoute + } '/settings/plugin/$source': { id: '/settings/plugin/$source' path: '/plugin/$source' @@ -518,6 +570,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof GameUpdateSourceIdRouteImport parentRoute: typeof rootRouteImport } + '/store/details/download/$source/$id': { + id: '/store/details/download/$source/$id' + path: '/store/details/download/$source/$id' + fullPath: '/store/details/download/$source/$id' + preLoaderRoute: typeof StoreDetailsDownloadSourceIdRouteImport + parentRoute: typeof rootRouteImport + } } } @@ -528,6 +587,7 @@ interface SettingsRouteRouteChildren { SettingsEmulatorsRoute: typeof SettingsEmulatorsRoute SettingsInterfaceRoute: typeof SettingsInterfaceRoute SettingsPluginsRoute: typeof SettingsPluginsRoute + SettingsTasksRoute: typeof SettingsTasksRoute SettingsUpdateRoute: typeof SettingsUpdateRoute SettingsPluginSourceRoute: typeof SettingsPluginSourceRoute } @@ -539,6 +599,7 @@ const SettingsRouteRouteChildren: SettingsRouteRouteChildren = { SettingsEmulatorsRoute: SettingsEmulatorsRoute, SettingsInterfaceRoute: SettingsInterfaceRoute, SettingsPluginsRoute: SettingsPluginsRoute, + SettingsTasksRoute: SettingsTasksRoute, SettingsUpdateRoute: SettingsUpdateRoute, SettingsPluginSourceRoute: SettingsPluginSourceRoute, } @@ -548,6 +609,7 @@ const SettingsRouteRouteWithChildren = SettingsRouteRoute._addFileChildren( ) interface StoreTabRouteRouteChildren { + StoreTabDownloadRoute: typeof StoreTabDownloadRoute StoreTabEmulatorsRoute: typeof StoreTabEmulatorsRoute StoreTabGamesRoute: typeof StoreTabGamesRoute StoreTabPluginsRoute: typeof StoreTabPluginsRoute @@ -555,6 +617,7 @@ interface StoreTabRouteRouteChildren { } const StoreTabRouteRouteChildren: StoreTabRouteRouteChildren = { + StoreTabDownloadRoute: StoreTabDownloadRoute, StoreTabEmulatorsRoute: StoreTabEmulatorsRoute, StoreTabGamesRoute: StoreTabGamesRoute, StoreTabPluginsRoute: StoreTabPluginsRoute, @@ -579,6 +642,7 @@ const rootRouteChildren: RootRouteChildren = { GameUpdateSourceIdRoute: GameUpdateSourceIdRoute, StoreDetailsEmulatorIdRoute: StoreDetailsEmulatorIdRoute, StoreDetailsPluginIdRoute: StoreDetailsPluginIdRoute, + StoreDetailsDownloadSourceIdRoute: StoreDetailsDownloadSourceIdRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/src/mainview/index.css b/src/mainview/index.css index 4c82b71..332862e 100644 --- a/src/mainview/index.css +++ b/src/mainview/index.css @@ -9,6 +9,7 @@ @theme { --breakpoint-sm: 0px; --breakpoint-md: 1024px; + --breakpoint-lg: 1280px; --page-scroll-bg: transparent; --animation-size: 1; diff --git a/src/mainview/index.tsx b/src/mainview/index.tsx index f5639f9..166cc4f 100644 --- a/src/mainview/index.tsx +++ b/src/mainview/index.tsx @@ -8,7 +8,7 @@ import RouterProvider, } from "@tanstack/react-router"; import { routeTree } from "./gen/routeTree.gen"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { QueryClient } from "@tanstack/react-query"; import "./scripts/gamepads"; import "./scripts/windowEvents"; import "./scripts/spatialNavigation"; @@ -16,6 +16,16 @@ import NotFound from "./components/NotFound"; import Error from "./components/Error"; import serviceWorker from './scripts/serviceWorker?worker&url'; import App from "./App"; +import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'; +import { createStore, get, set, del } from "idb-keyval"; +import +{ + PersistedClient, + Persister, +} from '@tanstack/react-query-persist-client'; +import pkg from '../../package.json'; + +const idbStore = createStore("tanstack-query", "cache"); if ('serviceWorker' in navigator) { @@ -24,7 +34,31 @@ if ('serviceWorker' in navigator) const hashHistory = createHashHistory({}); -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + gcTime: 1000 * 60 * 60 * 24 * 5, // 5 days + } + } +}); + +export function createIDBPersister (idbValidKey: IDBValidKey = 'reactQuery'): Persister +{ + return { + persistClient: async (client: PersistedClient) => + { + await set(idbValidKey, client, idbStore); + }, + restoreClient: async () => + { + return await get(idbValidKey, idbStore); + }, + removeClient: async () => + { + await del(idbValidKey, idbStore); + }, + } satisfies Persister; +} export interface RouterContext { @@ -74,9 +108,9 @@ if (!rootElement.innerHTML) root.render( - + - + , ); diff --git a/src/mainview/routes/__root.tsx b/src/mainview/routes/__root.tsx index fbe2f26..cafbab4 100644 --- a/src/mainview/routes/__root.tsx +++ b/src/mainview/routes/__root.tsx @@ -8,6 +8,7 @@ import { useEffect } from "react"; import AppCommunication from "../components/AppCommunication"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; +import GlobalContextDialog from "../components/GlobalContextDialog"; export const Route = createRootRouteWithContext()({ component: RootComponent, @@ -39,9 +40,11 @@ function RootComponent () return (
        - - - + + + + + {queryDevOptions && } diff --git a/src/mainview/routes/game/$source.$id.tsx b/src/mainview/routes/game/$source.$id.tsx index 761e9ea..13ea8ac 100644 --- a/src/mainview/routes/game/$source.$id.tsx +++ b/src/mainview/routes/game/$source.$id.tsx @@ -33,7 +33,9 @@ export const Route = createFileRoute("/game/$source/$id")({ }, component: RouteComponent, errorComponent: Error, - validateSearch: zodValidator(z.object({ focus: z.string().optional() })), + validateSearch: zodValidator(z.object({ + focus: z.string().optional(), + })), staticData: { enterSound: 'openDetails', goBackSound: "returnDetails" diff --git a/src/mainview/routes/game/add.tsx b/src/mainview/routes/game/add.tsx index 3a6a2f8..6399cd0 100644 --- a/src/mainview/routes/game/add.tsx +++ b/src/mainview/routes/game/add.tsx @@ -8,15 +8,18 @@ import { PathSettingsOptionBase } from '@/mainview/components/options/PathSettin import SelectMenu from '@/mainview/components/SelectMenu'; import { FloatingShortcuts } from '@/mainview/components/Shortcuts'; import { oneShot } from '@/mainview/scripts/audio/audio'; +import { rommApi } from '@/mainview/scripts/clientApi'; import { addManualGameMutation, allGamesInvalidateQuery, gameLookupDetails, platformLookupMatchQuery } from '@/mainview/scripts/queries/romm'; import { GamePadButtonCode, useShortcuts } from '@/mainview/scripts/shortcuts'; import { HandleGoBack } from '@/mainview/scripts/utils'; +import { isUrl } from '@/shared/utils'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { createFileRoute, useNavigate, useRouter } from '@tanstack/react-router'; import { zodValidator } from '@tanstack/zod-adapter'; -import { ArrowBigRightDash, Check, CirclePlus, CircleQuestionMark, CircleX, FileSearch, FolderOpen, HardDrive } from 'lucide-react'; +import { ArrowBigRightDash, Check, CirclePlus, CircleQuestionMark, CircleX, File, FileSearch, FolderOpen, Globe, HardDrive, Link, Save } from 'lucide-react'; import { basename } from 'pathe'; +import prettyBytes from 'pretty-bytes'; import { JSX, useState } from 'react'; import toast from 'react-hot-toast'; import { twMerge } from 'tailwind-merge'; @@ -39,6 +42,7 @@ export const Route = createFileRoute('/game/add')({ function FileSelectionField (data: { location: string | undefined, setLocation: (location: string | undefined) => void; }) { const [localLocation, setLocalLocation] = useState(data.location); + const navigate = useNavigate(); return ; + > + + ; } const TAG_REGEX = /\(([^)]+)\)|\[([^\]]+)\]/g; @@ -95,6 +101,17 @@ function Overview (data: {}) const navigate = useNavigate(); const router = useRouter(); const state = Route.useSearch(); + const linkInfo = useQuery({ + enabled (query) + { + return isUrl(query.queryKey[1]); + }, + queryKey: ['dl-link-info', state.gameLocation], + queryFn: async () => + { + return rommApi.api.romm.download.file.info.get({ query: { file_url: state.gameLocation! } }); + } + }); const { data: game } = useQuery(gameLookupDetails(state.selectedGame?.source, state.selectedGame?.id)); const { data: platform } = useQuery(platformLookupMatchQuery(state.selectedGame?.source, state.platformId)); const addGame = useMutation({ @@ -105,7 +122,7 @@ function Overview (data: {}) }, async onSuccess (data, variables, onMutateResult, context) { - if (data.id === null) return; + if (data.id === null || isUrl(state.gameLocation)) return; await context.client.invalidateQueries(allGamesInvalidateQuery); navigate({ to: '/game/$source/$id', params: { @@ -136,7 +153,13 @@ function Overview (data: {})
        {platform?.match.type}
        -
        {state.gameLocation}
        +
        {isUrl(state.gameLocation) ? : }{state.gameLocation}
        +
        + + {linkInfo.isFetching ? : (linkInfo.data?.data?.size && prettyBytes(linkInfo.data.data.size))} + + {linkInfo.isFetching ? : (linkInfo.data?.data?.content_type && linkInfo.data.data.content_type)} +
        Actions
        @@ -150,6 +173,11 @@ function Overview (data: {}) gamePath: state.gameLocation, platformId: state.platformId }); + if (isUrl(state.gameLocation)) + { + navigate({ to: '/settings/tasks' }); + } + }} > Add Game
        ; } diff --git a/src/mainview/routes/games.tsx b/src/mainview/routes/games.tsx index cd1fe45..bd0fc8e 100644 --- a/src/mainview/routes/games.tsx +++ b/src/mainview/routes/games.tsx @@ -31,7 +31,7 @@ function RouteComponent () return + [ { navigate({ to: '/game/add' }); }} >, diff --git a/src/mainview/routes/index.tsx b/src/mainview/routes/index.tsx index a9d33c1..8ae562a 100644 --- a/src/mainview/routes/index.tsx +++ b/src/mainview/routes/index.tsx @@ -14,6 +14,7 @@ import import { createFileRoute, + useNavigate, useRouter, } from "@tanstack/react-router"; import { useMutation, useQueryClient } from "@tanstack/react-query"; @@ -40,7 +41,7 @@ import z from "zod"; import CollectionList from "../components/CollectionList"; import { zodValidator } from '@tanstack/zod-adapter'; import { mobileCheck, scrollIntoViewHandler, useDragScroll } from "../scripts/utils"; -import { AnimatedBackgroundContext } from "../scripts/contexts"; +import { AnimatedBackgroundContext, GlobalDialogContext } from "../scripts/contexts"; import Carousel from "../components/Carousel"; import { closeMutation } from "@queries/system"; import { gameQuery } from "../scripts/queries/romm"; @@ -51,6 +52,10 @@ import HeaderSearchField from "../components/HeaderSearchField"; import CardElement from "../components/CardElement"; import { Router } from ".."; import { FrontEndId } from "@simeonradivoev/gameflow-sdk/shared"; +import { playGame, usePlayMutation } from "../components/game/MainActions"; +import { rommApi } from "../scripts/clientApi"; +import { ContextList, DialogEntry } from "../components/ContextDialog"; +import { FOCUS_KEYS } from "../scripts/types"; export const Route = createFileRoute("/")({ component: ConsoleHomeUI, @@ -152,6 +157,9 @@ function HomeList (data: { focusKey: "home-list", preferredChildFocusKey: `${data.selectedFilter}-list` }); + const navigate = useNavigate(); + const playGameMut = usePlayMutation(navigate); + const globalDialog = useContext(GlobalDialogContext); const handleNodeFocus = (id: string, node: HTMLElement, details: FocusDetails) => { @@ -169,6 +177,52 @@ function HomeList (data: { router.navigate({ to: '/game/$source/$id', params: { id: String(sourceId ?? id.id), source: source ?? id.source } }); }; + async function handleGamePlay (id: FrontEndId, source: string | null, sourceId: string | null) + { + const finalSource = source ?? id.source; + const finalId = String(sourceId ?? id.id); + + const validCommands = await rommApi.api.romm.game({ source: finalSource })({ id: finalId }).commands.get(); + if (validCommands.data) + { + const preferredCommand = localStorage.getItem(`${finalSource}-${finalId}-preferred-command`); + if (preferredCommand) + { + playGame(finalSource, finalId, validCommands.data.commands[JSON.parse(preferredCommand)], navigate, playGameMut.mutate); + } else + { + if (validCommands.data.commands.length > 1) + { + globalDialog.openContext({ + content: + { + const option: DialogEntry = { + id: String(c.id), + content: c.label ?? String(c.id), + type: "primary", + action (ctx) + { + localStorage.setItem(`${finalSource}-${finalId}-preferred-command`, JSON.stringify(i)); + ctx.close(); + playGame(finalSource, finalId, validCommands.data.commands[0], navigate, playGameMut.mutate); + }, + }; + + return option; + }) + } /> + }, FOCUS_KEYS.GAME_LIST_CARD('games-list', id)); + } else if (validCommands.data.commands.length === 1) + { + playGame(finalSource, finalId, validCommands.data.commands[0], navigate, playGameMut.mutate); + } + + } + } + + } + let activeList: JSX.Element; switch (data.selectedFilter) { @@ -190,6 +244,7 @@ function HomeList (data: { activeList = <> { @@ -203,7 +258,7 @@ function HomeList (data: { setBackground={bg.setBackground} filters={{ limit: 12, orderBy: 'activity' }} finalElement={[ - , + , ]} emptyElement={[ diff --git a/src/mainview/routes/platform.$source.$id.tsx b/src/mainview/routes/platform.$source.$id.tsx index f4df81d..bc35faf 100644 --- a/src/mainview/routes/platform.$source.$id.tsx +++ b/src/mainview/routes/platform.$source.$id.tsx @@ -8,8 +8,10 @@ import { zodValidator } from "@tanstack/zod-adapter"; import z from "zod"; import { useLocalStorage } from "usehooks-ts"; import { RefreshCcw, Settings2 } from "lucide-react"; -import { ContextList, DialogEntry, useContextDialog } from "../components/ContextDialog"; +import { ContextList, DialogEntry } from "../components/ContextDialog"; import toast from "react-hot-toast"; +import { useContext } from "react"; +import { GlobalDialogContext } from "../scripts/contexts"; export const Route = createFileRoute("/platform/$source/$id")({ component: RouteComponent, @@ -45,6 +47,7 @@ function RouteComponent () context.client.invalidateQueries(localPlatformFilter(id)); }, }); + const globalDialog = useContext(GlobalDialogContext); const deletePlatform = useMutation({ ...deletePlatformMutation(id), onError (error, variables, onMutateResult, context) @@ -77,7 +80,7 @@ function RouteComponent () if (source === 'local') { settingsOptions.push({ - id: 'update-platform', + id: 'delete-platform', type: "error", content: "Delete", icon: deletePlatform.isPending ? : , @@ -88,10 +91,6 @@ function RouteComponent () }); } - const { dialog: platformSettingsDialog, setOpen: setPlatformSettingsOpen } = useContextDialog('platform-settings-dialog', { - content: - }); - return (
        , action () { - setPlatformSettingsOpen(true, 'open-platform-settings-btn'); + globalDialog.openContext({ content: }, 'open-platform-settings-btn'); }, }]} countHint={countHint} title={} filters={{ platform_id: Number(id), platform_source: source }} /> - {platformSettingsDialog}
        ); } diff --git a/src/mainview/routes/settings/emulators.tsx b/src/mainview/routes/settings/emulators.tsx index 9abddb4..17344df 100644 --- a/src/mainview/routes/settings/emulators.tsx +++ b/src/mainview/routes/settings/emulators.tsx @@ -24,6 +24,7 @@ import { SettingsDropdown } from '@/mainview/components/options/SettingsDropdown import { FrontEndEmulator } from '@simeonradivoev/gameflow-sdk/shared'; import { zodValidator } from '@tanstack/zod-adapter'; import z from 'zod'; +import { isUrl } from '@/shared/utils'; export const Route = createFileRoute('/settings/emulators')({ component: RouteComponent, @@ -238,7 +239,7 @@ function EmulatorBadge (data: { let logoUrl: string | undefined = undefined; if (data.emulator.logo) { - if (data.emulator.logo.startsWith('http')) + if (isUrl(data.emulator.logo)) { logoUrl = data.emulator.logo; } else diff --git a/src/mainview/routes/settings/route.tsx b/src/mainview/routes/settings/route.tsx index fd83578..625e884 100644 --- a/src/mainview/routes/settings/route.tsx +++ b/src/mainview/routes/settings/route.tsx @@ -16,6 +16,7 @@ import classNames from "classnames"; import { ArrowBigLeft, + Cog, FingerprintPattern, HardDrive, Info, @@ -155,6 +156,12 @@ function SettingsMenu (data: {}) label="Plugins" icon={} /> + } + /> ([]); + const [queuedJobs, setQueuedJobs] = useState([]); + const wsRef = useRef<{ send: (data: any) => void; }>(null); + + useEffect(() => + { + const sub = jobsApi.api.jobs.list.subscribe(); + wsRef.current = { + send (data) + { + sub.ws.send(JSON.stringify(data)); + }, + }; + sub.on('message', e => + { + switch (e.data.type) + { + case 'allJobs': + setActiveJobs(e.data.active); + setQueuedJobs(e.data.queued); + break; + + case 'aborted': + const abortedJobId = e.data.id; + setActiveJobs(jobs => jobs.map(j => j.id === abortedJobId ? { ...j, status: 'aborted' } : j)); + setQueuedJobs(jobs => jobs.filter(j => j.id !== abortedJobId)); + break; + + case 'queued': + const queuedJob = e.data.job; + setQueuedJobs(jobs => [...jobs, queuedJob]); + break; + + case 'progress': + const progressJob = e.data.job; + setActiveJobs(jobs => jobs.map(j => j.id === progressJob.id ? progressJob : j)); + break; + + case 'started': + const newJob = e.data.job; + setActiveJobs(jobs => [newJob, ...jobs]); + setQueuedJobs(jobs => jobs.filter(j => j.id !== newJob.id)); + break; + + case 'ended': + const endedJobId = e.data.id; + setActiveJobs(jobs => jobs.filter(j => j.id !== endedJobId)); + break; + } + }); + + return () => + { + sub.close(); + wsRef.current = null; + }; + }, []); + + const handleCancel = (id: string) => + { + wsRef.current?.send({ type: 'cancel', id: id }); + }; + + return
        +
        Active
        +
          + {activeJobs.map((job, i) =>
        • +
          +
          + {job.data.preview_url ? : } +
          +
          {job.data.name ?? job.id}
          +
          +
          +
          +
          +
          {job.state}
          +
          {job.progress.toFixed(1)}%
          +
          + +
          + {job.data.downloaded != null && job.data.total != null &&
          {prettyBytes(job.data.downloaded)}/{prettyBytes(job.data.total)}
          } + {job.data.speed != null &&
          {prettyBytes(job.data.speed)}/s
          } +
          +
          + +
          +
        • )} +
        +
        Queued
        +
          + {queuedJobs.map((job, i) =>
        • +
          +
          +
          {job.data.name ?? job.id}
          +
          +
          +
          +
          + {job.data.total !== undefined &&
          {prettyBytes(job.data.total)}
          } +
          +
          + +
          +
        • )} +
        +
        ; +} diff --git a/src/mainview/routes/store/details.download.$source.$id.tsx b/src/mainview/routes/store/details.download.$source.$id.tsx new file mode 100644 index 0000000..2100442 --- /dev/null +++ b/src/mainview/routes/store/details.download.$source.$id.tsx @@ -0,0 +1,129 @@ +import { AutoFocus } from '@/mainview/components/AutoFocus'; +import DotsLoading from '@/mainview/components/backgrounds/dots'; +import { ContextList, DialogEntry } from '@/mainview/components/ContextDialog'; +import { StickyHeaderUI } from '@/mainview/components/Header'; +import { Button } from '@/mainview/components/options/Button'; +import Screenshots from '@/mainview/components/Screenshots'; +import SelectMenu from '@/mainview/components/SelectMenu'; +import { FloatingShortcuts } from '@/mainview/components/Shortcuts'; +import { GlobalDialogContext } from '@/mainview/scripts/contexts'; +import { downloadLookupQuery } from '@/mainview/scripts/queries/romm'; +import { GamePadButtonCode, useShortcuts } from '@/mainview/scripts/shortcuts'; +import { HandleGoBack } from '@/mainview/scripts/utils'; +import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; +import { createFileRoute, useNavigate, useRouter } from '@tanstack/react-router'; +import { Download } from 'lucide-react'; +import prettyBytes from 'pretty-bytes'; +import { useContext } from 'react'; + +export const Route = createFileRoute('/store/details/download/$source/$id')({ + component: RouteComponent, + pendingComponent: Loading, + async loader (ctx) + { + const data = await ctx.context.queryClient.fetchQuery(downloadLookupQuery(decodeURIComponent(ctx.params.source), decodeURIComponent(ctx.params.id))); + return { data }; + } +}); + +function Loading () +{ + const { ref, focusSelf } = useFocusable({ focusKey: 'download-details' }); + return <> + + + ; +} + +const imagesMap = new Set(['JPEG', 'PNG', 'Motion JPEG', 'Item Image']); +const videoFormat = new Set(['h.264']); +const downloadsBlacklist = new Set(['JPEG Thumb', 'Metadata', 'Thumbnail', 'Item Tile', 'Archive BitTorrent', ...videoFormat, ...imagesMap]); + +function Details (data: { onDownload: (focusKey: string) => void; }) +{ + const { data: download } = Route.useLoaderData(); + const screenshots = download.files.filter(f => f.format && imagesMap.has(f.format)).map(f => f.download_url); + if (screenshots.length <= 0 && download.cover_url) screenshots.push(download.cover_url); + return
        + +
        +
        +
        + {!!download.cover_url && } +
        +
        {download.name}
        +
        +
        {download.date?.toDateString()}
        +
        +
        {download.source}
        +
        +
        +
        +
        + +
        +
        +
        + {!!download.summary &&
        +
        +
        } +
        +
        Downloads
        +
          + {download.files.filter(f => f.format && !downloadsBlacklist.has(f.format)).map(f =>
        • + {f.id} + {!!f.size && prettyBytes(f.size)} +
        • )} +
        +
        + +
        +
        +
        ; +} + +function RouteComponent () +{ + const navigate = useNavigate(); + const router = useRouter(); + const { ref, focusKey, focusSelf } = useFocusable({ focusKey: 'download-details', preferredChildFocusKey: 'download-btn' }); + const { data } = Route.useLoaderData(); + const globalDialog = useContext(GlobalDialogContext); + + useShortcuts(focusKey, () => [{ + label: "Return", + action: (e) => HandleGoBack(router, e), + button: GamePadButtonCode.B + }], [router]); + + return
        + + +
        globalDialog.openContext({ + content: f.format && !downloadsBlacklist.has(f.format)).map(f => + { + const option: DialogEntry = { + id: f.id, + content: f.id, + type: 'primary', + action (ctx) + { + navigate({ + to: '/game/add', search: { + gameLocation: f.download_url, + search: data.name, + step: 1 + } + }); + }, + }; + + return option; + })} /> + }, focusKey)} /> + + + + +
        ; +} diff --git a/src/mainview/routes/store/details.emulator.$id.tsx b/src/mainview/routes/store/details.emulator.$id.tsx index 3383ea8..d8c8372 100644 --- a/src/mainview/routes/store/details.emulator.$id.tsx +++ b/src/mainview/routes/store/details.emulator.$id.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useContext, useRef, useState } from "react"; import { useFocusable, @@ -11,7 +11,7 @@ import { AnimatedBackground } from "@/mainview/components/AnimatedBackground"; import { rommApi, systemApi } from "@/mainview/scripts/clientApi"; import { Button } from "@/mainview/components/options/Button"; import { ChevronDown, CircleFadingArrowUp, CloudUpload, Cpu, Download, Fullscreen, Gamepad2, Info, Monitor, Puzzle, Settings, Settings2, Terminal, Trash2, TriangleAlert, WandSparkles } from "lucide-react"; -import { ContextList, DialogEntry, useContextDialog } from "@/mainview/components/ContextDialog"; +import { ContextList, DialogEntry } from "@/mainview/components/ContextDialog"; import { RPC_URL } from "@/shared/constants"; import Screenshots from "@/mainview/components/Screenshots"; import { StickyHeaderUI } from "@/mainview/components/Header"; @@ -30,6 +30,7 @@ import { AutoFocus } from "@/mainview/components/AutoFocus"; import { FilterUI } from "@/mainview/components/Filters"; import Markdown from "react-markdown"; import { FrontEndEmulatorDetailed } from "@simeonradivoev/gameflow-sdk/shared"; +import { GlobalDialogContext } from "@/mainview/scripts/contexts"; export const Route = createFileRoute('/store/details/emulator/$id')({ component: RouteComponent, @@ -65,6 +66,7 @@ function TitleArea (data: { onUpdate: (source: string) => void; }) { + const globalDialog = useContext(GlobalDialogContext); const navigation = useNavigate(); const queryClient = useQueryClient(); const deleteMutation = useMutation({ @@ -253,14 +255,12 @@ function TitleArea (data: { installButtonContent = <>Unsupported; } - const { dialog: installOptionsDialog, setOpen } = useContextDialog("install-context-menu", { - content: - }); + const openOptionsDialog = (focusKey: string) => globalDialog.openContext({ content: }, focusKey); const handleOptionsOpen = () => { if (isInstalling || !data.emulator) return false; - setOpen(true, 'install-btn'); + openOptionsDialog('install-btn'); }; return
        @@ -294,10 +294,10 @@ function TitleArea (data: {
        {(data.emulator?.storeDownloadInfo?.hasUpdate || !data.emulator?.storeDownloadInfo) && installedFromStore && !!updateToVersion &&
        - +
        } {(!data.emulator?.bios || data.emulator.bios.length <= 0) && (data.emulator?.biosRequirement === 'required') && installedFromStore &&
        - +
        }
        - {installOptionsDialog}
        ; } diff --git a/src/mainview/routes/store/tab/download.tsx b/src/mainview/routes/store/tab/download.tsx new file mode 100644 index 0000000..062698a --- /dev/null +++ b/src/mainview/routes/store/tab/download.tsx @@ -0,0 +1,109 @@ +import DotsLoading from '@/mainview/components/backgrounds/dots'; +import LoadMoreButton from '@/mainview/components/LoadMoreButton'; +import { SideDownloadFilters } from '@/mainview/components/SideFilters'; +import { downloadLookupFiltersQuery, downloadsLookupQuery } from '@/mainview/scripts/queries/romm'; +import { scrollIntoViewHandler } from '@/mainview/scripts/utils'; +import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; +import { DownloadLookupEntry, DownloadsLookupFilter } from '@simeonradivoev/gameflow-sdk/shared'; +import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { DownloadIcon, Eye, MessageCircle, Save, Star } from 'lucide-react'; +import prettyBytes from 'pretty-bytes'; +import { useSessionStorage } from 'usehooks-ts'; + +export const Route = createFileRoute('/store/tab/download')({ + component: RouteComponent, +}); + +function Download (data: { focusKey: string, match: DownloadLookupEntry; }) +{ + const navigate = useNavigate(); + const handleAction = () => navigate({ + to: '/store/details/download/$source/$id', params: { + source: encodeURIComponent(data.match.source), + id: encodeURIComponent(data.match.id) + } + }); + const { ref, focusKey } = useFocusable({ + focusKey: data.focusKey, + onFocus: (l, p, d) => scrollIntoViewHandler({ behavior: "smooth", block: "center", inline: "center" })(focusKey, ref.current, d), + onEnterPress: handleAction + }); + return
      • + {!!data.match.cover_url && } +
        +
        {data.match.name}
        +
        {data.match.date?.toDateString()}
        +
          + {!!data.match.size &&
        • {prettyBytes(data.match.size)}
        • } + {!!data.match.download_count &&
        • {data.match.download_count}
        • } + {!!data.match.view_count &&
        • {data.match.view_count}
        • } + {!!data.match.comment_count &&
        • {data.match.comment_count}
        • } + {!!data.match.rating &&
        • {data.match.rating}
        • } +
        +
        +
      • ; +} + +function Downloads (data: { + pages: { + data: DownloadLookupEntry[]; + totalCount: number; + nextPage: number; + }[]; + hasNextPage: boolean, + isFetchingNextPage: boolean, + isFetching: boolean, + fetchNextPage: () => void, + error: string | undefined; +}) +{ + const { ref, focusKey } = useFocusable({ focusKey: 'downloads-list' }); + return
          + + {data.pages.flatMap((page, p) => page.data.map((match, i) => ))} + {data.hasNextPage && + { + if (data.isFetchingNextPage || data.isFetching) + return; + data.fetchNextPage(); + }} />} + {!!data.error} + +
        ; +} + +function RouteComponent () +{ + const [search] = useSessionStorage(`${Route.to}-search`, undefined); + const [filter, setFilter] = useSessionStorage('store-download-lookup-filters', {}); + const { data, error, isPending, isFetching, isFetchingNextPage, fetchNextPage, hasNextPage } = useInfiniteQuery({ + ...downloadsLookupQuery({ ...filter, search }), + maxPages: 10, + refetchOnMount: false + }); + const { ref, focusKey } = useFocusable({ + focusKey: "main-area", + preferredChildFocusKey: "downloads-list" + }); + + const { data: lookupFilters } = useQuery(downloadLookupFiltersQuery); + + return
        + +
        + {isFetching && } + Results + {isPending ? : {data?.pages[0].totalCount}} +
        + {isPending && } + {data && } +
        + +
        + +
        +
        ; +} diff --git a/src/mainview/routes/store/tab/emulators.tsx b/src/mainview/routes/store/tab/emulators.tsx index 0d4ba0e..1fb831f 100644 --- a/src/mainview/routes/store/tab/emulators.tsx +++ b/src/mainview/routes/store/tab/emulators.tsx @@ -11,10 +11,15 @@ import { useQuery } from '@tanstack/react-query'; import { storeEmulatorsQuery } from '@queries/store'; import InvalidStoreError from '@/mainview/components/store/InvalidStoreError'; import { useSessionStorage } from 'usehooks-ts'; +import { zodValidator } from '@tanstack/zod-adapter'; +import z from 'zod'; export const Route = createFileRoute('/store/tab/emulators')({ component: RouteComponent, - errorComponent: InvalidStoreError + errorComponent: InvalidStoreError, + validateSearch: zodValidator(z.object({ + search: z.string().optional() + })) }); function RouteComponent () @@ -26,7 +31,11 @@ function RouteComponent () preferredChildFocusKey: focus }); const storeContext = useContext(StoreContext); - const { data: emulators } = useQuery({ ...storeEmulatorsQuery({ search }), retry: false, throwOnError: true }); + const { data: emulators } = useQuery({ + ...storeEmulatorsQuery({ search }), + retry: false, + throwOnError: true + }); useEffect(() => { @@ -62,6 +71,7 @@ function RouteComponent () /> )) ?? Array.from({ length: 10 }).map((_, i) =>
        )}
        + ; diff --git a/src/mainview/routes/store/tab/games.tsx b/src/mainview/routes/store/tab/games.tsx index 21e059f..9176c20 100644 --- a/src/mainview/routes/store/tab/games.tsx +++ b/src/mainview/routes/store/tab/games.tsx @@ -15,6 +15,7 @@ import { zodValidator } from '@tanstack/zod-adapter'; import z from 'zod'; import SideFilters from '@/mainview/components/SideFilters'; import { gameFiltersQuery } from '@/mainview/scripts/queries/romm'; +import { isUrl } from '@/shared/utils'; export const Route = createFileRoute('/store/tab/games')({ component: RouteComponent, @@ -68,7 +69,7 @@ function RouteComponent () Games
        -
        +
        {data.plugin.package.description}
        -
          {data.plugin.package.keywords.concat(...data.plugin.installed ? ["installed"] : []).map(k =>
        • {k}
        • )}
        +
          {data.plugin.package.keywords.concat(...data.plugin.installed ? ["installed"] : []).map((k, i) =>
        • {k}
        • )}
        • {data.plugin.package.publisher.username}
        • diff --git a/src/mainview/routes/store/tab/route.tsx b/src/mainview/routes/store/tab/route.tsx index 05b4c1e..81a5a01 100644 --- a/src/mainview/routes/store/tab/route.tsx +++ b/src/mainview/routes/store/tab/route.tsx @@ -14,7 +14,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { useMatchRoute, useRouter } from '@tanstack/react-router'; import { createFileRoute, Outlet } from '@tanstack/react-router'; import { zodValidator } from '@tanstack/zod-adapter'; -import { Gamepad2, Home, Joystick, Puzzle } from 'lucide-react'; +import { DownloadCloud, Gamepad2, Home, Joystick, Puzzle } from 'lucide-react'; import { useRef } from 'react'; import { useSessionStorage } from 'usehooks-ts'; import z from 'zod'; @@ -97,6 +97,7 @@ function RouteComponent () home: { label: "Home", icon: , selected: useIsSettings(''), }, emulators: { label: "Emulators", icon: , selected: useIsSettings('emulators') }, games: { label: "Games", icon: , selected: useIsSettings('games') }, + download: { label: "Download", icon: , selected: useIsSettings('download') }, plugins: { label: "Plugins", icon: , selected: useIsSettings('plugins') } }; const [search, setSearch] = useSessionStorage(`${router.history.location.pathname}-search`, undefined); diff --git a/src/mainview/scripts/contexts.ts b/src/mainview/scripts/contexts.ts index 3b33a01..0c958b1 100644 --- a/src/mainview/scripts/contexts.ts +++ b/src/mainview/scripts/contexts.ts @@ -1,4 +1,4 @@ -import { SystemInfoType, Drive } from '@simeonradivoev/gameflow-sdk/shared'; +import { SystemInfoType, Drive, AppInfoContext } from '@simeonradivoev/gameflow-sdk/shared'; import { Direction, FocusDetails } from "@noriginmedia/norigin-spatial-navigation"; import { createContext } from "react"; import { Shortcut } from "./shortcuts"; @@ -45,6 +45,16 @@ export const ShortcutsContext = createContext({} as { export const SystemInfoContext = createContext({} as SystemInfoType | undefined); +export const AppContext = createContext({} as AppInfoContext); + +export const GlobalDialogContext = createContext({} as { + openContext: (options: { + content: any; + preferredChildFocusKey?: string; + onClose?: () => void; + }, focusKey: string) => void; +}); + export const GameDetailsContext = createContext<{ update: () => void; }>({} as any); \ No newline at end of file diff --git a/src/mainview/scripts/queries/romm.ts b/src/mainview/scripts/queries/romm.ts index 63f8623..5c76d2b 100644 --- a/src/mainview/scripts/queries/romm.ts +++ b/src/mainview/scripts/queries/romm.ts @@ -1,7 +1,7 @@ import { DefaultRommStaleTime } from "@/shared/constants"; -import { GameListFilterType, RommLoginDataSchema, FrontEndId } from '@simeonradivoev/gameflow-sdk/shared'; +import { GameListFilterType, RommLoginDataSchema, FrontEndId, DownloadLookupEntry, DownloadsLookupFilter } from '@simeonradivoev/gameflow-sdk/shared'; import { rommApi, settingsApi } from "../clientApi"; -import { InvalidateQueryFilters, mutationOptions, QueryClient, QueryFilters, queryOptions } from "@tanstack/react-query"; +import { infiniteQueryOptions, InvalidateQueryFilters, mutationOptions, QueryClient, QueryFilters, queryOptions } from "@tanstack/react-query"; import z from "zod"; import { statsApiStatsGetOptions } from "@/clients/romm/@tanstack/react-query.gen"; @@ -293,4 +293,36 @@ export const addManualGameMutation = mutationOptions({ if (error) throw error; return data; } +}); + +export const downloadsLookupQuery = (filter: DownloadsLookupFilter) => infiniteQueryOptions<{ data: DownloadLookupEntry[], totalCount: number, nextPage: number; }>({ + initialPageParam: 1, + queryKey: ["downloads", filter], + getNextPageParam: (lastPage, pages) => lastPage.nextPage, + queryFn: async (params) => + { + const pageParam = params.pageParam as number; + const { data, error } = await rommApi.api.romm.downloads.lookup.get({ query: { ...filter, page: pageParam } }); + if (error) throw error; + return { data: data.matches, totalCount: data.totalCount, nextPage: pageParam + 1 }; + } +}); + +export const downloadLookupQuery = (source: string, id: string) => queryOptions({ + queryKey: ["downloads", source, id], + queryFn: async () => + { + const { data, error } = await rommApi.api.romm.download.lookup({ source: encodeURIComponent(source) })({ id: encodeURIComponent(id) }).get(); + if (error) throw error; + return data; + } +}); + +export const downloadLookupFiltersQuery = queryOptions({ + queryKey: ['game', 'filters'], queryFn: async () => + { + const { data, error } = await rommApi.api.romm.download.lookup.filters.get(); + if (error) throw error; + return data; + } }); \ No newline at end of file diff --git a/src/mainview/scripts/types.ts b/src/mainview/scripts/types.ts index 6ab944a..fbfcb36 100644 --- a/src/mainview/scripts/types.ts +++ b/src/mainview/scripts/types.ts @@ -12,7 +12,9 @@ export const FOCUS_KEYS = { EMULATOR_CARD: (id: string) => `EMULATOR_${id}`, GAME_SECTION: "GAME_SECTION", GAME_CARD: (id: FrontEndId) => `GAME_${id.source}_${id.id}`, + GAME_LIST_CARD: (list: string, id: FrontEndId) => `LIST_${list}_GAME_${id.source}_${id.id}`, GAME_MATCH: (id: FrontEndId) => `GAME_${id.source}_${id.id}`, STATS_SECTION: "STATS_SECTION", - PLUGIN_ENTRY: (id: string) => `PLUGIN_${id}` + PLUGIN_ENTRY: (id: string) => `PLUGIN_${id}`, + DOWNLOAD_ENTRY: (source: string, id: string) => `DOWNLOAD_${source}_${id}` } as const; \ No newline at end of file diff --git a/src/mainview/types.d.ts b/src/mainview/types.d.ts index 1a1246e..2a0c101 100644 --- a/src/mainview/types.d.ts +++ b/src/mainview/types.d.ts @@ -50,6 +50,7 @@ declare interface GameMeta extends FocusParams { id: string, onSelect?: () => void, + onQuickAction?: () => void, title: string, subtitle?: any, previewUrls?: string | URL[]; diff --git a/src/packages/gameflow-sdk/hooks/app.ts b/src/packages/gameflow-sdk/hooks/app.ts index d8fef7a..1b73daa 100644 --- a/src/packages/gameflow-sdk/hooks/app.ts +++ b/src/packages/gameflow-sdk/hooks/app.ts @@ -1,7 +1,9 @@ +import { AsyncSeriesBailHook } from "tapable"; import AuthHooks from "./auth"; import EmulatorHooks from "./emulators"; import GameHooks from "./games"; import StoreHooks from "./store"; +import { DownloadFileEntry, ProgressStats } from "../shared"; export class GameflowHooks { @@ -9,4 +11,39 @@ export class GameflowHooks emulators = new EmulatorHooks(); auth = new AuthHooks(); store = new StoreHooks(); + /** Download the given files and return their final paths. */ + downloadFiles = new AsyncSeriesBailHook<[ctx: { + /** Unique ID of the download */ + id: string, + /** The root download path. Each file has it's own download sub path */ + downloadPath: string, + abortSignal?: AbortSignal, + /** Authentication needed for download. Should be put in the headers. */ + auth?: string, + /** The files to download */ + files: DownloadFileEntry[]; + /** Call it to update progress in the UI */ + updateProgress: (stats: ProgressStats) => void; + + }], { + /** What downloaded the files. Will be passed to {@link postDownloadFiles} files hook. */ + source: string, + /** The file paths ot the downloaded files. */ + files: string[]; + } | undefined>(['ctx']); + /** Called after {@link downloadFiles} has finished downloading. + * @returns The modified file paths. + */ + postDownloadFiles = new AsyncSeriesBailHook<[ctx: { + /** Who downloaded the files. Passed from the {@link downloadFiles} hook. */ + source: string; + /** Can be directories or files */ + files: string[]; + /** The root downloads folder. */ + downloadPath: string, + /** The sub path where the archive should be extracted to. This will be a sub path of `path_fs` */ + extract_path?: string; + /** This is the parent path for the extracted files. */ + path_fs?: string; + }], string[] | undefined>(['ctx']); } \ No newline at end of file diff --git a/src/packages/gameflow-sdk/hooks/emulators.ts b/src/packages/gameflow-sdk/hooks/emulators.ts index 768e56f..d852d06 100644 --- a/src/packages/gameflow-sdk/hooks/emulators.ts +++ b/src/packages/gameflow-sdk/hooks/emulators.ts @@ -5,6 +5,7 @@ import { AsyncSeriesBailHook, AsyncSeriesHook } from "tapable"; export default class EmulatorHooks { + /** Download emulator bios files */ fetchBiosDownload = new AsyncSeriesBailHook<[ctx: { emulator: string; systems: EmulatorSystem[]; @@ -15,7 +16,9 @@ export default class EmulatorHooks * Triggered when emulator is downloaded or updated */ emulatorPostInstall = new AsyncSeriesHook<[ctx: EmulatorPostInstallContextType], { emulator: string; }>(['ctx']); + /** Find locations of emulators on the system. Be it already installed ones or ones downloaded by the store. */ findEmulatorSource = new AsyncSeriesHook<[ctx: { emulator: string; sources: EmulatorSourceEntryType[]; }]>(['ctx']); + /** Match emulators for a given system */ findEmulatorForSystem = new AsyncSeriesHook<[ctx: { system: string; emulators: string[]; }]>(['ctx']); constructor() diff --git a/src/packages/gameflow-sdk/hooks/games.ts b/src/packages/gameflow-sdk/hooks/games.ts index 9083e47..314b138 100644 --- a/src/packages/gameflow-sdk/hooks/games.ts +++ b/src/packages/gameflow-sdk/hooks/games.ts @@ -1,30 +1,32 @@ -import { EmulatorPackageType, GameListFilterType, CommandEntry, DownloadInfo, EmulatorSourceEntryType, EmulatorSupport, EmulatorSystem, FrontEndCollection, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeWithIds, FrontEndId, FrontEndPlatformType, GameLookup, SaveFileChange, SaveSlots } from '../shared'; +import { EmulatorPackageType, GameListFilterType, CommandEntry, DownloadInfo, EmulatorSourceEntryType, EmulatorSupport, EmulatorSystem, FrontEndCollection, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeWithIds, FrontEndId, FrontEndPlatformType, GameLookup, SaveFileChange, SaveSlots, DownloadLookupEntry, DownloadLookupDetails, DownloadsLookupFilterValues, DownloadsLookupFilter } from '../shared'; import { SyncBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } from 'tapable'; export default class GameHooks { + /** Build commands the game can be launched with. */ buildLaunchCommands = new AsyncSeriesBailHook<[ctx: { source: string | null; sourceId: string | null; id: FrontEndId; systemSlug: string; gamePath: string | null, + /** The glob pattern for the main executable of the game */ mainGlob?: string | null, }], CommandEntry[] | Error | undefined>(['ctx']); /** override the launch command for an emulator - * @param ctx.autoValidCommands The auto generated command for example based on the ES-DE listing - * @param ctx.emulator The emulator ID if any - * @param ctx.game.source The source of the game - * @param ctx.game.sourceId The ID of the source. This could be for example the ROMM ID the game was * @returns The argument list to be used when running the emulator. * If no emulator bin in the command entry is found the actual command will be used as the bin. */ emulatorLaunch = new AsyncSeriesBailHook<[ctx: { + /** The auto generated command for example based on the ES-DE listing */ autoValidCommand: CommandEntry; + /** Don't actually launch just see if it can be launched */ dryRun: boolean, game: { + /** The source of the game */ source?: string; + /** The ID of the source. This could be for example the ROMM ID the game was */ sourceId?: string; id: FrontEndId; platformSlug?: string; @@ -41,34 +43,36 @@ export default class GameHooks }], EmulatorSupport | undefined, { emulator: string; }>(['ctx']); /** * Fetches and returns a list of games converted to frontend. - * @param ctx.localGameIds This is local game ids in the format '@' */ fetchGames = new AsyncSeriesHook<[ctx: { query: GameListFilterType; games: FrontEndGameTypeWithIds[]; }]>(['ctx']); + /** Return all filters the users can apply for a give source. */ fetchFilters = new AsyncSeriesHook<[ctx: { source?: string; filters: FrontEndFilterSets; }]>(['ctx']); + /** Get game metadata */ fetchGame = new AsyncSeriesBailHook<[ctx: { source: string; localGame?: FrontEndGameTypeDetailed; id: string; }], FrontEndGameTypeDetailed | undefined>(['ctx']); + /** Search for a given game based on the igdb id or ra id. */ searchGame = new AsyncSeriesBailHook<[ctx: { source: string; igdb_id?: number; ra_id?: number; }], FrontEndGameTypeDetailed | undefined>(['ctx']); - /** Get download file URLs - * @param ctx.checksum Check if file already exists using checksums - */ + /** Get download file URLs */ fetchDownloads = new AsyncSeriesBailHook<[ctx: { source: string; id: string; + /** If there are multiple downloads, use the one with same ID */ downloadId?: string; }], DownloadInfo[] | undefined>(['ctx']); + /** Get the paths to rom files. This is mainly used for emulator js. */ fetchRomFiles = new AsyncSeriesBailHook<[ctx: { source: string; id: string; @@ -86,6 +90,7 @@ export default class GameHooks source: string; id: string; }], FrontEndPlatformType | undefined>(['ctx']); + /** Lookup a given platform with a given slug or id. This may or may not exist. */ platformLookup = new AsyncSeriesBailHook<[ctx: { source?: string; id?: string; @@ -96,6 +101,23 @@ export default class GameHooks name?: string; family_name?: string; } | undefined>(['ctx']); + /** Lookup downloads based on a search pattern. + * This is just downloads. Doesn't actually have to be a game. + * This is mainly used to manually add games from outside sources */ + downloadsLookup = new AsyncSeriesWaterfallHook<[matches: Map, ctx: { + page?: number; + rows?: number; + } & DownloadsLookupFilter]>(['matches', 'ctx']); + /** List all available filters */ + downloadsLookupFilters = new AsyncSeriesHook<[ctx: { + filters: DownloadsLookupFilterValues; + }]>(['ctx']); + /** Look for the files for a download the user can pick from */ + downloadLookup = new AsyncSeriesBailHook<[ctx: { source: string, id: string; }], DownloadLookupDetails | undefined>(['ctx']); + /** Look up game metadata based on a search */ gameLookup = new AsyncSeriesWaterfallHook<[matches: Map, ctx: { source?: string, id?: string; @@ -104,6 +126,7 @@ export default class GameHooks fetchPlatforms = new AsyncSeriesHook<[ctx: { platforms: FrontEndPlatformType[]; }]>(['ctx']); + /** Called before the game is played. */ prePlay = new AsyncSeriesHook<[ctx: { source: string, id: string; @@ -115,20 +138,25 @@ export default class GameHooks }; }]>(["ctx"]); /** - * @param changedSaveFiles Auto detected changed files. This is mainly used to see what changed during gameplay - * @param validChangedSaveFiles This will be final valid changes to be saved using save integrations like rclone + * Called after the game process has finished. */ postPlay = new AsyncSeriesHook<[ctx: { source: string, id: string; saveFolderSlots?: SaveSlots; + /** Auto detected changed files. This is mainly used to see what changed during gameplay */ changedSaveFiles: { subPath: string, cwd: string; }[], + /** This will be final valid changes to be saved using save integrations like rclone */ validChangedSaveFiles: Record, + /** The command that was used to launch the game */ command: CommandEntry; gameInfo: { platformSlug?: string; }; }]>(["ctx"]); + /** Called after game install + * This includes game being downloaded and registered in the database. + */ postInstall = new AsyncSeriesHook<[ctx: { source: string, id: string; diff --git a/src/packages/gameflow-sdk/package.json b/src/packages/gameflow-sdk/package.json index 09354fe..4e4ce28 100644 --- a/src/packages/gameflow-sdk/package.json +++ b/src/packages/gameflow-sdk/package.json @@ -13,10 +13,6 @@ "peerDependencies": { "7zip-bin": "^5.2.0", "@auth/core": "^0.34.3", - "@elysiajs/cors": "^1.4.2", - "@elysiajs/eden": "^1.4.9", - "@jimp/wasm-webp": "^1.6.1", - "@phalcode/ts-igdb-client": "^1.0.26", "cheerio": "^1.2.0", "conf": "^15.1.0", "drizzle-orm": "^0.45.2", @@ -36,16 +32,12 @@ "pathe": "^2.0.3", "slugify": "^1.6.9", "smol-toml": "^1.6.1", - "systeminformation": "^5.31.5", "tapable": "^2.3.3", - "tough-cookie": "^6.0.1", - "tough-cookie-file-store": "^3.3.0", "unzip-stream": "^0.3.4", - "webview-bun": "^2.4.0", "zod": "^4.4.3" }, "keywords": [ "gameflow", "sdk" ] -} +} \ No newline at end of file diff --git a/src/packages/gameflow-sdk/shared.ts b/src/packages/gameflow-sdk/shared.ts index 147db36..96ddb01 100644 --- a/src/packages/gameflow-sdk/shared.ts +++ b/src/packages/gameflow-sdk/shared.ts @@ -250,6 +250,7 @@ export interface EmulatorSourceEntryType binPath: string; rootPath?: string; type: EmulatorSourceType; + /** Does the emulator exist in the file system */ exists: boolean; } @@ -489,6 +490,15 @@ export interface GameInstallProgress export type JobStatus = 'completed' | 'error' | 'running' | 'queued' | 'aborted'; export type GameInstallProgressEvent = 'refresh'; +export interface FrontEndJob +{ + id: string; + data: any; + progress: number; + state?: string; + status: string; +} + export interface FrontendPlugin { name: string; @@ -622,10 +632,79 @@ export interface GameLookup }[]; } +export interface DownloadLookupEntry +{ + source: string; + id: string; + cover_url: string | null | undefined; + name: string; + summary: string | null | undefined; + size: number | null | undefined; + date: Date | null | undefined; + rating: number | null | undefined; + view_count: number | null | undefined; + download_count: number | null | undefined; + comment_count: number | null | undefined; +} + +export interface DownloadLookupDetailsFile +{ + id: string; + format: string | null | undefined; + mtime: Date | null | undefined; + size: number | null | undefined; + download_url: string; +} + +export interface DownloadLookupDetails +{ + source: string; + id: string; + cover_url: string | null | undefined; + name: string; + summary: string | null | undefined; + date: Date | null | undefined; + files: DownloadLookupDetailsFile[]; +} + export interface AutoSaveChange { subPath: string; cwd: string; } +export interface AppInfoContext +{ + activeTaskProgress: number | null; +} + export type SaveSlots = Record; + +/** Jobs that are downloading stuff can implement this data interface to show up in the downloads screen */ +export interface DownloadJobData extends Partial> +{ + preview_url?: string | null; + name?: string; +} + +export interface ProgressStats +{ + progress: number; + speed: number; + total: number; + downloaded: number; +} + +export interface DownloadsLookupFilter +{ + source?: string, + orderBy?: string, + search?: string; + sortDirection?: "desc" | "asc"; +} + +export interface DownloadsLookupFilterValues +{ + orderBy: string[], + source: string[]; +} \ No newline at end of file diff --git a/src/packages/gameflow-sdk/task-queue.ts b/src/packages/gameflow-sdk/task-queue.ts index b86aab6..9ed555e 100644 --- a/src/packages/gameflow-sdk/task-queue.ts +++ b/src/packages/gameflow-sdk/task-queue.ts @@ -18,14 +18,24 @@ export class TaskQueue }); } - public enqueue (id: string, job: T, throwOnError?: boolean): T extends IJob + public enqueue (id: string, job: T, options?: { throwOnCancel?: boolean; }): T extends IJob ? Promise : never { this.disposeSafeguard(); if (!this.queue || !this.events) throw new Error("Queue disposed"); - const context = new JobContext(id, this.events, job); + if (this.activeQueue.some(j => j.id === id)) throw new Error(`Job with ID ${id} already active`); + if (this.queue.some(j => j.id === id)) throw new Error(`Job with ${id} already queued`); + const context = new JobContext(id, this.events, job, options); this.queue.push(context as any); + context.abortSignal.addEventListener('abort', () => + { + const queueIndex = this.queue?.findIndex(c => c === context); + if (queueIndex !== undefined && queueIndex >= 0) + { + this.queue?.splice(queueIndex, 1); + } + }); this.events?.emit('queued', { id: context.id, job: context }); this.processQueue(); return context.promise.promise as any; @@ -35,7 +45,24 @@ export class TaskQueue { if (!this.queue) return Promise.resolve(); - const next = this.queue.filter(j => !j.job.group || !this.activeQueue.some(a => a.job.group === j.job.group)).map((job, i) => ({ i, job })); + let activeGroupsSet = new Set(this.activeQueue.filter(j => j.job.group).map(j => j.job.group)); + const next = this.queue.filter(j => + { + if (j.job.group) + { + // Only take one task per group to be active + if (!activeGroupsSet.has(j.job.group)) + { + activeGroupsSet.add(j.job.group); + return true; + } + } else + { + return true; + } + + return false; + }).map((job, i) => ({ i, job })); next.reverse().forEach(({ i }) => this.queue!.splice(i, 1)); @@ -82,6 +109,14 @@ export class TaskQueue return job?.promise.promise ?? Promise.resolve(); } + public cancelJob (id: string) + { + const job = this.queue?.find(j => j.id === id) + ?? this.activeQueue?.find(j => j.id === id); + + job?.abort('cancel'); + } + public findJob ( id: string, type: new (...args: any[]) => T @@ -99,6 +134,16 @@ export class TaskQueue return undefined as any; } + public getActiveJobs () + { + return this.activeQueue; + } + + public getQueuedJobs () + { + return this.queue; + } + public on (event: E, listener: E extends keyof EventsList ? EventsList[E] extends unknown[] ? (...args: EventsList[E]) => void : never : never): () => void { this.events?.on(event, listener); @@ -170,6 +215,7 @@ export interface CompletedEvent extends BaseEvent export interface IJob { + /** What group does the job belong to. Grouped jobs can only have 1 active job per group */ group?: string; start (context: JobContext, TData, TState>): Promise; exposeData?(): TData; @@ -210,12 +256,14 @@ export class JobContext, TData, TState extends str private events: EventEmitter; private abortController: AbortController; private m_promise: PromiseWithResolvers; + private throwOnCancel: boolean; private readonly m_job: T; - constructor(id: string, events: EventEmitter, job: T) + constructor(id: string, events: EventEmitter, job: T, options?: { throwOnCancel?: boolean; }) { this.m_id = id; this.m_job = job; + this.throwOnCancel = options?.throwOnCancel ?? false; this.abortController = new AbortController(); this.abortController.signal.addEventListener('abort', () => { @@ -247,7 +295,13 @@ export class JobContext, TData, TState extends str { if (error.target instanceof AbortSignal) { - this.m_promise.resolve(undefined); + if (this.throwOnCancel) + { + this.m_promise.reject(this.abortSignal.reason); + } else + { + this.m_promise.resolve(undefined); + } } else { console.error(error); diff --git a/src/shared/types.schema.ts b/src/shared/types.schema.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/shared/types.ts b/src/shared/types.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 1e128cf..2b3623d 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -22,4 +22,12 @@ export async function delay (delay: number | Date, signal?: AbortSignal) } }); -}; \ No newline at end of file +}; + +const urlRegex = /^https?:\/\//; + +export function isUrl (value: string | undefined) +{ + if (!value) return false; + return urlRegex.test(value); +} \ No newline at end of file From 55939858842eed0bc328ea68de6cf4ca565fb8b6 Mon Sep 17 00:00:00 2001 From: Simeon Radivoev Date: Fri, 15 May 2026 15:07:51 +0300 Subject: [PATCH 7/7] chore: Fixed tests --- .gitignore | 1 + src/bun/api/app.ts | 7 ++ src/bun/api/games/games.ts | 6 +- .../jobs/{update-store.ts => ensure-store.ts} | 27 +++--- src/bun/api/jobs/jobs.ts | 4 +- .../store.ts | 10 +-- src/bun/api/plugins/register-plugins.ts | 89 ++++++++++--------- src/bun/api/plugins/services.ts | 2 + src/bun/api/store/store.ts | 6 +- src/bun/utils.ts | 15 ++++ src/packages/gameflow-sdk/index.ts | 3 +- src/packages/gameflow-sdk/task-queue.ts | 29 ++++++ src/tests/preload.ts | 6 +- 13 files changed, 139 insertions(+), 66 deletions(-) rename src/bun/api/jobs/{update-store.ts => ensure-store.ts} (60%) diff --git a/.gitignore b/.gitignore index e7e5c74..880e27d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ downloads gameflow-deck.code-workspace .env.local src/tests/mock-roms/db.sqlite +src/tests/mock-roms/store src/tests/mock-config bin .config/flatpak/repo diff --git a/src/bun/api/app.ts b/src/bun/api/app.ts index 68ec287..4695726 100644 --- a/src/bun/api/app.ts +++ b/src/bun/api/app.ts @@ -116,6 +116,13 @@ export async function cleanup () cleannedUp = true; } +/** Reset the cleanup flags. This is mainly used by tests since they run the same app. */ +export async function resetCleanup () +{ + cleaningUp = false; + cleannedUp = false; +} + export async function reloadDatabase () { await ensureDir(config.get('downloadPath')); diff --git a/src/bun/api/games/games.ts b/src/bun/api/games/games.ts index f06f71f..d922b36 100644 --- a/src/bun/api/games/games.ts +++ b/src/bun/api/games/games.ts @@ -454,18 +454,18 @@ export default new Elysia() }, { params: z.object({ id: z.string(), source: z.string() }), }) - .post('/game/:source/:id/install', async ({ params: { id, source }, body: { downloadId } }) => + .post('/game/:source/:id/install', async ({ params: { id, source }, body }) => { if (!taskQueue.findJob(InstallJob.query({ source, id }), InstallJob)) { - return taskQueue.enqueue(InstallJob.query({ source, id }), new InstallJob(id, source, { downloadId })); + return taskQueue.enqueue(InstallJob.query({ source, id }), new InstallJob(id, source, body)); } else { return status('Not Implemented'); } }, { params: z.object({ id: z.string(), source: z.string() }), - body: z.object({ downloadId: z.string().optional() }), + body: z.object({ downloadId: z.string().optional() }).optional(), response: z.any() }) .delete('/game/:source/:id/install', async ({ params: { id, source } }) => diff --git a/src/bun/api/jobs/update-store.ts b/src/bun/api/jobs/ensure-store.ts similarity index 60% rename from src/bun/api/jobs/update-store.ts rename to src/bun/api/jobs/ensure-store.ts index 697fb3a..bce028b 100644 --- a/src/bun/api/jobs/update-store.ts +++ b/src/bun/api/jobs/ensure-store.ts @@ -6,8 +6,9 @@ import { runBunPackageCommand } from "../plugins/services"; import { PluginRegistry } from "@/shared/constants"; import path from "node:path"; import sdkPkg from '@simeonradivoev/gameflow-sdk/package.json'; +import { IsPluginAllowed } from "@/bun/utils"; -export default class UpdateStoreJob implements IJob +export default class EnsureStore implements IJob { static id = "update-store" as const; static dataSchema = z.never(); @@ -20,7 +21,7 @@ export default class UpdateStoreJob implements IJob this.storeVersion = process.env.STORE_VERSION ?? "^0.1.0"; } - async start (context: JobContext) + async start (context: JobContext) { const storeFolder = getStoreRootFolder(); await ensureDir(storeFolder); @@ -32,17 +33,23 @@ export default class UpdateStoreJob implements IJob const storePackage = await Bun.file(path.join(storeFolder, "package.json")).json(); - if (!storePackage.dependencies?.[sdkPkg.name] || storePackage.dependencies?.[sdkPkg.name] !== sdkPkg.version) + if (IsPluginAllowed(sdkPkg.name)) { - let response = await runBunPackageCommand(["add", `${sdkPkg.name}@${sdkPkg.version}`, "--registry", PluginRegistry, '--omit', 'peer']); - console.log(response); - } + if (!storePackage.dependencies?.[sdkPkg.name] || storePackage.dependencies?.[sdkPkg.name] !== sdkPkg.version) + { + let response = await runBunPackageCommand(["add", `${sdkPkg.name}@${sdkPkg.version}`, "--registry", PluginRegistry, '--omit', 'peer']); + console.log(response); + } - // probably just means we couldn't find a version of the sdk, just install latest - if (storePackage.dependencies?.[sdkPkg.name] !== sdkPkg.version) + // probably just means we couldn't find a version of the sdk, just install latest + if (storePackage.dependencies?.[sdkPkg.name] !== sdkPkg.version) + { + let response = await runBunPackageCommand(["add", '--exact', `${sdkPkg.name}@latest`, "--registry", PluginRegistry, '--omit', 'peer']); + console.log(response); + } + } else { - let response = await runBunPackageCommand(["add", '--exact', `${sdkPkg.name}@latest`, "--registry", PluginRegistry, '--omit', 'peer']); - console.log(response); + console.log("Ignoring SDK package"); } if (process.env.CUSTOM_STORE_PATH) return; diff --git a/src/bun/api/jobs/jobs.ts b/src/bun/api/jobs/jobs.ts index e7e20a2..5471e56 100644 --- a/src/bun/api/jobs/jobs.ts +++ b/src/bun/api/jobs/jobs.ts @@ -3,7 +3,7 @@ import z, { _ZodType } from "zod"; import { taskQueue } from "../app"; import { LoginJob } from "./login-job"; import TwitchLoginJob from "./twitch-login-job"; -import UpdateStoreJob from "./update-store"; +import EnsureStore from "./ensure-store"; import { EmulatorDownloadJob } from "./emulator-download-job"; import { getErrorMessage } from "@/bun/utils"; import { BaseEvent, IJob } from "@simeonradivoev/gameflow-sdk/task-queue"; @@ -184,7 +184,7 @@ export const jobs = new Elysia({ prefix: '/api/jobs' }) .use(registerJob(LaunchGameJob)) .use(registerJob(LoginJob)) .use(registerJob(TwitchLoginJob)) - .use(registerJob(UpdateStoreJob)) + .use(registerJob(EnsureStore)) .use(registerJob(BiosDownloadJob)) .use(registerJob(InstallJob)) .use(registerJob(ReloadPluginsJob)) diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts index 118eb11..90d1cbc 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts @@ -2,14 +2,14 @@ import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-s import desc from './package.json'; import path, { } from 'node:path'; import { buildStoreFrontendEmulatorSystems, getAllStoreEmulatorPackages, getStoreEmulatorPackage, getStoreFolder } from "@/bun/api/store/services/gamesService"; -import { Glob, pathToFileURL, which } from "bun"; +import { Glob, pathToFileURL, sleep, which } from "bun"; import { and, eq } from "drizzle-orm"; import * as emulatorSchema from '@schema/emulators'; import { config, emulatorsDb, taskQueue } from "@/bun/api/app"; import fs from "node:fs/promises"; import { getSourceGameDetailed } from "@/bun/api/games/services/utils"; -import UpdateStoreJob from "@/bun/api/jobs/update-store"; +import EnsureStore from "@/bun/api/jobs/ensure-store"; import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService"; import { buildFilters, buildLaunchCommand, buildSaves, convertStoreEmulatorToFrontend, convertStoreToFrontend, convertStoreToFrontendDetailed, getExistingStoreEmulatorDownload, getShuffledStoreGames, getStoreGame, getValidDownloads } from "./services"; import { DownloadInfo, FrontEndEmulatorDetailed, FrontEndGameTypeWithIds } from "@simeonradivoev/gameflow-sdk/shared"; @@ -20,7 +20,7 @@ import StreamZip from "node-stream-zip"; import { path7za } from "7zip-bin"; import Seven from 'node-7z'; -export default class RommIntegration implements PluginType +export default class StoreIntegration implements PluginType { eventsNames = [{ id: 'updateStore', title: "Update Store", description: "Update the Store Manifest", action: "Update" }]; @@ -29,7 +29,7 @@ export default class RommIntegration implements PluginType switch (e) { case 'updateStore': - await taskQueue.enqueue(UpdateStoreJob.id, new UpdateStoreJob()); + await taskQueue.enqueue(EnsureStore.id, new EnsureStore()); return { reload: true }; } } @@ -38,7 +38,7 @@ export default class RommIntegration implements PluginType { console.log("Store Directory is ", getStoreFolder()); ctx.setProgress(0, "Updating Store"); - await taskQueue.enqueue(UpdateStoreJob.id, new UpdateStoreJob()); + await taskQueue.enqueue(EnsureStore.id, new EnsureStore()); } async load (ctx: PluginLoadingContextType) diff --git a/src/bun/api/plugins/register-plugins.ts b/src/bun/api/plugins/register-plugins.ts index 1275740..a4b5666 100644 --- a/src/bun/api/plugins/register-plugins.ts +++ b/src/bun/api/plugins/register-plugins.ts @@ -14,10 +14,12 @@ import rclone from './builtin/other/com.simeonradivoev.gameflow.rclone/package.j import { PluginDescriptionSchema, PluginDescriptionType, PluginSchema } from "@simeonradivoev/gameflow-sdk"; import path from 'node:path'; import { getStoreRootFolder } from "../store/services/gamesService"; -import { getUpdates } from "./services"; +import { getUpdates, runBunPackageCommand } from "./services"; import { PluginSourceType } from "@simeonradivoev/gameflow-sdk/shared"; import { taskQueue } from "../app"; -import UpdateStoreJob from "../jobs/update-store"; +import EnsureStore from "../jobs/ensure-store"; +import { PluginRegistry } from "@/shared/constants"; +import { IsPluginAllowed } from "@/bun/utils"; type PluginEntry = PluginDescriptionType & { load: () => Promise; }; @@ -58,15 +60,9 @@ export async function unregisterPlugin (id: string, pluginManager: PluginManager export async function registerPlugin (plugin: PluginEntry, source: PluginSourceType, pluginManager: PluginManager) { - if (process.env.PLUGIN_WHITELIST && !process.env.PLUGIN_WHITELIST.includes(plugin.name)) + if (!IsPluginAllowed(plugin.name)) { - console.log("Skipping", plugin.name, "missing in whitelist"); - return; - } - - if (process.env.PLUGIN_BLACKLIST && process.env.PLUGIN_BLACKLIST.includes(plugin.name)) - { - console.log("Skipping", plugin.name, "found in whitelist"); + console.log("Skipping", plugin.name, "plugin not allowed"); return; } @@ -101,39 +97,52 @@ export default async function register (pluginManager: PluginManager) await Promise.all(plugins.map(p => registerPlugin(p, 'builtin', pluginManager))); - const storePackageFilePath = path.join(getStoreRootFolder(), 'package.json'); - if (!await Bun.file(storePackageFilePath).exists()) + if (IsPluginAllowed('@simeonradivoev/gameflow-store')) { - console.log("Store is missing. Updating it."); - await taskQueue.enqueue(UpdateStoreJob.id, new UpdateStoreJob()); - console.log("Store Updated"); - } - const storePackage = await Bun.file(storePackageFilePath).json(); - - if (storePackage?.dependencies) - { - const storePlugins = await Promise.all(Object.keys(storePackage.dependencies).filter(p => !blacklist.has(p)).map(async p => + const storePackageFilePath = path.join(getStoreRootFolder(), 'package.json'); + if (!await Bun.file(storePackageFilePath).exists()) { - return getPlugin(p, pluginManager); - })); - - console.log("Checking for outdated packages"); - const outdated = await getUpdates(); - - const validPlugins = storePlugins.filter(p => !!p); - - if (outdated) - { - validPlugins.forEach(p => - { - const newVersion = outdated[p.name]; - if (newVersion) - { - console.log("Plugin", p.name, "has update", p.version, "=>", newVersion); - } - }); + console.log("Store is missing. Updating it."); + await taskQueue.enqueue(EnsureStore.id, new EnsureStore()); + console.log("Store Updated"); } + const storePackage = await Bun.file(storePackageFilePath).json(); - await Promise.all(validPlugins.map(p => registerPlugin(p, 'store', pluginManager))); + if (storePackage?.dependencies) + { + const storePlugins = await Promise.all(Object.keys(storePackage.dependencies).filter(p => !blacklist.has(p)).map(async p => + { + return getPlugin(p, pluginManager); + })); + + console.log("Checking for outdated packages"); + const outdated = await getUpdates(); + + const validPlugins = storePlugins.filter(p => !!p); + + if (outdated) + { + for await (const plugin of validPlugins) + { + const newVersion = outdated[plugin.name]; + if (newVersion) + { + console.log("Plugin", plugin.name, "has update", plugin.version, "=>", newVersion); + } + + if (plugin.autoUpdate) + { + console.log("Auto Updating Plugin", plugin.name); + let response = await runBunPackageCommand(["add", `${plugin.name}@${newVersion}`, "--registry", PluginRegistry, '--omit', 'peer']); + console.log(response); + } + } + } + + await Promise.all(validPlugins.map(p => registerPlugin(p, 'store', pluginManager))); + } + } else + { + console.log('Skipping Store Packages'); } } \ No newline at end of file diff --git a/src/bun/api/plugins/services.ts b/src/bun/api/plugins/services.ts index 9452b7e..0ba40b3 100644 --- a/src/bun/api/plugins/services.ts +++ b/src/bun/api/plugins/services.ts @@ -3,6 +3,7 @@ import os from 'node:os'; import { getStoreRootFolder } from '../store/services/gamesService'; import { PluginDescriptionType } from '@simeonradivoev/gameflow-sdk'; import { run } from 'npm-check-updates'; +import { existsSync } from 'node:fs'; export function canDisable (description: PluginDescriptionType) { @@ -15,6 +16,7 @@ export function canDisable (description: PluginDescriptionType) export async function getUpdates () { + if (!existsSync(getStoreRootFolder())) return {}; const updated = await run({ packageManager: 'bun', peer: true, cwd: getStoreRootFolder(), jsonUpgraded: true, reject: ['@simeonradivoev/gameflow-sdk'] }); return updated as Record; } diff --git a/src/bun/api/store/store.ts b/src/bun/api/store/store.ts index 39d5630..7706699 100644 --- a/src/bun/api/store/store.ts +++ b/src/bun/api/store/store.ts @@ -188,16 +188,16 @@ export const store = new Elysia({ prefix: '/api/store' }) emulator.integrations = integrations; return emulator; }, { params: z.object({ id: z.string() }) }) - .post('/install/emulator/:id/:source', async ({ params: { source, id }, body: { isUpdate } }) => + .post('/install/emulator/:id/:source', async ({ params: { source, id }, body }) => { if (taskQueue.hasActiveOfType(EmulatorDownloadJob)) { return status("Conflict", "Installation already running"); } - const job = new EmulatorDownloadJob(id, source, { isUpdate }); + const job = new EmulatorDownloadJob(id, source, body); return taskQueue.enqueue(EmulatorDownloadJob.id, job); }, { - body: z.object({ isUpdate: z.boolean().optional() }) + body: z.object({ isUpdate: z.boolean().optional() }).optional() }) .delete('/emulator/:id', async ({ params: { id } }) => { diff --git a/src/bun/utils.ts b/src/bun/utils.ts index a3868e4..6fbc630 100644 --- a/src/bun/utils.ts +++ b/src/bun/utils.ts @@ -185,4 +185,19 @@ export function getAppVersion () export function isArchive (path: string) { return archiveRegex.test(path); +} + +export function IsPluginAllowed (id: string) +{ + if (process.env.PLUGIN_WHITELIST && !process.env.PLUGIN_WHITELIST.includes(id)) + { + return false; + } + + if (process.env.PLUGIN_BLACKLIST && process.env.PLUGIN_BLACKLIST.includes(id)) + { + return false; + } + + return true; } \ No newline at end of file diff --git a/src/packages/gameflow-sdk/index.ts b/src/packages/gameflow-sdk/index.ts index 4149891..c78c757 100644 --- a/src/packages/gameflow-sdk/index.ts +++ b/src/packages/gameflow-sdk/index.ts @@ -41,7 +41,8 @@ export const PluginDescriptionSchema = z.object({ peerDependencies: z.record(z.string(), z.string()).optional(), category: z.string().default("other"), main: z.string().describe("The main entry. It must export a default class implementing PluginType"), - canDisable: z.boolean().default(true).optional().describe("Can the plugin be disabled or enabled by the user") + canDisable: z.boolean().default(true).optional().describe("Can the plugin be disabled or enabled by the user"), + autoUpdate: z.boolean().optional().describe("Should the plugin auto update to latest version") }); export const PluginSchema = z.object({ diff --git a/src/packages/gameflow-sdk/task-queue.ts b/src/packages/gameflow-sdk/task-queue.ts index 9ed555e..e86cebc 100644 --- a/src/packages/gameflow-sdk/task-queue.ts +++ b/src/packages/gameflow-sdk/task-queue.ts @@ -91,6 +91,11 @@ export class TaskQueue return this.activeQueue.length > 0; } + public hasQueued () + { + return this.queue && this.queue.length > 0; + } + public hasActiveOfType (type: any) { for (const entry of this.activeQueue) @@ -109,6 +114,30 @@ export class TaskQueue return job?.promise.promise ?? Promise.resolve(); } + public waitForAll () + { + return new Promise((resolve) => + { + if (!this.hasActive()) + { + resolve(true); + return; + } + + const handleEnded = () => + { + if (!this.hasActive() && !this.hasQueued()) + { + resolve(true); + this.events?.removeListener('ended', handleEnded); + this.events?.removeListener('abort', handleEnded); + } + }; + this.events?.on('ended', handleEnded); + this.events?.on('abort', handleEnded); + }); + } + public cancelJob (id: string) { const job = this.queue?.find(j => j.id === id) diff --git a/src/tests/preload.ts b/src/tests/preload.ts index e9a17b9..40cf49d 100644 --- a/src/tests/preload.ts +++ b/src/tests/preload.ts @@ -1,18 +1,20 @@ import { beforeAll, beforeEach, afterEach } from 'bun:test'; import { resolve } from 'node:path'; import * as app from '@/bun/api/app'; -import { remove } from 'fs-extra'; +import { ensureDir, remove } from 'fs-extra'; export async function LoadApp () { console.log("Loading App"); await app.load(); + await app.taskQueue.waitForAll(); } export async function CleanupApp () { console.log("Cleaning Up App"); await app.cleanup(); + await app.resetCleanup(); } beforeAll(async () => @@ -20,7 +22,7 @@ beforeAll(async () => process.env.CUSTOM_STORE_PATH = resolve('./src/tests/mock-store'); process.env.CONFIG_CWD = resolve('./src/tests/mock-config'); process.env.DEFAULT_DOWNLOAD_PATH = resolve('./src/tests/mock-roms'); - process.env.PLUGIN_BLACKLIST = 'com.simeonradivoev.gameflow.rclone'; + process.env.PLUGIN_BLACKLIST = 'com.simeonradivoev.gameflow.rclone,@simeonradivoev/gameflow-store,com.simeonradivoev.gameflow.romm,com.simeonradivoev.gameflow.igdb,@simeonradivoev/gameflow-sdk'; }); async function FileCleanup ()