diff --git a/extensions/cpp/explorer/src/extension.cpp b/extensions/cpp/explorer/src/extension.cpp index 0eb616e..5a9c721 100644 --- a/extensions/cpp/explorer/src/extension.cpp +++ b/extensions/cpp/explorer/src/extension.cpp @@ -396,9 +396,9 @@ tryFetchPs3Game(const std::filesystem::directory_entry &entry) { } struct ExplorerDescriber : ExplorerDescriberInterface { - ExplorerDescribeResponse - describe(const ExplorerDescribeRequest &request) override { - ExplorerDescribeResponse result; + ExplorerDescriberDescribeResponse + describe(const ExplorerDescriberDescribeRequest &request) override { + ExplorerDescriberDescribeResponse result; unsigned index = -1; for (auto &uri : request.uris) { diff --git a/package-lock.json b/package-lock.json index 2116cb8..d6aba3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4360,18 +4360,6 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -4543,13 +4531,6 @@ "node": ">=14" } }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" - }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -5054,306 +5035,6 @@ "url": "https://github.com/SpacingBat3/ReForged?sponsor=1" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", - "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", - "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", - "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", - "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", - "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", - "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", - "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", - "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", - "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", - "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", - "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", - "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", - "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", - "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", - "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", - "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", - "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", - "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", - "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", - "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -5405,104 +5086,6 @@ "dev": true, "license": "ISC" }, - "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", - "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, - "node_modules/@sveltejs/kit": { - "version": "2.36.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.36.1.tgz", - "integrity": "sha512-dldNCtSIpaGxQMEfHaUxSPH/k3uU28pTZwtKzfkn8fqpOjWufKlMBeIL7FJ/s93dOrhEq41zaQYkXh+XTgEgVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/cookie": "^0.6.0", - "acorn": "^8.14.1", - "cookie": "^0.6.0", - "devalue": "^5.1.0", - "esm-env": "^1.2.2", - "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "mrmime": "^2.0.0", - "sade": "^1.8.1", - "set-cookie-parser": "^2.6.0", - "sirv": "^3.0.0" - }, - "bin": { - "svelte-kit": "svelte-kit.js" - }, - "engines": { - "node": ">=18.13" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - } - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.3.tgz", - "integrity": "sha512-3pppgIeIZs6nrQLazzKcdnTJ2IWiui/UucEPXKyFG35TKaHQrfkWBnv6hyJcLxFuR90t+LaoecrqTs8rJKWfSQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "debug": "^4.4.1", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.17", - "vitefu": "^1.1.1" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", - "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.4.1" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -5591,13 +5174,6 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -6523,17 +6099,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -6752,17 +6317,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -7650,17 +7204,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -7825,16 +7368,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/core-js-compat": { "version": "3.45.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", @@ -8206,13 +7739,6 @@ "license": "MIT", "optional": true }, - "node_modules/devalue": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", - "dev": true, - "license": "MIT" - }, "node_modules/dir-compare": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", @@ -9219,13 +8745,6 @@ "node": "*" } }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, - "license": "MIT" - }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -9283,17 +8802,6 @@ "node": ">=0.10" } }, - "node_modules/esrap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", - "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -11414,17 +10922,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "^1.0.6" - } - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -11999,16 +11496,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/lan-network": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.1.7.tgz", @@ -12390,14 +11877,6 @@ "node": ">=0.10.0" } }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -12539,16 +12018,6 @@ "yallist": "^3.0.2" } }, - "node_modules/magic-string": { - "version": "0.30.18", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", - "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, "node_modules/make-fetch-happen": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", @@ -13367,26 +12836,6 @@ "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", "license": "MIT" }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -14257,36 +13706,6 @@ "node": ">= 0.4" } }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -15524,47 +14943,6 @@ "node": ">=8.0" } }, - "node_modules/rollup": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", - "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.47.1", - "@rollup/rollup-android-arm64": "4.47.1", - "@rollup/rollup-darwin-arm64": "4.47.1", - "@rollup/rollup-darwin-x64": "4.47.1", - "@rollup/rollup-freebsd-arm64": "4.47.1", - "@rollup/rollup-freebsd-x64": "4.47.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", - "@rollup/rollup-linux-arm-musleabihf": "4.47.1", - "@rollup/rollup-linux-arm64-gnu": "4.47.1", - "@rollup/rollup-linux-arm64-musl": "4.47.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", - "@rollup/rollup-linux-ppc64-gnu": "4.47.1", - "@rollup/rollup-linux-riscv64-gnu": "4.47.1", - "@rollup/rollup-linux-riscv64-musl": "4.47.1", - "@rollup/rollup-linux-s390x-gnu": "4.47.1", - "@rollup/rollup-linux-x64-gnu": "4.47.1", - "@rollup/rollup-linux-x64-musl": "4.47.1", - "@rollup/rollup-win32-arm64-msvc": "4.47.1", - "@rollup/rollup-win32-ia32-msvc": "4.47.1", - "@rollup/rollup-win32-x64-msvc": "4.47.1", - "fsevents": "~2.3.2" - } - }, "node_modules/rpcsx-ui-kit": { "resolved": "rpcsx-ui-kit", "link": true @@ -15593,19 +14971,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -15969,13 +15334,6 @@ "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", "license": "MIT" }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "dev": true, - "license": "MIT" - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -16211,21 +15569,6 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "license": "MIT" }, - "node_modules/sirv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", - "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -16943,33 +16286,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svelte": { - "version": "5.38.2", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.38.2.tgz", - "integrity": "sha512-iAcp/oFAWauVSGILdD67n7DiwgLHXZzWZIdzl7araRxu72jUr7PFAo2Iie7gXt0IbnlYvhxCb9GT3ZJUquO3PA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "esm-env": "^1.2.1", - "esrap": "^2.1.0", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -17222,16 +16538,6 @@ "node": ">=0.6" } }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -17767,117 +17073,6 @@ "node": ">= 0.8" } }, - "node_modules/vite": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", - "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.14" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "dev": true, - "license": "MIT", - "peer": true, - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, "node_modules/vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", @@ -18386,14 +17581,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zimmerframe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", - "dev": true, - "license": "MIT", - "peer": true - }, "rpcsx-ui-kit": { "version": "0.1.0", "license": "GPL-3.0-only", @@ -18408,7 +17595,6 @@ "rpcsx-ui-kit": "build/main.js" }, "devDependencies": { - "@sveltejs/kit": "^2.31.0", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.39.1", "@typescript-eslint/parser": "^8.39.1", diff --git a/rpcsx-ui-kit/package.json b/rpcsx-ui-kit/package.json index cccb2e5..b2c765b 100644 --- a/rpcsx-ui-kit/package.json +++ b/rpcsx-ui-kit/package.json @@ -21,7 +21,6 @@ "yargs": "^18.0.0" }, "devDependencies": { - "@sveltejs/kit": "^2.31.0", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.39.1", "@typescript-eslint/parser": "^8.39.1", diff --git a/rpcsx-ui-kit/src/cpp-generators.ts b/rpcsx-ui-kit/src/cpp-generators.ts index 59006e7..952a6ed 100644 --- a/rpcsx-ui-kit/src/cpp-generators.ts +++ b/rpcsx-ui-kit/src/cpp-generators.ts @@ -120,7 +120,7 @@ ${components.map(x => `#include <${this.getPrefix("/")}/${x.manifest.name}.hpp>` }; class CppTypesGenerator implements ContributionGenerator { - generatedTypes: Record = {}; + body = ""; includes = new Set(); constructor(private namespace: string, private libPath: string) { } @@ -129,13 +129,12 @@ class CppTypesGenerator implements ContributionGenerator { let result = '#pragma once\n\n'; result += [...this.includes].map(x => `#include <${x}>`).join("\n"); result += `\n\nnamespace ${this.namespace} {\n`; - result += Object.values(this.generatedTypes).join("\n"); + result += this.body; return result + `\n} // namespace ${this.namespace}\n`; } generateType(component: string, type: object, name: string) { const labelName = generateComponentLabelName(component, name, true); - const typeName = labelName; if (typeof type != 'object') { throw `${type}: must be object`; @@ -148,36 +147,32 @@ class CppTypesGenerator implements ContributionGenerator { throw `${name}: type must be string value`; } - if (!(typeName in this.generatedTypes)) { - let paramsType = ""; - if (type.type === "object") { - if (!("params" in type)) { - throw `${type}: params must be present`; - } - - if ((typeof type.params != 'object') || !type.params) { - throw `${type.params}: must be object`; - } - - paramsType += `struct ${labelName} {\n${this.generateObjectBody(component, type.params)}};\n\n`; - paramsType += this.generateObjectSerializer(labelName, type.params); - paramsType += this.generateObjectDeserializer(labelName, type.params); - } else if (type.type === "enum") { - if (!("enumerators" in type)) { - throw `${type}: enumerators must be present`; - } - - if ((typeof type.enumerators != 'object') || !type.enumerators) { - throw `${type.enumerators}: must be object`; - } - - paramsType += `enum class ${labelName} {\n${this.generateEnumBody(type.enumerators)}};\n`; + let paramsType = ""; + if (type.type === "object") { + if (!("params" in type)) { + throw `${type}: params must be present`; } - this.generatedTypes[typeName] = paramsType; - } else { - throw new Error(`${name}: type ${typeName} already declared`); + if ((typeof type.params != 'object') || !type.params) { + throw `${type.params}: must be object`; + } + + paramsType += `struct ${labelName} {\n${this.generateObjectBody(component, type.params)}};\n\n`; + paramsType += this.generateObjectSerializer(labelName, type.params); + paramsType += this.generateObjectDeserializer(labelName, type.params); + } else if (type.type === "enum") { + if (!("enumerators" in type)) { + throw `${type}: enumerators must be present`; + } + + if ((typeof type.enumerators != 'object') || !type.enumerators) { + throw `${type.enumerators}: must be object`; + } + + paramsType += `enum class ${labelName} {\n${this.generateEnumBody(type.enumerators)}};\n`; } + + this.body += paramsType; } generateEnumBody(enumerators: object) { @@ -228,35 +223,50 @@ class CppTypesGenerator implements ContributionGenerator { const requestTypeName = `${labelName}Request`; const responseTypeName = `${labelName}Response`; - if (!(requestTypeName in this.generatedTypes)) { + { let paramsType = ''; - paramsType += `struct ${requestTypeName} {\n`; - if ("params" in method && method.params && typeof method.params == "object") { - paramsType += this.generateObjectBody(component, method.params); + + if ("params" in method && method.params) { + if (typeof method.params == "object") { + paramsType += `struct ${requestTypeName} {\n`; + paramsType += this.generateObjectBody(component, method.params); + paramsType += "};\n"; + paramsType += this.generateObjectSerializer(requestTypeName, method.params); + paramsType += this.generateObjectDeserializer(requestTypeName, method.params); + } else if (typeof method.params == "string") { + paramsType += `using ${requestTypeName} = ${this.getTypeName(component, method.params)}\n`; + } + } else { + paramsType += `struct ${requestTypeName} {};\n`; + paramsType += this.generateObjectSerializer(requestTypeName, {}); + paramsType += this.generateObjectDeserializer(requestTypeName, {}); } - paramsType += "};\n"; - paramsType += this.generateObjectSerializer(requestTypeName, "params" in method ? method.params ?? {} : {}); - paramsType += this.generateObjectDeserializer(requestTypeName, "params" in method ? method.params ?? {} : {}); - this.generatedTypes[requestTypeName] = paramsType; - } else { - throw new Error(`${name}: type ${requestTypeName} already declared`); + this.body += paramsType; } - if (!(responseTypeName in this.generatedTypes)) { - let responseType = `struct ${responseTypeName} {\n`; - if ("returns" in method && method.returns && typeof method.returns == "object") { - responseType += this.generateObjectBody(component, method.returns); - } - responseType += "};\n"; - responseType += this.generateObjectSerializer(responseTypeName, "returns" in method ? method.returns ?? {} : {}); - responseType += this.generateObjectDeserializer(responseTypeName, "returns" in method ? method.returns ?? {} : {}); + { + let responseType = ''; - this.generatedTypes[responseTypeName] = responseType; - } else { - throw new Error(`${name}: type ${responseTypeName} already declared`); + if ("returns" in method && method.returns) { + if (typeof method.returns == "object") { + responseType += `struct ${responseTypeName} {\n`; + responseType += this.generateObjectBody(component, method.returns); + responseType += "};\n"; + responseType += this.generateObjectSerializer(responseTypeName, method.returns); + responseType += this.generateObjectDeserializer(responseTypeName, method.returns); + } else if (typeof method.returns == "string") { + responseType += `using ${responseTypeName} = ${this.getTypeName(component, method.returns)}\n`; + } + } else { + responseType += `struct ${responseTypeName} {};\n`; + responseType += this.generateObjectSerializer(responseTypeName, {}); + responseType += this.generateObjectDeserializer(responseTypeName, {}); + } + + this.body += responseType; } - this.generatedTypes[labelName] = `struct ${labelName} { + this.body += `struct ${labelName} { using Request = ${requestTypeName}; using Response = ${responseTypeName}; }; @@ -267,20 +277,28 @@ class CppTypesGenerator implements ContributionGenerator { const labelName = generateComponentLabelName(component, name, true); const requestTypeName = `${labelName}Request`; - if (!(requestTypeName in this.generatedTypes)) { - let paramsType = `struct ${requestTypeName} {\n`; - if ("params" in notification && notification.params && typeof notification.params == "object") { - paramsType += this.generateObjectBody(component, notification.params); + { + let paramsType = ''; + + if ("params" in notification && notification.params) { + if (typeof notification.params == "object") { + paramsType += `struct ${requestTypeName} {\n`; + paramsType += this.generateObjectBody(component, notification.params); + paramsType += "};\n"; + paramsType += this.generateObjectSerializer(requestTypeName, notification.params); + paramsType += this.generateObjectDeserializer(requestTypeName, notification.params); + } else if (typeof notification.params == "string") { + paramsType += `using ${requestTypeName} = ${this.getTypeName(component, notification.params)}\n`; + } + } else { + paramsType += `struct ${requestTypeName} {};\n`; + paramsType += this.generateObjectSerializer(requestTypeName, {}); + paramsType += this.generateObjectDeserializer(requestTypeName, {}); } - paramsType += "};\n"; - paramsType += this.generateObjectSerializer(requestTypeName, "params" in notification ? notification.params ?? {} : {}); - paramsType += this.generateObjectDeserializer(requestTypeName, "params" in notification ? notification.params ?? {} : {}); - this.generatedTypes[requestTypeName] = paramsType; - } else { - throw new Error(`${name}: type ${requestTypeName} already declared`); + this.body += paramsType; } - this.generatedTypes[labelName] = `struct ${labelName} { + this.body += `struct ${labelName} { using Request = ${requestTypeName}; }; ` @@ -288,14 +306,14 @@ class CppTypesGenerator implements ContributionGenerator { generateInterface(component: string, iface: object, name: string) { const labelName = generateComponentLabelName(component, name + "-interface", true); - this.generatedTypes[labelName] = `struct ${labelName} { + this.body += `struct ${labelName} { static constexpr auto kInterfaceId = "${component}/${name}"; using InterfaceType = ${labelName}; virtual ~${labelName}() = default; ${"methods" in iface ? iface.methods && Object.keys(iface.methods).map(method => { - const methodTypeLabel = generateComponentLabelName(component, method, true); + const methodTypeLabel = generateComponentLabelName(component, `${name}-${method}`, true); if ("params" in (iface.methods as any)[method]) { return ` virtual ${methodTypeLabel}Response ${generateLabelName(method, false)}(const ${methodTypeLabel}Request &request) = 0;` } else { @@ -303,7 +321,7 @@ ${"methods" in iface ? iface.methods && Object.keys(iface.methods).map(method => } }).join("\n") : ""} ${"notifications" in iface ? iface.notifications && Object.keys(iface.notifications).map(notification => { - const methodTypeLabel = generateComponentLabelName(component, notification, true); + const methodTypeLabel = generateComponentLabelName(component, `${name}-${notification}`, true); if ("params" in (iface.notifications as any)[notification]) { return ` virtual void ${generateLabelName(notification, false)}(const ${methodTypeLabel}Request &request) = 0;` } else { @@ -353,22 +371,19 @@ ${"notifications" in iface ? iface.notifications && Object.keys(iface.notificati const labelName = generateComponentLabelName(component, name, true); const typeName = `${labelName}Event`; - if (!(typeName in this.generatedTypes)) { - if (typeof event == 'object') { - let paramsType = `struct ${typeName} {\n`; - paramsType += this.generateObjectBody(component, event); - paramsType += "\n};\n" - paramsType += this.generateObjectSerializer(typeName, event); - paramsType += this.generateObjectDeserializer(typeName, event); - this.generatedTypes[typeName] = paramsType; - } else if (typeof event == 'string') { - this.generatedTypes[typeName] = `using ${typeName} = ${this.getTypeName(component, event)};\n`; - } else { - throw new Error(`${name}: must be object or string`); - } + if (typeof event == 'object') { + let paramsType = `struct ${typeName} {\n`; + paramsType += this.generateObjectBody(component, event); + paramsType += "};\n" + paramsType += this.generateObjectSerializer(typeName, event); + paramsType += this.generateObjectDeserializer(typeName, event); + this.body += paramsType; + } else if (typeof event == 'string') { + this.body += `using ${typeName} = ${this.getTypeName(component, event)};\n`; } else { - throw new Error(`${name}: type ${typeName} already declared`); + throw new Error(`${name}: must be object or string`); } + } addInclude(include: string) { @@ -381,7 +396,8 @@ ${"notifications" in iface ? iface.notifications && Object.keys(iface.notificati this.addInclude("string") return "std::string"; case "number": - return "int"; + this.addInclude("cstdint") + return "std::int64_t"; case "void": return "void"; case "boolean": diff --git a/rpcsx-ui-kit/src/generators.ts b/rpcsx-ui-kit/src/generators.ts index d7e2e7c..6a14fcd 100644 --- a/rpcsx-ui-kit/src/generators.ts +++ b/rpcsx-ui-kit/src/generators.ts @@ -60,7 +60,7 @@ export type ContributionGenerator = { generateView?(component: string, path: string, name: string): void | Promise; generateSetting?(component: string, schema: object, name: string): void | Promise; generateInterface?(component: string, iface: object, name: string): void | Promise; - toString(): string; + toString(component: string): string; }; const componentManifestName = "component.json"; @@ -640,10 +640,16 @@ class TypesGenerator implements ContributionGenerator { if (!(requestTypeName in this.generatedTypes)) { let paramsType = `type ${requestTypeName} = `; - if ("params" in method && method.params && typeof method.params == "object") { - paramsType += "{\n"; - paramsType += this.generateObjectBody(component, method.params); - paramsType += "};\n"; + if ("params" in method && method.params) { + if (typeof method.params == "object") { + paramsType += "{\n"; + paramsType += this.generateObjectBody(component, method.params); + paramsType += "};\n"; + } else if (typeof method.params == "string") { + paramsType += `${this.getTypeName(component, method.params)};\n`; + } else { + throw new Error(`${name}: params must be object or string`); + } } else { paramsType += "undefined;\n"; } @@ -654,10 +660,16 @@ class TypesGenerator implements ContributionGenerator { if (!(responseTypeName in this.generatedTypes)) { let responseType = `type ${responseTypeName} = `; - if ("returns" in method && method.returns && typeof method.returns == "object") { - responseType += "{\n"; - responseType += this.generateObjectBody(component, method.returns); - responseType += "}\n"; + if ("returns" in method && method.returns) { + if (typeof method.returns == "object") { + responseType += "{\n"; + responseType += this.generateObjectBody(component, method.returns); + responseType += "};\n"; + } else if (typeof method.returns == "string") { + responseType += `${this.getTypeName(component, method.returns)};\n`; + } else { + throw new Error(`${name}: returns must be object or string`); + } } else { responseType += "void;\n"; } @@ -673,10 +685,16 @@ class TypesGenerator implements ContributionGenerator { if (!(requestTypeName in this.generatedTypes)) { let paramsType = `type ${requestTypeName} = `; - if ("params" in notification && notification.params && typeof notification.params == "object") { - paramsType += "{\n"; - paramsType += this.generateObjectBody(component, notification.params); - paramsType += "};\n"; + if ("params" in notification && notification.params) { + if (typeof notification.params == "object") { + paramsType += "{\n"; + paramsType += this.generateObjectBody(component, notification.params); + paramsType += "};\n"; + } else if (typeof notification.params == "string") { + paramsType += `${this.getTypeName(component, notification.params)};\n`; + } else { + throw new Error(`${name}: params must be object or string`); + } } else { paramsType += "undefined;\n"; } @@ -686,6 +704,29 @@ class TypesGenerator implements ContributionGenerator { } } + generateInterface(component: string, iface: object, name: string) { + const uLabel = generateLabelName(name, true); + this.generatedTypes[`${uLabel}Interface`] = ` +type ${uLabel}Interface = { +${"methods" in iface ? iface.methods && Object.keys(iface.methods).map(method => { + const methodTypeLabel = generateComponentLabelName(component, `${name}-${method}`, true); + if ("params" in (iface.methods as any)[method]) { + return ` ${generateLabelName(method, false)}(caller: Component, request: ${methodTypeLabel}Request): ${methodTypeLabel}Response | Promise<${methodTypeLabel}Response>`; + } else { + return ` ${generateLabelName(method, false)}(caller: Component): ${methodTypeLabel}Response | Promise<${methodTypeLabel}Response>`; + } + }).join("\n") : ""} +${"notifications" in iface ? iface.notifications && Object.keys(iface.notifications).map(notification => { + const methodTypeLabel = generateComponentLabelName(component, `${name}-${notification}`, true); + if ("params" in (iface.notifications as any)[notification]) { + return ` ${generateLabelName(notification, false)}(caller: Component, request: ${methodTypeLabel}Request): void | Promise;` + } else { + return ` ${generateLabelName(notification, false)}(caller: Component): void | Promise;`; + } + }).join("\n") : ""} +};` + } + generateEvent(component: string, event: object, name: string) { const labelName = generateComponentLabelName(component, name, true); const typeName = `${labelName}Event`; @@ -702,7 +743,7 @@ class TypesGenerator implements ContributionGenerator { } this.generatedTypes[typeName] = paramsType; } else if (typeof event == 'string') { - this.generatedTypes[typeName] = `export type ${typeName} = ${this.getTypeName(component, event)};\n`; + this.generatedTypes[typeName] = `type ${typeName} = ${this.getTypeName(component, event)};\n`; } else { throw new Error(`${name}: must be object or string`); } @@ -780,7 +821,6 @@ class TypesGenerator implements ContributionGenerator { } class ServerPublicApiGenerator implements ContributionGenerator { - toString(): string { return `${generatedHeader} import { thisComponent } from "$/component-info"; @@ -801,17 +841,121 @@ export function onEvent(caller: ComponentInstance, event: string, listener: (par } }; +function generateInterfaceContribution(component: string, iface: object, name: string) { + const uLabel = generateLabelName(name, true); + return ` +export class ${uLabel}ComponentObject implements ComponentObject { + typeId = "${component}/${name}"; + + constructor(public impl: ${uLabel}Interface, public name: string) {} + + call(caller: Component, method: string, params: JsonObject | undefined): Promise | JsonObject | void { + void caller, params; + + switch (method) { +${"methods" in iface ? iface.methods && Object.keys(iface.methods).map(method => { + if ("params" in (iface.methods as any)[method]) { + return ` case "${method}": return this.impl.${generateLabelName(method, false)}(caller, params as any);\n` + } else { + return ` case "${method}": return this.impl.${generateLabelName(method, false)}(caller);\n` + } +}).join("\n") : ""} + + default: + throw createError(ErrorCode.MethodNotFound); + } + } + + notify(caller: Component, method: string, params: JsonObject | undefined): void | Promise { + void caller, params; + + switch (method) { +${"notifications" in iface ? iface.notifications && Object.keys(iface.notifications).map(notification => { + if ("params" in (iface.notifications as any)[notification]) { + return ` case "${notification}": return this.impl.${generateLabelName(notification, false)}(caller, params as any);\n` + } else { + return ` case "${notification}": return this.impl.${generateLabelName(notification, false)}(caller);\n` + } +}).join("\n") : ""} + + default: + throw createError(ErrorCode.MethodNotFound); + } + } + + dispose(): void | Promise { + if ("dispose" in this.impl && typeof this.impl.dispose == "function") { + return this.impl.dispose(); + } + } +} + +export class ${uLabel} { + constructor(private id: number) {} + +${"methods" in iface ? iface.methods && Object.keys(iface.methods).map(method => { + const methodTypeLabel = generateComponentLabelName(component, `${name}-${method}`, true); + if ("params" in (iface.methods as any)[method]) { + return ` async ${generateLabelName(method, false)}(request: ${methodTypeLabel}Request): Promise<${methodTypeLabel}Response> { + return (await ${component == "core" ? "" : "core."}objectCall({ object: this.id, method: "${method}", params: request})).result as any; + } +` + } else { + return ` async ${generateLabelName(method, false)}(): Promise<${methodTypeLabel}Response> { + return (await ${component == "core" ? "" : "core."}objectCall({ object: this.id, method: "${method}", params: {}})).result as any; + } +` + } + }).join("\n") : ""} +${"notifications" in iface ? iface.notifications && Object.keys(iface.notifications).map(notification => { + const methodTypeLabel = generateComponentLabelName(component, `${name}-${notification}`, true); + if ("params" in (iface.notifications as any)[notification]) { + return ` async ${generateLabelName(notification, false)}(request: ${methodTypeLabel}Request) { + return ${component == "core" ? "" : "core."}objectNotify({ object: this.id, notification: "${notification}", params: request}); + } +` + } else { + return ` async ${generateLabelName(notification, false)}() { + return ${component == "core" ? "" : "core."}objectNotify({ object: this.id, notification: "${notification}", params: {}}); + } +` + } + }).join("\n") : ""} + destroy() { + return ${component == "core" ? "" : "core."}objectDestroy({ object: this.id }); + } + + async getName() { + return (await ${component == "core" ? "" : "core."}objectGetName({ object: this.id })).name; + } + + getId() { + return this.id; + } +}; + +export async function get${uLabel}Objects() { + return (await ${component == "core" ? "" : "core."}objectGetList({ interface: "${component}/${name}" })).objects.map(id => new ${uLabel}(id)); +} +export async function find${uLabel}Object(name: string) { + return new ${uLabel}((await ${component == "core" ? "" : "core."}objectFind({ interfaceName: "${component}/${name}", objectName: name })).object); +} +export function to${uLabel}(handle: number): ${uLabel} { + return new ${uLabel}(handle); +} +`; +} + class ServerComponentApiGenerator implements ContributionGenerator { private body = ''; private viewBody = ''; - private externalComponent?: string; + private interfaceBody = ''; generateMethod(component: string, method: object, name: string) { if ("virtual" in method && method.virtual === true) { return; } - this.externalComponent ??= component; const label = generateComponentLabelName(component, name, false); const uLabel = generateComponentLabelName(component, name, true); this.body += `export async function ${label}(params: ${uLabel}Request): Promise<${uLabel}Response> { @@ -825,7 +969,6 @@ class ServerComponentApiGenerator implements ContributionGenerator { return; } - this.externalComponent ??= component; const label = generateComponentLabelName(component, name, false); const uLabel = generateComponentLabelName(component, name, true); this.body += `export async function ${label}(params: ${uLabel}Request) { @@ -834,8 +977,34 @@ class ServerComponentApiGenerator implements ContributionGenerator { `; } + generateInterface(component: string, iface: object, name: string) { + const uLabel = generateLabelName(name, true); + this.interfaceBody += generateInterfaceContribution(component, iface, name); + this.interfaceBody += ` +export async function create${uLabel}Object(name: string, Constructor: new (id: number, ...args: Params) => Impl, ...args: Params) { + const id = (await ${component == "core" ? "" : "core."}objectCreate({ interface: "${component}/${name}", name })).object; + const impl = new Constructor(id, ...args); + self.registerObject(id, new ${uLabel}ComponentObject(impl, name)); + return impl; +} +export function on${uLabel}Created(handler: (object: ${uLabel}) => Promise | void) { + return ${component == "core" ? "" : "core."}onObjectCreated((params) => { + if (params.interface == "${component}/${name}") { + handler(new ${uLabel}(params.object)); + } + }); +} +export function onAny${uLabel}Created(handler: () => Promise | void) { + return ${component == "core" ? "" : "core."}onObjectCreated((params) => { + if (params.interface == "${component}/${name}") { + handler(); + } + }); +} +`; + } + generateEvent(component: string, event: object, name: string) { - this.externalComponent ??= component; const label = generateComponentLabelName(component, name, true); if (Object.keys(event).length == 0) { this.body += `export function on${label}(handler: () => Promise | void) { @@ -850,7 +1019,7 @@ class ServerComponentApiGenerator implements ContributionGenerator { `; } - generateView(_component: string, _path: string, name: string) { + generateView(component: string, _path: string, name: string) { this.viewBody += ` export function push${name}View(target: Window, params: ${name}Props) { return target.pushView("${name}", params); @@ -862,18 +1031,30 @@ export function set${name}View(target: Window, params: ${name}Props) { `; } - toString(): string { - if (this.body.length === 0 || !this.externalComponent) { - return `${generatedHeader}export { };\n`; - } - + toString(component: string): string { return `${generatedHeader} -import { thisComponent } from "$/component-info"; -import * as ${generateLabelName(this.externalComponent, false)} from '$${this.externalComponent}/api'; -${this.viewBody && "import { Window } from '$core/Window';"} +${this.body && `import { thisComponent } from "$/component-info";` || ""} +import * as ${generateLabelName(component, false)} from '$${component}/api'; +${this.viewBody && "import { Window } from '$core/Window';" || ""} +${(this.interfaceBody && component != "core") && "import * as core from '$core'" || ""} +${this.interfaceBody && "import * as self from './api';" || ""} + +import { createError } from '$core/Error'; + +type ComponentObject = { + typeId: string; + name: string; + impl: object; + call(caller: Component, method: string, params: JsonObject | undefined): Promise | JsonObject | void; + notify(caller: Component, method: string, params: JsonObject | undefined): void | Promise; + dispose(): void | Promise; +}; ${this.body} ${this.viewBody} +${this.interfaceBody} + +export { }; `; } }; @@ -969,7 +1150,7 @@ export async function ${label}(params: ${uLabel}Request) { this.notifyBody += ` case "${name}": return notify${uLabel}(caller, params as ${uLabel}Request);\n`; } - generateView(component: string, _path: string, name: string) { + generateView(_component: string, _path: string, name: string) { this.viewBody += ` export function push${name}View(target: Window, params: ${name}Props) { return target.pushView("${name}", params); @@ -994,64 +1175,22 @@ export function set${name}View(target: Window, params: ${name}Props) { generateInterface(component: string, iface: object, name: string) { const uLabel = generateLabelName(name, true); + + this.interfaceBody += generateInterfaceContribution(component, iface, name); this.interfaceBody += ` -export class ${uLabel}Interface { - constructor(private id: number) {} - -${"methods" in iface ? iface.methods && Object.keys(iface.methods).map(method => { - const methodTypeLabel = generateComponentLabelName(component, method, true); - if ("params" in (iface.methods as any)[method]) { - return ` async ${generateLabelName(method, false)}(request: ${methodTypeLabel}Request): Promise<${methodTypeLabel}Response> { - return (await core.objectCall({ object: this.id, method: "${method}", params: request})).result as ${methodTypeLabel}Response; - } -` - } else { - return ` async ${generateLabelName(method, false)}(): Promise<${methodTypeLabel}Response> { - return (await core.objectCall({ object: this.id, method: "${method}", params: {}})).result as ${methodTypeLabel}Response; - } -` - } -}).join("\n") : ""} -${"notifications" in iface ? iface.notifications && Object.keys(iface.notifications).map(notification => { - const methodTypeLabel = generateComponentLabelName(component, notification, true); - if ("params" in (iface.notifications as any)[notification]) { - return ` async ${generateLabelName(notification, false)}(request: ${methodTypeLabel}Request) { - return core.objectNotify({ object: this.id, notification: "${notification}", params: request}); - } -` - } else { - return ` async ${generateLabelName(notification, false)}() { - return core.objectNotify({ object: this.id, notification: "${notification}", params: {}}); - } -` - } -}).join("\n") : ""} - destroy() { - return core.objectDestroy({ object: this.id }); - } - - async getName() { - return (await core.objectGetName({ object: this.id })).name; - } - - getId() { - return this.id; - } -}; - -export async function get${uLabel}Objects() { - return (await core.objectGetList({ interface: "${component}/${name}" })).objects.map(id => new ${uLabel}Interface(id)); +export async function create${uLabel}Object(name: string, Constructor: new (id: number, ...args: Params) => Impl, ...args: Params) { + const id = (await ${component == "core" ? "" : "core."}objectCreate({ interface: "${component}/${name}", name })).object; + const impl = new Constructor(id, ...args); + objects[id] = new ${uLabel}ComponentObject(impl, name); + return impl; } -export async function find${uLabel}Object(name: string) { - return new ${uLabel}Interface((await core.objectFind({ interfaceName: "${component}/${name}", objectName: name })).object); -} -export async function create${uLabel}Object(name: string) { - return new ${uLabel}Interface((await core.objectCreate({ interface: "${component}/${name}", name })).object); -} -export function on${uLabel}Created(handler: (object: ${uLabel}Interface) => Promise | void) { +`; + if (component != 'core') { + this.interfaceBody += ` +export function on${uLabel}Created(handler: (object: ${uLabel}) => Promise | void) { return core.onObjectCreated((params) => { if (params.interface == "${component}/${name}") { - handler(new ${uLabel}Interface(params.object)); + handler(new ${uLabel}(params.object)); } }); } @@ -1063,34 +1202,14 @@ export function onAny${uLabel}Created(handler: () => Promise | void) { }); } `; + } } toString(): string { - if (this.body.length === 0) { - return `${generatedHeader} -import { createError } from "$core/Error"; -import { Component } from "$core/Component"; -import * as core from "$core"; -${this.viewBody && "import { Window } from '$core/Window';"} - -export async function call(_caller: Component, _method: string, _params: JsonObject | undefined): Promise { - throw createError(ErrorCode.MethodNotFound); -} - -export async function notify(_caller: Component, _method: string, _params: JsonObject | undefined) { - throw createError(ErrorCode.MethodNotFound); -} - -export const settings = {${this.settingBody}}; -${this.viewBody} -`; - } - return `${generatedHeader} ${this.callBody.length > 0 || this.notifyBody.length > 0 ? 'import * as impl from "$/main";' : ""} import { createError } from "$core/Error"; -import { Component } from "$core/Component"; import { thisComponent } from "$/component-info"; import * as core from "$core"; ${this.viewBody && "import { Window } from '$core/Window';"} @@ -1098,13 +1217,53 @@ export { thisComponent } from "$/component-info"; ${this.body} +type ComponentObject = { + typeId: string; + name: string; + impl: object; + call(caller: Component, method: string, params: JsonObject | undefined): Promise | JsonObject | void; + notify(caller: Component, method: string, params: JsonObject | undefined): void | Promise; + dispose(): void | Promise; +}; + +const objects: Record = {}; + +export async function destroyObject(object: number) { + await core.objectDestroy({ object }); + const instance = objects[object]; + if (instance) { + instance.dispose(); + delete objects[object]; + } +} +export function registerObject(id: number, object: ComponentObject) { + objects[id] = object; +} + +export function ownObjects() { + return Object.values(objects); +} + export async function call(caller: Component, method: string, params: JsonObject | undefined): Promise { void caller, params; switch (method) { ${this.callBody} - default: - throw createError(ErrorCode.MethodNotFound); + case "$/object/call": + if (params && typeof params.method == "string" && typeof params.object == 'number') { + if (params.object in objects) { + return objects[params.object].call(caller, params.method, + "params" in params ? params.params as any : undefined + ); + } + + throw createError(ErrorCode.InvalidParams); + } + + throw createError(ErrorCode.InvalidRequest); + + default: + throw createError(ErrorCode.MethodNotFound); } } @@ -1113,8 +1272,21 @@ export async function notify(caller: Component, method: string, params: JsonObje switch (method) { ${this.notifyBody} - default: - throw createError(ErrorCode.MethodNotFound); + case "$/object/notify": + if (params && typeof params.notification == "string" && typeof params.object == 'number') { + if (params.object in objects) { + return objects[params.object].notify(caller, params.notification, + "params" in params ? params.params as any : undefined + ); + } + + throw createError(ErrorCode.InvalidParams); + } + + throw createError(ErrorCode.InvalidRequest); + + default: + throw createError(ErrorCode.MethodNotFound); } } @@ -1331,7 +1503,7 @@ export async function generateContributions = T extends [never] ? string | symbol : K | keyof T; + +type Listener = T extends [never] ? F : ( + K extends keyof T ? ( + T[K] extends unknown[] ? (...args: T[K]) => void : never + ) + : never +); + +declare global { + export type ComponentContext = { + manage(...objects: (IDisposable | (() => void))[]): void; + subscribe | [never] = [never]>(emitter: NodeJS.EventEmitter, channel: Key, listener: Listener void>): void; + }; + + export type ComponentId = string; + + export type Component = { + getId(): ComponentId; + onClose(listener: () => void | Promise): IDisposable; + sendEvent(event: string, params?: any): void; + }; +} diff --git a/rpcsx-ui/src/core/lib/Component.ts b/rpcsx-ui/src/core/lib/Component.ts deleted file mode 100644 index d7635d1..0000000 --- a/rpcsx-ui/src/core/lib/Component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { IDisposable } from "./Disposable"; - - -type Key = T extends [never] ? string | symbol : K | keyof T; - -type Listener = T extends [never] ? F : ( - K extends keyof T ? ( - T[K] extends unknown[] ? (...args: T[K]) => void : never - ) - : never -); - -export type ComponentContext = { - manage(...objects: (IDisposable | (() => void))[]): void; - subscribe | [never] = [never]>(emitter: NodeJS.EventEmitter, channel: Key, listener: Listener void>): void; -}; - -export type ComponentId = string; - -export type Component = { - getId(): ComponentId; - onClose(listener: () => void | Promise): IDisposable; - sendEvent(event: string, params?: any): void; -}; diff --git a/rpcsx-ui/src/core/lib/path.ts b/rpcsx-ui/src/core/lib/path.ts new file mode 100644 index 0000000..120f18a --- /dev/null +++ b/rpcsx-ui/src/core/lib/path.ts @@ -0,0 +1,11 @@ +export function join(...a: string[]) { + return a.join("/"); +} + +export function toURI(path: string) { + if (path.startsWith("/")) { + return "file://" + path; + } else { + return "file:///" + path; + } +} diff --git a/rpcsx-ui/src/core/server/ComponentActivation.web.ts b/rpcsx-ui/src/core/server/ComponentActivation.web.ts index 8aeada4..b3e8c16 100644 --- a/rpcsx-ui/src/core/server/ComponentActivation.web.ts +++ b/rpcsx-ui/src/core/server/ComponentActivation.web.ts @@ -1,6 +1,5 @@ import { ComponentInstance } from "./ComponentInstance"; import { Disposable } from "$/Disposable"; -import { Component } from "lib/Component"; import { ipcMain } from "electron"; import { createError } from "lib/Error"; diff --git a/rpcsx-ui/src/core/server/ComponentInstance.ts b/rpcsx-ui/src/core/server/ComponentInstance.ts index 478e750..fb8ad0b 100644 --- a/rpcsx-ui/src/core/server/ComponentInstance.ts +++ b/rpcsx-ui/src/core/server/ComponentInstance.ts @@ -1,5 +1,4 @@ import * as Event from "$/Event"; -import { ComponentContext, ComponentId, Component } from "$/Component"; import { Disposable, IDisposable } from "$/Disposable"; import { isJsonObject } from '$/Json'; import { createError } from "$/Error"; @@ -23,6 +22,7 @@ export type IComponentImpl = IDisposable & { deactivate(context: ComponentContext): void | Promise; call?(caller: Component, method: string, params: JsonObject | undefined): Promise; notify?(caller: Component, notification: string, params: JsonObject | undefined): Promise; + getPid?(): number; } const activateEvent = "activate"; @@ -154,6 +154,10 @@ export class ComponentInstance implements ComponentContext { } } + getPid() { + return this.impl.getPid ? this.impl.getPid() : 0; + } + subscribe | [never] = [never]>(emitter: NodeJS.EventEmitter, channel: Key, listener: Listener void>) { emitter.on(channel, listener); const disposable = Disposable.Create(() => { emitter.off(channel, listener); }); diff --git a/rpcsx-ui/src/core/server/Extension.ts b/rpcsx-ui/src/core/server/Extension.ts index f5c142b..e6683ad 100644 --- a/rpcsx-ui/src/core/server/Extension.ts +++ b/rpcsx-ui/src/core/server/Extension.ts @@ -1,7 +1,6 @@ import { Process } from './Launcher'; import packageJson from '../../../../package.json' with { type: "json" }; -import { Component, ComponentContext } from '$core/Component.js'; import { findComponent, getComponentId, registerComponent, uninitializeComponent, IComponentImpl } from './ComponentInstance'; import { createError } from 'lib/Error'; @@ -72,6 +71,10 @@ export class Extension implements IComponentImpl { registerComponent(this.componentManifest, this); } + getPid() { + return this.extensionProcess.getPid(); + } + private debugLog(message: string) { const date = new Date().toLocaleString("en-US"); for (const line of message.split('\n')) { diff --git a/rpcsx-ui/src/core/server/Launcher.ts b/rpcsx-ui/src/core/server/Launcher.ts index 98f9768..ac8f82d 100644 --- a/rpcsx-ui/src/core/server/Launcher.ts +++ b/rpcsx-ui/src/core/server/Launcher.ts @@ -11,6 +11,7 @@ export type Process = { on: (event: 'close' | 'exit', listener: (...args: any[]) => void) => void; once: (event: 'close' | 'exit', listener: (...args: any[]) => void) => void; off: (event: 'close' | 'exit', listener: (...args: any[]) => void) => void; + getPid(): number; } export type LaunchParams = { diff --git a/rpcsx-ui/src/core/server/Objects.ts b/rpcsx-ui/src/core/server/Objects.ts index 250c881..b25b9cc 100644 --- a/rpcsx-ui/src/core/server/Objects.ts +++ b/rpcsx-ui/src/core/server/Objects.ts @@ -1,4 +1,3 @@ -import { Component, ComponentId } from "lib/Component"; import { createError } from "lib/Error"; import { ComponentInstance, findComponentById } from "./ComponentInstance"; import * as self from '$'; diff --git a/rpcsx-ui/src/core/server/main.ts b/rpcsx-ui/src/core/server/main.ts index 7d055b0..d24a9da 100644 --- a/rpcsx-ui/src/core/server/main.ts +++ b/rpcsx-ui/src/core/server/main.ts @@ -1,5 +1,4 @@ import * as self from '$'; -import { Component } from '$core/Component'; import { createError } from '$core/Error'; import { ComponentInstance, findComponentById, getActivatedComponentList, getComponentList, initializeComponent } from './ComponentInstance'; import * as instance from './ComponentInstance'; diff --git a/rpcsx-ui/src/core/server/registerBuiltinLaunchers.web.ts b/rpcsx-ui/src/core/server/registerBuiltinLaunchers.web.ts index 1490d45..6eb5b78 100644 --- a/rpcsx-ui/src/core/server/registerBuiltinLaunchers.web.ts +++ b/rpcsx-ui/src/core/server/registerBuiltinLaunchers.web.ts @@ -4,18 +4,20 @@ import { Target } from "./Target"; import { fork, spawn } from "child_process"; import { Duplex } from "stream"; import { EventEmitter } from "events"; +import * as locations from '$/locations'; const nativeLauncher: Launcher = { launch: async (path: string, args: string[], params: LaunchParams) => { const newProcess = spawn(path, args, { argv0: path, - cwd: dirname(path), + cwd: locations.rootPath, signal: params.signal, stdio: 'pipe' }); newProcess.stdout.setEncoding('utf8'); newProcess.stderr.setEncoding('utf8'); + const pid = newProcess.pid ?? 0; const result: Process = { stdin: newProcess.stdin, @@ -33,7 +35,8 @@ const nativeLauncher: Launcher = { }, off: (event: string, handler: (...args: any[]) => void) => { newProcess.off(event, handler); - } + }, + getPid: () => pid }; return result; @@ -50,6 +53,7 @@ const nodeLauncher: Launcher = { newProcess.stdout!.setEncoding('utf8'); newProcess.stderr!.setEncoding('utf8'); + const pid = newProcess.pid ?? 0; const result: Process = { stdin: newProcess.stdin!, @@ -67,7 +71,8 @@ const nodeLauncher: Launcher = { }, off: (event: string, handler: (...args: any[]) => void) => { newProcess.off(event, handler); - } + }, + getPid: () => pid }; return result; @@ -106,7 +111,10 @@ const inlineLauncher: Launcher = { }, off: (event: string, handler: (...args: any[]) => void) => { eventEmitter.off(event, handler); - } + }, + getPid() { + return 0; + }, }; return result; diff --git a/rpcsx-ui/src/explorer/server/Component.ts b/rpcsx-ui/src/explorer/server/Component.ts index 60979c6..39e489e 100644 --- a/rpcsx-ui/src/explorer/server/Component.ts +++ b/rpcsx-ui/src/explorer/server/Component.ts @@ -1,4 +1,3 @@ -import { Component, ComponentContext } from "$core/Component"; import * as self from "$"; import * as progress from "$progress"; import { IDisposable } from "$core/Disposable"; diff --git a/rpcsx-ui/src/explorer/server/main.ts b/rpcsx-ui/src/explorer/server/main.ts index 25adc94..61209e9 100644 --- a/rpcsx-ui/src/explorer/server/main.ts +++ b/rpcsx-ui/src/explorer/server/main.ts @@ -1,4 +1,3 @@ -import { Component, ComponentContext } from "$core/Component"; import { createError } from "$core/Error"; import { ExplorerComponent } from "./Component"; diff --git a/rpcsx-ui/src/extension-host/component.json b/rpcsx-ui/src/extension-host/component.json index b309298..f3e5997 100644 --- a/rpcsx-ui/src/extension-host/component.json +++ b/rpcsx-ui/src/extension-host/component.json @@ -6,7 +6,10 @@ "name": "settings" }, { - "name": "app" + "name": "explorer" + }, + { + "name": "fs" } ] } \ No newline at end of file diff --git a/rpcsx-ui/src/extension-host/server/extension-host.ts b/rpcsx-ui/src/extension-host/server/extension-host.ts index 2509390..6920cb3 100644 --- a/rpcsx-ui/src/extension-host/server/extension-host.ts +++ b/rpcsx-ui/src/extension-host/server/extension-host.ts @@ -1,2 +1,44 @@ -export function activateLocalExtensions(_list: Set) { +import * as core from "$core"; +import * as locations from "$core/locations"; +import * as fs from '$fs'; +import * as path from '$core/path'; + +export async function activateLocalExtensions(list: Set) { + try { + const rootPath = locations.localExtensionsPath; + for (const entry of (await fs.fsReadDir({ uri: path.toURI(rootPath) })).items) { + if (entry.type != FsDirEntryType.Directory) { + continue; + } + + try { + await fs.fsStat({ uri: path.toURI(path.join(rootPath, entry.name, "extension.json")) }); + } catch { + continue; + } + + try { + await core.extensionLoad({ id: entry.name }); + } catch (e) { + console.error(`failed to load local extension ${entry.name}`, e); + continue; + } + + try { + await core.componentActivate({ id: entry.name }); + } catch (e) { + console.error(`failed to activate extension ${entry.name}`, e); + try { + await core.extensionUnload({ id: entry.name }); + } catch (e) { + console.error(`failed to unload extension ${entry.name}`, e); + } + continue; + } + + list.add(entry.name); + } + } catch (e) { + console.error(`failed to load local extensions`, e); + } } diff --git a/rpcsx-ui/src/extension-host/server/extension-host.web.ts b/rpcsx-ui/src/extension-host/server/extension-host.web.ts deleted file mode 100644 index 02ef9aa..0000000 --- a/rpcsx-ui/src/extension-host/server/extension-host.web.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as core from "$core"; -import * as locations from "$core/locations"; -import fs from 'fs/promises'; -import path from 'path'; - -export async function activateLocalExtensions(list: Set) { - try { - for (const entry of await fs.readdir(locations.localExtensionsPath, { withFileTypes: true, encoding: 'utf-8' })) { - if (!entry.isDirectory()) { - continue; - } - - try { - await fs.stat(path.join(path.join(entry.parentPath, entry.name, "extension.json"))); - } catch { - continue; - } - - try { - await core.extensionLoad({ id: entry.name }); - } catch (e) { - console.error(`failed to load local extension ${entry.name}`, e); - continue; - } - - try { - await core.componentActivate({ id: entry.name }); - } catch (e) { - console.error(`failed to activate extension ${entry.name}`, e); - try { - await core.extensionUnload({ id: entry.name }); - } catch (e) { - console.error(`failed to unload extension ${entry.name}`, e); - } - continue; - } - - list.add(entry.name); - } - } catch (e) { - console.error(`failed to load local extensions`, e); - } -} diff --git a/rpcsx-ui/src/fs/component.json b/rpcsx-ui/src/fs/component.json new file mode 100644 index 0000000..a95cedf --- /dev/null +++ b/rpcsx-ui/src/fs/component.json @@ -0,0 +1,198 @@ +{ + "name": "fs", + "version": "0.1.0", + "contributions": { + "types": { + "dir-entry-type": { + "type": "enum", + "enumerators": { + "file": 0, + "directory": 1, + "block-device": 2, + "character-device": 2, + "symbolic-link": 3, + "fifo": 4, + "socket": 5, + "other": 6 + } + }, + "dir-entry": { + "type": "object", + "params": { + "type": { + "type": "dir-entry-type" + }, + "name": { + "type": "string" + } + } + }, + "file-stat": { + "type": "object", + "params": { + "type": { + "type": "dir-entry-type" + }, + "size": { + "type": "number" + } + } + } + }, + "interfaces": { + "file": { + "methods": { + "read": { + "params": { + "offset": { + "type": "number" + }, + "size": { + "type": "number" + } + }, + "returns": { + "data": { + "type": "array", + "item-type": "number" + } + } + }, + "write": { + "params": { + "offset": { + "type": "number" + }, + "data": { + "type": "array", + "item-type": "number" + } + }, + "returns": { + "written": { + "type": "number" + } + } + }, + "close": {} + } + }, + "file-system": { + "methods": { + "open": { + "params": { + "uri": { + "type": "string" + } + }, + "returns": { + "file": { + "type": "number" + } + } + }, + "read-to-string": { + "params": { + "uri": { + "type": "string" + } + }, + "returns": { + "data": { + "type": "string" + } + } + }, + "read-dir": { + "params": { + "uri": { + "type": "string" + } + }, + "returns": { + "items": { + "type": "array", + "item-type": "dir-entry" + } + } + }, + "stat": { + "params": { + "uri": { + "type": "string" + } + }, + "returns": { + "item": { + "type": "file-stat" + } + } + } + } + } + }, + "methods": { + "open": { + "handler": "handleOpen", + "params": { + "uri": { + "type": "string" + }, + "flags": { + "type": "string", + "optional": true + }, + "mode": { + "type": "number", + "optional": true + } + }, + "returns": { + "file": { + "type": "number" + } + } + }, + "read-to-string": { + "handler": "handleReadToString", + "params": { + "uri": { + "type": "string" + } + }, + "returns": { + "data": { + "type": "string" + } + } + }, + "read-dir": { + "handler": "handleReadDir", + "params": { + "uri": { + "type": "string" + } + }, + "returns": { + "items": { + "type": "array", + "item-type": "dir-entry" + } + } + }, + "stat": { + "handler": "handleStat", + "params": { + "uri": { + "type": "string" + } + }, + "returns": { + "item": { + "type": "file-stat" + } + } + } + } + } +} \ No newline at end of file diff --git a/rpcsx-ui/src/fs/server/fs.ts b/rpcsx-ui/src/fs/server/fs.ts new file mode 100644 index 0000000..4c33d8b --- /dev/null +++ b/rpcsx-ui/src/fs/server/fs.ts @@ -0,0 +1,25 @@ +import { createError } from "$core/Error"; + +export async function initialize() { + throw createError(ErrorCode.InternalError, "not implemented"); +} + +export async function uninitialize() { + throw createError(ErrorCode.InternalError, "not implemented"); +} + +export async function open(_caller: Component, _request: FsOpenRequest): Promise { + throw createError(ErrorCode.InternalError, "not implemented"); +} + +export async function readToString(_caller: Component, _request: FsReadToStringRequest): Promise { + throw createError(ErrorCode.InternalError, "not implemented"); +} + +export async function readDir(_caller: Component, _request: FsReadDirRequest): Promise { + throw createError(ErrorCode.InternalError, "not implemented"); +} + +export async function stat(_caller: Component, _request: FsStatRequest): Promise { + throw createError(ErrorCode.InternalError, "not implemented"); +} diff --git a/rpcsx-ui/src/fs/server/fs.web.ts b/rpcsx-ui/src/fs/server/fs.web.ts new file mode 100644 index 0000000..a93d340 --- /dev/null +++ b/rpcsx-ui/src/fs/server/fs.web.ts @@ -0,0 +1,168 @@ +import * as self from '$'; +import * as core from '$core'; +import { createError } from "$core/Error"; +import fs from 'fs/promises'; + +class NativeFile implements FileInterface { + constructor(private id: number, private handle: fs.FileHandle) { } + + async close(_caller: Component) { + core.objectDestroy({ + object: this.id + }); + await this.handle.close(); + } + + async read(_caller: Component, request: FsFileReadRequest): Promise { + const buffer = new Uint8Array(request.size); + const result = await this.handle.read(buffer, 0, request.size, request.offset); + + return { + data: Array.from(result.buffer) + }; + } + + async write(_caller: Component, request: FsFileWriteRequest): Promise { + const result = await this.handle.write(Uint8Array.from(request.data), { + position: request.offset + }); + + return { + written: result.bytesWritten + }; + } + + getId() { + return this.id; + } +} + +type WithFileType = { + isFile(): boolean; + isDirectory(): boolean; + isBlockDevice(): boolean; + isCharacterDevice(): boolean; + isSymbolicLink(): boolean; + isFIFO(): boolean; + isSocket(): boolean; +}; + +function toFileType(fileType: WithFileType) { + if (fileType.isFile()) { + return FsDirEntryType.File; + } + if (fileType.isDirectory()) { + return FsDirEntryType.Directory; + } + if (fileType.isBlockDevice()) { + return FsDirEntryType.BlockDevice; + } + if (fileType.isCharacterDevice()) { + return FsDirEntryType.CharacterDevice; + } + if (fileType.isSocket()) { + return FsDirEntryType.Socket; + } + if (fileType.isSymbolicLink()) { + return FsDirEntryType.SymbolicLink; + } + if (fileType.isFIFO()) { + return FsDirEntryType.Fifo; + } + + return FsDirEntryType.Other; + +} + +class NativeFileSystem implements FileSystemInterface { + async open(_caller: Component, request: FsFileSystemOpenRequest): Promise { + const filePath = new URL(request.uri).pathname; + + try { + const descriptor = await fs.open(filePath, "rb"); + await self.createFileObject(request.uri, NativeFile, descriptor); + } catch (e) { } + + throw createError(ErrorCode.InvalidParams); + } + + async readToString(_caller: Component, request: FsFileSystemReadToStringRequest): Promise { + const filePath = new URL(request.uri).pathname; + + try { + return { + data: await fs.readFile(filePath, { encoding: "utf8" }) + }; + } catch (e) { } + + throw createError(ErrorCode.InvalidParams); + } + + async readDir(_caller: Component, request: FsFileSystemReadDirRequest): Promise { + const path = new URL(request.uri).pathname; + const result = await fs.readdir(path, { withFileTypes: true }); + + + return { + items: result.map(item => { + const result: FsDirEntry = { + name: item.name, + type: toFileType(item) + }; + + return result; + }) + }; + } + + async stat(_caller: Component, request: FsFileSystemStatRequest): Promise { + const path = new URL(request.uri).pathname; + const result = await fs.stat(path); + + return { + item: { + size: result.size, + type: toFileType(result) + } + }; + } + +} + +export async function initialize() { + await self.createFileSystemObject("file:", NativeFileSystem); +} + +export async function uninitialize() { + await Promise.all(self.ownObjects().map(object => object.dispose())); +} + +export async function open(_caller: Component, request: FsOpenRequest): Promise { + const protocol = new URL(request.uri).protocol; + + const object = await self.findFileSystemObject(protocol); + return await object.open(request); +} + +export async function readToString(_caller: Component, request: FsReadToStringRequest): Promise { + const protocol = new URL(request.uri).protocol; + + const object = await self.findFileSystemObject(protocol); + return await object.readToString(request); +} + +export async function readDir(_caller: Component, request: FsReadDirRequest): Promise { + const protocol = new URL(request.uri).protocol; + + const object = await self.findFileSystemObject(protocol); + return await object.readDir(request); +} + + +export async function stat(_caller: Component, request: FsStatRequest): Promise { + const protocol = new URL(request.uri).protocol; + + const object = await self.findFileSystemObject(protocol); + return await object.stat(request); +} + diff --git a/rpcsx-ui/src/fs/server/main.ts b/rpcsx-ui/src/fs/server/main.ts new file mode 100644 index 0000000..6b744dc --- /dev/null +++ b/rpcsx-ui/src/fs/server/main.ts @@ -0,0 +1,24 @@ +import * as fs from './fs'; + +export async function activate() { + await fs.initialize(); +} +export async function deactivate() { + await fs.uninitialize(); +} + +export async function handleOpen(caller: Component, request: FsOpenRequest) { + return fs.open(caller, request); +} + +export async function handleReadToString(caller: Component, request: FsReadToStringRequest) { + return fs.readToString(caller, request); +} + +export async function handleReadDir(caller: Component, request: FsReadDirRequest) { + return fs.readDir(caller, request); +} + +export async function handleStat(caller: Component, request: FsStatRequest) { + return fs.stat(caller, request); +} diff --git a/rpcsx-ui/src/github/server/main.ts b/rpcsx-ui/src/github/server/main.ts index 9d1a43d..ec83e9a 100644 --- a/rpcsx-ui/src/github/server/main.ts +++ b/rpcsx-ui/src/github/server/main.ts @@ -1,4 +1,3 @@ -import { Component } from '$core/Component'; import * as github from './github'; export async function handleReleasesLatest(_caller: Component, params: GithubReleasesLatestRequest): Promise { diff --git a/rpcsx-ui/src/progress/server/main.ts b/rpcsx-ui/src/progress/server/main.ts index f6a888a..6df5954 100644 --- a/rpcsx-ui/src/progress/server/main.ts +++ b/rpcsx-ui/src/progress/server/main.ts @@ -1,5 +1,3 @@ -// import { ipcMain } from 'electron'; -import { Component } from '$core/Component'; import { createError } from '$core/Error'; import * as api from '$';