this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+7937 -4207
.github
.vscode
nix
packages
browser
core
core-extensions
injector
node-preload
types
web-preload
scripts
-75
.eslintrc.json
··· 1 - { 2 - "root": true, 3 - "extends": [ 4 - "eslint:recommended", 5 - "plugin:@typescript-eslint/recommended", 6 - "plugin:prettier/recommended", 7 - "plugin:react/recommended" 8 - ], 9 - "plugins": ["@typescript-eslint", "prettier", "react"], 10 - "parser": "@typescript-eslint/parser", 11 - "env": { 12 - "browser": true, 13 - "node": true 14 - }, 15 - "parserOptions": { 16 - "ecmaFeatures": { 17 - "jsx": true 18 - }, 19 - "ecmaVersion": "latest", 20 - "sourceType": "module" 21 - }, 22 - "rules": { 23 - "indent": "off", 24 - "eqeqeq": [ 25 - "error", 26 - "always", 27 - { 28 - "null": "ignore" 29 - } 30 - ], 31 - "quotes": [ 32 - "error", 33 - "double", 34 - { "avoidEscape": true, "allowTemplateLiterals": true } 35 - ], 36 - "@typescript-eslint/no-unused-vars": [ 37 - "error", 38 - { "args": "none", "varsIgnorePattern": "^_" } 39 - ], 40 - // Mostly so we don't forget to leave these in when committing 41 - "no-console": "error", 42 - "no-debugger": "error", 43 - 44 - // Quite honestly we're interacting with so much unknown within Discord that 45 - // this being enabled is a hinderance 46 - "@typescript-eslint/no-explicit-any": "off", 47 - 48 - "@typescript-eslint/no-var-requires": "off", 49 - 50 - // https://canary.discord.com/channels/1154257010532032512/1154275441788583996/1181760413231230976 51 - "no-unused-labels": "off", 52 - 53 - // baseUrl being set to ./packages/ makes language server suggest "types/src" instead of "@moonlight-mod/types" 54 - "no-restricted-imports": [ 55 - "error", 56 - { 57 - "patterns": [ 58 - { 59 - "group": ["types/*"], 60 - "message": "Use @moonlight-mod/types instead" 61 - }, 62 - { 63 - "group": ["core/*"], 64 - "message": "Use @moonlight-mod/core instead" 65 - } 66 - ] 67 - } 68 - ] 69 - }, 70 - "settings": { 71 - "react": { 72 - "version": "18.2" 73 - } 74 - } 75 - }
···
+4 -8
.github/workflows/browser.yml
··· 10 name: Browser extension builds 11 runs-on: ubuntu-latest 12 steps: 13 - - uses: actions/checkout@v3 14 - 15 - - uses: pnpm/action-setup@v2 16 - with: 17 - version: 9 18 - run_install: false 19 - - uses: actions/setup-node@v3 20 with: 21 - node-version: 18 22 cache: pnpm 23 24 - name: Install dependencies
··· 10 name: Browser extension builds 11 runs-on: ubuntu-latest 12 steps: 13 + - uses: actions/checkout@v4 14 + - uses: pnpm/action-setup@v4 15 + - uses: actions/setup-node@v4 16 with: 17 + node-version: 22 18 cache: pnpm 19 20 - name: Install dependencies
+4 -8
.github/workflows/lint.yml
··· 9 name: Lint commits 10 runs-on: ubuntu-latest 11 steps: 12 - - uses: actions/checkout@v3 13 - 14 - - uses: pnpm/action-setup@v2 15 - with: 16 - version: 9 17 - run_install: false 18 - - uses: actions/setup-node@v3 19 with: 20 - node-version: 18 21 cache: pnpm 22 23 - name: Install dependencies
··· 9 name: Lint commits 10 runs-on: ubuntu-latest 11 steps: 12 + - uses: actions/checkout@v4 13 + - uses: pnpm/action-setup@v4 14 + - uses: actions/setup-node@v4 15 with: 16 + node-version: 22 17 cache: pnpm 18 19 - name: Install dependencies
+7 -11
.github/workflows/nightly.yml
··· 15 name: Nightly builds on GitHub Pages 16 runs-on: ubuntu-latest 17 steps: 18 - - uses: actions/checkout@v3 19 - 20 - - uses: pnpm/action-setup@v2 21 - with: 22 - version: 9 23 - run_install: false 24 - - uses: actions/setup-node@v3 25 with: 26 - node-version: 18 27 cache: pnpm 28 29 - name: Install dependencies ··· 47 echo "$(date +%s)" >> ./dist/ref 48 49 - name: Setup GitHub Pages 50 - uses: actions/configure-pages@v3 51 - name: Upload artifact 52 - uses: actions/upload-pages-artifact@v1 53 with: 54 path: ./dist 55 - name: Deploy to GitHub Pages 56 - uses: actions/deploy-pages@v2
··· 15 name: Nightly builds on GitHub Pages 16 runs-on: ubuntu-latest 17 steps: 18 + - uses: actions/checkout@v4 19 + - uses: pnpm/action-setup@v4 20 + - uses: actions/setup-node@v4 21 with: 22 + node-version: 22 23 cache: pnpm 24 25 - name: Install dependencies ··· 43 echo "$(date +%s)" >> ./dist/ref 44 45 - name: Setup GitHub Pages 46 + uses: actions/configure-pages@v5 47 - name: Upload artifact 48 + uses: actions/upload-pages-artifact@v3 49 with: 50 path: ./dist 51 - name: Deploy to GitHub Pages 52 + uses: actions/deploy-pages@v4
+16
.github/workflows/nix.yml
···
··· 1 + name: Check Nix flake 2 + on: [push, pull_request] 3 + 4 + permissions: 5 + checks: write 6 + 7 + jobs: 8 + nix: 9 + name: Check Nix flake 10 + runs-on: ubuntu-latest 11 + steps: 12 + - uses: actions/checkout@v4 13 + - uses: DeterminateSystems/nix-installer-action@main 14 + 15 + - name: Build default flake output 16 + run: nix build
+4 -8
.github/workflows/release.yml
··· 13 name: Release builds to GitHub Releases 14 runs-on: ubuntu-latest 15 steps: 16 - - uses: actions/checkout@v3 17 - 18 - - uses: pnpm/action-setup@v2 19 - with: 20 - version: 9 21 - run_install: false 22 - - uses: actions/setup-node@v3 23 with: 24 - node-version: 18 25 cache: pnpm 26 27 - name: Install dependencies
··· 13 name: Release builds to GitHub Releases 14 runs-on: ubuntu-latest 15 steps: 16 + - uses: actions/checkout@v4 17 + - uses: pnpm/action-setup@v4 18 + - uses: actions/setup-node@v4 19 with: 20 + node-version: 22 21 cache: pnpm 22 23 - name: Install dependencies
+32
.github/workflows/types.yml
···
··· 1 + name: Publish types on npm 2 + on: workflow_dispatch 3 + 4 + permissions: 5 + contents: read 6 + pages: write 7 + id-token: write 8 + 9 + jobs: 10 + types: 11 + name: Publish types on npm 12 + runs-on: ubuntu-latest 13 + steps: 14 + - uses: actions/checkout@v4 15 + - uses: pnpm/action-setup@v4 16 + - uses: actions/setup-node@v4 17 + with: 18 + node-version: 22 19 + cache: pnpm 20 + registry-url: https://registry.npmjs.org 21 + 22 + - name: Install dependencies 23 + run: pnpm install --frozen-lockfile 24 + - name: Build moonlight 25 + env: 26 + NODE_ENV: production 27 + run: pnpm run build 28 + 29 + - name: Publish types 30 + run: pnpm publish --filter=./packages/types --access public --no-git-checks 31 + env: 32 + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+5 -1
.gitignore
··· 3 dist.tar.gz 4 .DS_Store 5 eslint_report.json 6 - 7 # Nix 8 /result 9 *.drv
··· 3 dist.tar.gz 4 .DS_Store 5 eslint_report.json 6 + .eslintcache 7 # Nix 8 /result 9 *.drv 10 + 11 + # IDEs 12 + .vscode/ 13 + .idea/
+4 -4
.prettierrc
··· 1 { 2 - "printWidth": 80, 3 - "trailingComma": "none", 4 - "tabWidth": 2, 5 - "singleQuote": false 6 }
··· 1 { 2 + "printWidth": 120, 3 + "trailingComma": "none", 4 + "tabWidth": 2, 5 + "singleQuote": false 6 }
-14
.vscode/tasks.json
··· 1 - { 2 - "version": "2.0.0", 3 - "tasks": [ 4 - { 5 - "label": "build", 6 - "type": "shell", 7 - "command": "pnpm run build", 8 - "group": { 9 - "kind": "build", 10 - "isDefault": true 11 - } 12 - } 13 - ] 14 - }
···
+4 -1
CHANGELOG.md
··· 1 - - core-extensions/contextMenu: Fix patches
··· 1 + ## Core 2 + 3 + - Updated mappings 4 + - Fixed using remapped paths as patch finds not working
+10 -3
README.md
··· 5 <img src="./img/wordmark.png" alt="moonlight" /> 6 </picture> 7 8 - <a href="https://discord.gg/FdZBTFCP6F">Discord server</a> 9 \- <a href="https://github.com/moonlight-mod/moonlight">GitHub</a> 10 - \- <a href="https://moonlight-mod.github.io/">Docs</a> 11 12 <hr /> 13 </h3> 14 15 **moonlight** is yet another Discord client mod, focused on providing a decent user and developer experience. 16 17 moonlight is heavily inspired by hh3 (a private client mod) and the projects before it that it is inspired by, namely EndPwn. All core code is original or used with permission from their respective authors where not copyleft. 18 19 - **_This is an experimental passion project._** Anything and everything is subject to change, but it is stable enough for developers to experiment with. 20 21 moonlight is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.html) (`LGPL-3.0-or-later`). See [the documentation](https://moonlight-mod.github.io/) for more information.
··· 5 <img src="./img/wordmark.png" alt="moonlight" /> 6 </picture> 7 8 + <a href="https://moonlight-mod.github.io/using/install">Install</a> 9 + \- <a href="https://moonlight-mod.github.io/ext-dev/getting-started">Docs</a> 10 + \- <a href="https://discord.gg/FdZBTFCP6F">Discord server</a> 11 \- <a href="https://github.com/moonlight-mod/moonlight">GitHub</a> 12 13 <hr /> 14 + 15 + <picture> 16 + <source media="(prefers-color-scheme: dark)" srcset="https://moonlight-mod.github.io/moonbase.png"> 17 + <source media="(prefers-color-scheme: light)" srcset="https://moonlight-mod.github.io/moonbase-light.png"> 18 + <img src="https://moonlight-mod.github.io/moonbase.png" alt="A screenshot of Moonbase, the moonlight UI" /> 19 + </picture> 20 </h3> 21 22 **moonlight** is yet another Discord client mod, focused on providing a decent user and developer experience. 23 24 moonlight is heavily inspired by hh3 (a private client mod) and the projects before it that it is inspired by, namely EndPwn. All core code is original or used with permission from their respective authors where not copyleft. 25 26 + moonlight is a **_passion project_** - things may break from time to time, but we try our best to keep things working in a timely manner. 27 28 moonlight is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.html) (`LGPL-3.0-or-later`). See [the documentation](https://moonlight-mod.github.io/) for more information.
+34 -44
build.mjs
··· 15 const watch = process.argv.includes("--watch"); 16 const browser = process.argv.includes("--browser"); 17 const mv2 = process.argv.includes("--mv2"); 18 19 const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev"; 20 const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev"; ··· 69 name: "build-log", 70 setup(build) { 71 build.onEnd((result) => { 72 - console.log( 73 - `[${timeFormatter.format(new Date())}] [${tag}] build finished` 74 - ); 75 }); 76 } 77 }); ··· 104 MOONLIGHT_VERSION: `"${buildVersion}"` 105 }; 106 107 - for (const iterName of [ 108 - "injector", 109 - "node-preload", 110 - "web-preload", 111 - "browser" 112 - ]) { 113 const snake = iterName.replace(/-/g, "_").toUpperCase(); 114 define[`MOONLIGHT_${snake}`] = (name === iterName).toString(); 115 } ··· 121 if (name === "browser") { 122 plugins.push( 123 copyStaticFiles({ 124 - src: mv2 125 - ? "./packages/browser/manifestv2.json" 126 - : "./packages/browser/manifest.json", 127 dest: `./dist/${browserDir}/manifest.json` 128 }) 129 ); ··· 145 146 plugins.push( 147 copyStaticFiles({ 148 - src: mv2 149 - ? "./packages/browser/src/background-mv2.js" 150 - : "./packages/browser/src/background.js", 151 dest: `./dist/${browserDir}/background.js` 152 }) 153 ); ··· 174 dropLabels, 175 176 logLevel: "silent", 177 - plugins 178 }; 179 180 if (name === "browser") { 181 const coreExtensionsJson = {}; 182 183 - // eslint-disable-next-line no-inner-declarations 184 function readDir(dir) { 185 const files = fs.readdirSync(dir); 186 for (const file of files) { ··· 189 if (fs.statSync(filePath).isDirectory()) { 190 readDir(filePath); 191 } else { 192 - coreExtensionsJson[normalizedPath] = fs.readFileSync( 193 - filePath, 194 - "utf8" 195 - ); 196 } 197 } 198 } ··· 200 readDir("./dist/core-extensions"); 201 202 esbuildConfig.banner = { 203 - js: `window._moonlight_coreExtensionsStr = ${JSON.stringify( 204 - JSON.stringify(coreExtensionsJson) 205 - )};` 206 }; 207 } 208 ··· 214 } 215 } 216 217 - async function buildExt(ext, side, copyManifest, fileExt) { 218 const outdir = path.join("./dist", "core-extensions", ext); 219 if (!fs.existsSync(outdir)) { 220 fs.mkdirSync(outdir, { recursive: true }); 221 } 222 223 - const entryPoints = [ 224 - `packages/core-extensions/src/${ext}/${side}.${fileExt}` 225 - ]; 226 227 const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`; 228 if (fs.existsSync(wpModulesDir) && side === "index") { 229 const wpModules = fs.opendirSync(wpModulesDir); 230 for await (const wpModule of wpModules) { 231 if (wpModule.isFile()) { 232 - entryPoints.push( 233 - `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}` 234 - ); 235 } else { 236 for (const fileExt of ["ts", "tsx"]) { 237 const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`; ··· 259 } 260 }; 261 262 const esbuildConfig = { 263 entryPoints, 264 outdir, ··· 278 }, 279 logLevel: "silent", 280 plugins: [ 281 - ...(copyManifest 282 ? [ 283 copyStaticFiles({ 284 - src: `./packages/core-extensions/src/${ext}/manifest.json`, 285 - dest: `./dist/core-extensions/${ext}/manifest.json` 286 }) 287 ] 288 : []), ··· 302 303 const promises = []; 304 305 - if (browser) { 306 build("browser", "packages/browser/src/index.ts"); 307 } else { 308 for (const [name, entry] of Object.entries(config)) { ··· 311 312 const coreExtensions = fs.readdirSync("./packages/core-extensions/src"); 313 for (const ext of coreExtensions) { 314 - let copiedManifest = false; 315 - 316 for (const fileExt of ["ts", "tsx"]) { 317 for (const type of ["index", "node", "host"]) { 318 - if ( 319 - fs.existsSync( 320 - `./packages/core-extensions/src/${ext}/${type}.${fileExt}` 321 - ) 322 - ) { 323 - promises.push(buildExt(ext, type, !copiedManifest, fileExt)); 324 - copiedManifest = true; 325 } 326 } 327 }
··· 15 const watch = process.argv.includes("--watch"); 16 const browser = process.argv.includes("--browser"); 17 const mv2 = process.argv.includes("--mv2"); 18 + const clean = process.argv.includes("--clean"); 19 20 const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev"; 21 const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev"; ··· 70 name: "build-log", 71 setup(build) { 72 build.onEnd((result) => { 73 + console.log(`[${timeFormatter.format(new Date())}] [${tag}] build finished`); 74 }); 75 } 76 }); ··· 103 MOONLIGHT_VERSION: `"${buildVersion}"` 104 }; 105 106 + for (const iterName of ["injector", "node-preload", "web-preload", "browser"]) { 107 const snake = iterName.replace(/-/g, "_").toUpperCase(); 108 define[`MOONLIGHT_${snake}`] = (name === iterName).toString(); 109 } ··· 115 if (name === "browser") { 116 plugins.push( 117 copyStaticFiles({ 118 + src: mv2 ? "./packages/browser/manifestv2.json" : "./packages/browser/manifest.json", 119 dest: `./dist/${browserDir}/manifest.json` 120 }) 121 ); ··· 137 138 plugins.push( 139 copyStaticFiles({ 140 + src: mv2 ? "./packages/browser/src/background-mv2.js" : "./packages/browser/src/background.js", 141 dest: `./dist/${browserDir}/background.js` 142 }) 143 ); ··· 164 dropLabels, 165 166 logLevel: "silent", 167 + plugins, 168 + 169 + // https://github.com/evanw/esbuild/issues/3944 170 + footer: 171 + name === "web-preload" 172 + ? { 173 + js: `\n//# sourceURL=${name}.js` 174 + } 175 + : undefined 176 }; 177 178 if (name === "browser") { 179 const coreExtensionsJson = {}; 180 181 function readDir(dir) { 182 const files = fs.readdirSync(dir); 183 for (const file of files) { ··· 186 if (fs.statSync(filePath).isDirectory()) { 187 readDir(filePath); 188 } else { 189 + coreExtensionsJson[normalizedPath] = fs.readFileSync(filePath, "utf8"); 190 } 191 } 192 } ··· 194 readDir("./dist/core-extensions"); 195 196 esbuildConfig.banner = { 197 + js: `window._moonlight_coreExtensionsStr = ${JSON.stringify(JSON.stringify(coreExtensionsJson))};` 198 }; 199 } 200 ··· 206 } 207 } 208 209 + async function buildExt(ext, side, fileExt) { 210 const outdir = path.join("./dist", "core-extensions", ext); 211 if (!fs.existsSync(outdir)) { 212 fs.mkdirSync(outdir, { recursive: true }); 213 } 214 215 + const entryPoints = [`packages/core-extensions/src/${ext}/${side}.${fileExt}`]; 216 217 const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`; 218 if (fs.existsSync(wpModulesDir) && side === "index") { 219 const wpModules = fs.opendirSync(wpModulesDir); 220 for await (const wpModule of wpModules) { 221 if (wpModule.isFile()) { 222 + entryPoints.push(`packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`); 223 } else { 224 for (const fileExt of ["ts", "tsx"]) { 225 const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`; ··· 247 } 248 }; 249 250 + const styleInput = `packages/core-extensions/src/${ext}/style.css`; 251 + const styleOutput = `dist/core-extensions/${ext}/style.css`; 252 + 253 const esbuildConfig = { 254 entryPoints, 255 outdir, ··· 269 }, 270 logLevel: "silent", 271 plugins: [ 272 + copyStaticFiles({ 273 + src: `./packages/core-extensions/src/${ext}/manifest.json`, 274 + dest: `./dist/core-extensions/${ext}/manifest.json` 275 + }), 276 + ...(fs.existsSync(styleInput) 277 ? [ 278 copyStaticFiles({ 279 + src: styleInput, 280 + dest: styleOutput 281 }) 282 ] 283 : []), ··· 297 298 const promises = []; 299 300 + if (clean) { 301 + fs.rmSync("./dist", { recursive: true, force: true }); 302 + } else if (browser) { 303 build("browser", "packages/browser/src/index.ts"); 304 } else { 305 for (const [name, entry] of Object.entries(config)) { ··· 308 309 const coreExtensions = fs.readdirSync("./packages/core-extensions/src"); 310 for (const ext of coreExtensions) { 311 for (const fileExt of ["ts", "tsx"]) { 312 for (const type of ["index", "node", "host"]) { 313 + if (fs.existsSync(`./packages/core-extensions/src/${ext}/${type}.${fileExt}`)) { 314 + promises.push(buildExt(ext, type, fileExt)); 315 } 316 } 317 }
+25
eslint.config.mjs
···
··· 1 + import config from "@moonlight-mod/eslint-config"; 2 + 3 + export default [ 4 + ...config, 5 + { 6 + rules: { 7 + // baseUrl being set to ./packages/ makes language server suggest "types/src" instead of "@moonlight-mod/types" 8 + "no-restricted-imports": [ 9 + "error", 10 + { 11 + patterns: [ 12 + { 13 + group: ["types/*"], 14 + message: "Use @moonlight-mod/types instead" 15 + }, 16 + { 17 + group: ["core/*"], 18 + message: "Use @moonlight-mod/core instead" 19 + } 20 + ] 21 + } 22 + ] 23 + } 24 + } 25 + ];
+4 -73
flake.lock
··· 18 "type": "github" 19 } 20 }, 21 - "flake-utils_2": { 22 - "inputs": { 23 - "systems": "systems_2" 24 - }, 25 - "locked": { 26 - "lastModified": 1701680307, 27 - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", 28 - "owner": "numtide", 29 - "repo": "flake-utils", 30 - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", 31 - "type": "github" 32 - }, 33 - "original": { 34 - "owner": "numtide", 35 - "repo": "flake-utils", 36 - "type": "github" 37 - } 38 - }, 39 "nixpkgs": { 40 "locked": { 41 - "lastModified": 1728067476, 42 - "narHash": "sha256-/uJcVXuBt+VFCPQIX+4YnYrHaubJSx4HoNsJVNRgANM=", 43 "owner": "NixOS", 44 "repo": "nixpkgs", 45 - "rev": "6e6b3dd395c3b1eb9be9f2d096383a8d05add030", 46 "type": "github" 47 }, 48 "original": { 49 "owner": "NixOS", 50 - "ref": "nixos-24.05", 51 - "repo": "nixpkgs", 52 - "type": "github" 53 - } 54 - }, 55 - "nixpkgs_2": { 56 - "locked": { 57 - "lastModified": 1727802920, 58 - "narHash": "sha256-HP89HZOT0ReIbI7IJZJQoJgxvB2Tn28V6XS3MNKnfLs=", 59 - "owner": "nixos", 60 - "repo": "nixpkgs", 61 - "rev": "27e30d177e57d912d614c88c622dcfdb2e6e6515", 62 - "type": "github" 63 - }, 64 - "original": { 65 - "owner": "nixos", 66 "ref": "nixos-unstable", 67 "repo": "nixpkgs", 68 "type": "github" 69 } 70 }, 71 - "pnpm2nix": { 72 - "inputs": { 73 - "flake-utils": "flake-utils_2", 74 - "nixpkgs": "nixpkgs_2" 75 - }, 76 - "locked": { 77 - "lastModified": 1728137762, 78 - "narHash": "sha256-iEFvPR3BopGyI5KjQ1DK+gEZ1dKDugq838tKdet2moQ=", 79 - "owner": "NotNite", 80 - "repo": "pnpm2nix-nzbr", 81 - "rev": "b7a60d3c7d106b601665e3f05dba6cdc6f59f959", 82 - "type": "github" 83 - }, 84 - "original": { 85 - "owner": "NotNite", 86 - "repo": "pnpm2nix-nzbr", 87 - "type": "github" 88 - } 89 - }, 90 "root": { 91 "inputs": { 92 "flake-utils": "flake-utils", 93 - "nixpkgs": "nixpkgs", 94 - "pnpm2nix": "pnpm2nix" 95 } 96 }, 97 "systems": { 98 - "locked": { 99 - "lastModified": 1681028828, 100 - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 101 - "owner": "nix-systems", 102 - "repo": "default", 103 - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 104 - "type": "github" 105 - }, 106 - "original": { 107 - "owner": "nix-systems", 108 - "repo": "default", 109 - "type": "github" 110 - } 111 - }, 112 - "systems_2": { 113 "locked": { 114 "lastModified": 1681028828, 115 "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
··· 18 "type": "github" 19 } 20 }, 21 "nixpkgs": { 22 "locked": { 23 + "lastModified": 1744232761, 24 + "narHash": "sha256-gbl9hE39nQRpZaLjhWKmEu5ejtQsgI5TWYrIVVJn30U=", 25 "owner": "NixOS", 26 "repo": "nixpkgs", 27 + "rev": "f675531bc7e6657c10a18b565cfebd8aa9e24c14", 28 "type": "github" 29 }, 30 "original": { 31 "owner": "NixOS", 32 "ref": "nixos-unstable", 33 "repo": "nixpkgs", 34 "type": "github" 35 } 36 }, 37 "root": { 38 "inputs": { 39 "flake-utils": "flake-utils", 40 + "nixpkgs": "nixpkgs" 41 } 42 }, 43 "systems": { 44 "locked": { 45 "lastModified": 1681028828, 46 "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+3 -4
flake.nix
··· 2 description = "Yet another Discord mod"; 3 4 inputs = { 5 - nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; 6 flake-utils.url = "github:numtide/flake-utils"; 7 - pnpm2nix.url = "github:NotNite/pnpm2nix-nzbr"; 8 }; 9 10 - outputs = { self, nixpkgs, flake-utils, pnpm2nix }: 11 - let overlay = import ./nix/overlay.nix { inherit pnpm2nix; }; 12 in flake-utils.lib.eachDefaultSystem (system: 13 let 14 pkgs = import nixpkgs {
··· 2 description = "Yet another Discord mod"; 3 4 inputs = { 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 flake-utils.url = "github:numtide/flake-utils"; 7 }; 8 9 + outputs = { self, nixpkgs, flake-utils }: 10 + let overlay = import ./nix/overlay.nix { }; 11 in flake-utils.lib.eachDefaultSystem (system: 12 let 13 pkgs = import nixpkgs {
+40 -17
nix/default.nix
··· 1 - { pkgs, mkPnpmPackage }: 2 3 - mkPnpmPackage rec { 4 - workspace = ./..; 5 src = ./..; 6 7 - # Work around a bug with how it expects dist 8 - components = [ 9 - "packages/core" 10 - "packages/core-extensions" 11 - "packages/injector" 12 - "packages/node-preload" 13 - "packages/types" 14 - "packages/web-preload" 15 ]; 16 - distDirs = [ "dist" ]; 17 18 - copyNodeModules = true; 19 - buildPhase = "pnpm run build"; 20 - installPhase = "cp -r dist $out"; 21 22 - meta = with pkgs.lib; { 23 description = "Yet another Discord mod"; 24 homepage = "https://moonlight-mod.github.io/"; 25 license = licenses.lgpl3; 26 maintainers = with maintainers; [ notnite ]; 27 }; 28 - }
··· 1 + { 2 + lib, 3 + stdenv, 4 + nodejs_22, 5 + pnpm_10, 6 + }: 7 + 8 + stdenv.mkDerivation (finalAttrs: { 9 + pname = "moonlight"; 10 + version = (builtins.fromJSON (builtins.readFile ./../package.json)).version; 11 12 src = ./..; 13 14 + nativeBuildInputs = [ 15 + nodejs_22 16 + pnpm_10.configHook 17 ]; 18 + 19 + pnpmDeps = pnpm_10.fetchDeps { 20 + inherit (finalAttrs) pname version src; 21 + hash = "sha256-I+zRCUqJabpGJRFBGW0NrM9xzyzeCjioF54zlCpynBU="; 22 + }; 23 24 + env = { 25 + NODE_ENV = "production"; 26 + MOONLIGHT_VERSION = "v${finalAttrs.version}"; 27 + }; 28 + 29 + buildPhase = '' 30 + runHook preBuild 31 32 + pnpm run build 33 + 34 + runHook postBuild 35 + ''; 36 + 37 + installPhase = '' 38 + runHook preInstall 39 + 40 + cp -r dist $out 41 + 42 + runHook postInstall 43 + ''; 44 + 45 + meta = with lib; { 46 description = "Yet another Discord mod"; 47 homepage = "https://moonlight-mod.github.io/"; 48 license = licenses.lgpl3; 49 maintainers = with maintainers; [ notnite ]; 50 }; 51 + })
+3 -6
nix/overlay.nix
··· 1 - { pnpm2nix }: 2 3 let 4 nameTable = { ··· 29 ''; 30 31 packageJson = '' 32 - {"name":"discord","main":"./injector.js","private":true} 33 ''; 34 35 in old.installPhase + "\n" + '' ··· 49 ''; 50 }); 51 in final: prev: rec { 52 - moonlight-mod = final.callPackage ./default.nix { 53 - pkgs = final; 54 - mkPnpmPackage = pnpm2nix.packages.${final.system}.mkPnpmPackage; 55 - }; 56 discord = mkOverride prev moonlight-mod "discord"; 57 discord-ptb = mkOverride prev moonlight-mod "discord-ptb"; 58 discord-canary = mkOverride prev moonlight-mod "discord-canary";
··· 1 + { ... }: 2 3 let 4 nameTable = { ··· 29 ''; 30 31 packageJson = '' 32 + {"name":"${name}","main":"./injector.js","private":true} 33 ''; 34 35 in old.installPhase + "\n" + '' ··· 49 ''; 50 }); 51 in final: prev: rec { 52 + moonlight-mod = final.callPackage ./default.nix { }; 53 discord = mkOverride prev moonlight-mod "discord"; 54 discord-ptb = mkOverride prev moonlight-mod "discord-ptb"; 55 discord-canary = mkOverride prev moonlight-mod "discord-canary";
+24 -16
package.json
··· 1 { 2 "name": "moonlight", 3 - "version": "1.1.0", 4 "description": "Yet another Discord mod", 5 - "homepage": "https://moonlight-mod.github.io/", 6 "license": "LGPL-3.0-or-later", 7 "repository": { 8 "type": "git", 9 "url": "git+https://github.com/moonlight-mod/moonlight.git" ··· 11 "bugs": { 12 "url": "https://github.com/moonlight-mod/moonlight/issues" 13 }, 14 "scripts": { 15 "build": "node build.mjs", 16 "dev": "node build.mjs --watch", 17 "browser": "node build.mjs --browser", 18 "browser-mv2": "node build.mjs --browser --mv2", 19 "lint": "eslint packages", 20 - "lint:fix": "eslint packages", 21 - "lint:report": "eslint --output-file eslint_report.json --format json packages", 22 "typecheck": "tsc --noEmit", 23 "check": "pnpm run lint && pnpm run typecheck", 24 - "prepare": "husky install" 25 }, 26 "devDependencies": { 27 - "@typescript-eslint/eslint-plugin": "^6.13.2", 28 - "@typescript-eslint/parser": "^6.13.2", 29 - "esbuild": "^0.19.3", 30 - "esbuild-copy-static-files": "^0.1.0", 31 - "eslint": "^8.55.0", 32 - "eslint-config-prettier": "^9.1.0", 33 - "eslint-plugin-prettier": "^5.0.1", 34 - "eslint-plugin-react": "^7.33.2", 35 - "husky": "^8.0.3", 36 - "prettier": "^3.1.0", 37 - "typescript": "^5.3.2" 38 } 39 }
··· 1 { 2 "name": "moonlight", 3 + "version": "1.3.14", 4 + "packageManager": "pnpm@10.7.1", 5 "description": "Yet another Discord mod", 6 "license": "LGPL-3.0-or-later", 7 + "homepage": "https://moonlight-mod.github.io/", 8 "repository": { 9 "type": "git", 10 "url": "git+https://github.com/moonlight-mod/moonlight.git" ··· 12 "bugs": { 13 "url": "https://github.com/moonlight-mod/moonlight/issues" 14 }, 15 + "engineStrict": true, 16 + "engines": { 17 + "node": ">=22", 18 + "pnpm": ">=10", 19 + "npm": "pnpm", 20 + "yarn": "pnpm" 21 + }, 22 "scripts": { 23 "build": "node build.mjs", 24 "dev": "node build.mjs --watch", 25 + "clean": "node build.mjs --clean", 26 "browser": "node build.mjs --browser", 27 "browser-mv2": "node build.mjs --browser --mv2", 28 "lint": "eslint packages", 29 + "lint:fix": "pnpm lint --fix", 30 + "lint:report": "pnpm lint --output-file eslint_report.json --format json", 31 "typecheck": "tsc --noEmit", 32 "check": "pnpm run lint && pnpm run typecheck", 33 + "prepare": "husky install", 34 + "updates": "pnpm taze -r" 35 }, 36 "devDependencies": { 37 + "@moonlight-mod/eslint-config": "catalog:dev", 38 + "@types/node": "catalog:dev", 39 + "esbuild": "catalog:dev", 40 + "esbuild-copy-static-files": "catalog:dev", 41 + "eslint": "catalog:dev", 42 + "husky": "catalog:dev", 43 + "prettier": "catalog:dev", 44 + "taze": "catalog:dev", 45 + "typescript": "catalog:dev" 46 } 47 }
+2 -1
packages/browser/blockLoading.json
··· 6 "type": "block" 7 }, 8 "condition": { 9 - "urlFilter": "*://discord.com/assets/*.js", 10 "resourceTypes": ["script"] 11 } 12 }
··· 6 "type": "block" 7 }, 8 "condition": { 9 + "requestDomains": ["discord.com", "discordapp.com"], 10 + "urlFilter": "*/assets/*.js", 11 "resourceTypes": ["script"] 12 } 13 }
+7 -10
packages/browser/manifest.json
··· 1 { 2 "manifest_version": 3, 3 "name": "moonlight", 4 "description": "Yet another Discord mod", 5 - "version": "1.1.0", 6 - "permissions": [ 7 - "declarativeNetRequestWithHostAccess", 8 - "webRequest", 9 - "scripting", 10 - "webNavigation" 11 - ], 12 "host_permissions": [ 13 "https://moonlight-mod.github.io/*", 14 "https://api.github.com/*", 15 - "https://*.discord.com/*" 16 ], 17 "content_scripts": [ 18 { 19 "js": ["index.js"], 20 - "matches": ["https://*.discord.com/*"], 21 "run_at": "document_start", 22 "world": "MAIN" 23 } ··· 43 "web_accessible_resources": [ 44 { 45 "resources": ["index.js"], 46 - "matches": ["https://*.discord.com/*"] 47 } 48 ] 49 }
··· 1 { 2 + "$schema": "https://json.schemastore.org/chrome-manifest", 3 "manifest_version": 3, 4 "name": "moonlight", 5 "description": "Yet another Discord mod", 6 + "version": "1.3.14", 7 + "permissions": ["declarativeNetRequestWithHostAccess", "webRequest", "scripting", "webNavigation"], 8 "host_permissions": [ 9 "https://moonlight-mod.github.io/*", 10 "https://api.github.com/*", 11 + "https://*.discord.com/*", 12 + "https://*.discordapp.com/*" 13 ], 14 "content_scripts": [ 15 { 16 "js": ["index.js"], 17 + "matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"], 18 "run_at": "document_start", 19 "world": "MAIN" 20 } ··· 40 "web_accessible_resources": [ 41 { 42 "resources": ["index.js"], 43 + "matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"] 44 } 45 ] 46 }
+6 -5
packages/browser/manifestv2.json
··· 1 { 2 "manifest_version": 2, 3 "name": "moonlight", 4 "description": "Yet another Discord mod", 5 - "version": "1.1.0", 6 "permissions": [ 7 "webRequest", 8 "webRequestBlocking", 9 "scripting", 10 "webNavigation", 11 - "https://*.discord.com/assets/*.js", 12 "https://moonlight-mod.github.io/*", 13 - "https://api.github.com/*", 14 - "https://*.discord.com/*" 15 ], 16 "background": { 17 "scripts": ["background.js"] ··· 19 "content_scripts": [ 20 { 21 "js": ["index.js"], 22 - "matches": ["https://*.discord.com/*"], 23 "run_at": "document_start", 24 "world": "MAIN" 25 }
··· 1 { 2 + "$schema": "https://json.schemastore.org/chrome-manifest", 3 "manifest_version": 2, 4 "name": "moonlight", 5 "description": "Yet another Discord mod", 6 + "version": "1.3.14", 7 "permissions": [ 8 "webRequest", 9 "webRequestBlocking", 10 "scripting", 11 "webNavigation", 12 + "https://*.discord.com/*", 13 + "https://*.discordapp.com/*", 14 "https://moonlight-mod.github.io/*", 15 + "https://api.github.com/*" 16 ], 17 "background": { 18 "scripts": ["background.js"] ··· 20 "content_scripts": [ 21 { 22 "js": ["index.js"], 23 + "matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"], 24 "run_at": "document_start", 25 "world": "MAIN" 26 }
+1 -1
packages/browser/modifyResponseHeaders.json
··· 13 }, 14 "condition": { 15 "resourceTypes": ["main_frame"], 16 - "initiatorDomains": ["discord.com"] 17 } 18 } 19 ]
··· 13 }, 14 "condition": { 15 "resourceTypes": ["main_frame"], 16 + "requestDomains": ["discord.com"] 17 } 18 } 19 ]
+12 -2
packages/browser/package.json
··· 1 { 2 "name": "@moonlight-mod/browser", 3 "private": true, 4 "dependencies": { 5 "@moonlight-mod/core": "workspace:*", 6 "@moonlight-mod/types": "workspace:*", 7 "@moonlight-mod/web-preload": "workspace:*", 8 - "@zenfs/core": "^1.0.2", 9 - "@zenfs/dom": "^0.2.16" 10 } 11 }
··· 1 { 2 "name": "@moonlight-mod/browser", 3 "private": true, 4 + "engines": { 5 + "node": ">=22", 6 + "pnpm": ">=10", 7 + "npm": "pnpm", 8 + "yarn": "pnpm" 9 + }, 10 "dependencies": { 11 "@moonlight-mod/core": "workspace:*", 12 "@moonlight-mod/types": "workspace:*", 13 "@moonlight-mod/web-preload": "workspace:*", 14 + "@zenfs/core": "catalog:prod", 15 + "@zenfs/dom": "catalog:prod" 16 + }, 17 + "engineStrict": true, 18 + "devDependencies": { 19 + "@types/chrome": "catalog:dev" 20 } 21 }
+55 -70
packages/browser/src/background-mv2.js
··· 1 /* eslint-disable no-console */ 2 /* eslint-disable no-undef */ 3 4 - const starterUrls = ["web.", "sentry."]; 5 - let blockLoading = true; 6 - let doing = false; 7 - let collectedUrls = new Set(); 8 9 - chrome.webNavigation.onBeforeNavigate.addListener(async (details) => { 10 - const url = new URL(details.url); 11 - if (!blockLoading && url.hostname.endsWith("discord.com")) { 12 - console.log("Blocking", details.url); 13 - blockLoading = true; 14 - collectedUrls.clear(); 15 - } 16 - }); 17 18 - async function doTheThing(urls, tabId) { 19 - console.log("Doing", urls, tabId); 20 21 - blockLoading = false; 22 23 - try { 24 - await chrome.scripting.executeScript({ 25 - target: { tabId }, 26 - world: "MAIN", 27 - args: [urls], 28 - func: async (urls) => { 29 - try { 30 - await window._moonlightBrowserInit(); 31 - } catch (e) { 32 - console.log(e); 33 - } 34 35 - const scripts = [...document.querySelectorAll("script")].filter( 36 - (script) => script.src && urls.some((url) => url.includes(script.src)) 37 - ); 38 39 - // backwards 40 - urls.reverse(); 41 - for (const url of urls) { 42 - const script = scripts.find((script) => url.includes(script.src)); 43 - console.log("adding new script", script); 44 45 - const newScript = document.createElement("script"); 46 - for (const { name, value } of script.attributes) { 47 - newScript.setAttribute(name, value); 48 } 49 - 50 - script.remove(); 51 - document.documentElement.appendChild(newScript); 52 - } 53 - } 54 - }); 55 - } catch (e) { 56 - console.log(e); 57 - } 58 - 59 - doing = false; 60 - collectedUrls.clear(); 61 - } 62 - 63 - chrome.webRequest.onBeforeRequest.addListener( 64 - async (details) => { 65 - if (starterUrls.some((url) => details.url.includes(url))) { 66 - console.log("Adding", details.url); 67 - collectedUrls.add(details.url); 68 } 69 70 - if (collectedUrls.size === starterUrls.length) { 71 - if (doing) return; 72 - if (!blockLoading) return; 73 - doing = true; 74 - const urls = [...collectedUrls]; 75 - const tabId = details.tabId; 76 - 77 - // yes this is a load-bearing sleep 78 - setTimeout(() => doTheThing(urls, tabId), 0); 79 - } 80 - 81 - if (blockLoading) return { cancel: true }; 82 }, 83 { 84 - urls: ["https://*.discord.com/assets/*.js"] 85 }, 86 ["blocking"] 87 ); ··· 94 ) 95 }; 96 }, 97 - { urls: ["https://*.discord.com/*"] }, 98 ["blocking", "responseHeaders"] 99 );
··· 1 /* eslint-disable no-console */ 2 /* eslint-disable no-undef */ 3 4 + const scriptUrls = ["web.", "sentry."]; 5 + let blockedScripts = new Set(); 6 7 + chrome.webRequest.onBeforeRequest.addListener( 8 + async (details) => { 9 + if (details.tabId === -1) return; 10 11 + const url = new URL(details.url); 12 + const hasUrl = scriptUrls.some((scriptUrl) => { 13 + return ( 14 + details.url.includes(scriptUrl) && 15 + !url.searchParams.has("inj") && 16 + (url.host.endsWith("discord.com") || url.host.endsWith("discordapp.com")) 17 + ); 18 + }); 19 + if (hasUrl) blockedScripts.add(details.url); 20 21 + if (blockedScripts.size === scriptUrls.length) { 22 + const blockedScriptsCopy = Array.from(blockedScripts); 23 + blockedScripts.clear(); 24 25 + setTimeout(async () => { 26 + console.log("Starting moonlight"); 27 + await chrome.scripting.executeScript({ 28 + target: { tabId: details.tabId }, 29 + world: "MAIN", 30 + args: [blockedScriptsCopy], 31 + func: async (blockedScripts) => { 32 + console.log("Initializing moonlight"); 33 + try { 34 + await window._moonlightBrowserInit(); 35 + } catch (e) { 36 + console.error(e); 37 + } 38 39 + console.log("Readding scripts"); 40 + try { 41 + const scripts = [...document.querySelectorAll("script")].filter( 42 + (script) => script.src && blockedScripts.some((url) => url.includes(script.src)) 43 + ); 44 45 + blockedScripts.reverse(); 46 + for (const url of blockedScripts) { 47 + if (url.includes("/sentry.")) continue; 48 49 + const script = scripts.find((script) => url.includes(script.src)); 50 + const newScript = document.createElement("script"); 51 + for (const attr of script.attributes) { 52 + if (attr.name === "src") attr.value += "?inj"; 53 + newScript.setAttribute(attr.name, attr.value); 54 + } 55 + script.remove(); 56 + document.documentElement.appendChild(newScript); 57 + } 58 + } catch (e) { 59 + console.error(e); 60 + } 61 } 62 + }); 63 + }, 0); 64 } 65 66 + if (hasUrl) return { cancel: true }; 67 }, 68 { 69 + urls: ["https://*.discord.com/assets/*.js", "https://*.discordapp.com/assets/*.js"] 70 }, 71 ["blocking"] 72 ); ··· 79 ) 80 }; 81 }, 82 + { urls: ["https://*.discord.com/*", "https://*.discordapp.com/*"] }, 83 ["blocking", "responseHeaders"] 84 );
+37 -40
packages/browser/src/background.js
··· 1 /* eslint-disable no-console */ 2 /* eslint-disable no-undef */ 3 4 - const starterUrls = ["web.", "sentry."]; 5 - let blockLoading = true; 6 - let doing = false; 7 - let collectedUrls = new Set(); 8 9 chrome.webNavigation.onBeforeNavigate.addListener(async (details) => { 10 const url = new URL(details.url); 11 - if (!blockLoading && url.hostname.endsWith("discord.com")) { 12 await chrome.declarativeNetRequest.updateEnabledRulesets({ 13 enableRulesetIds: ["modifyResponseHeaders", "blockLoading"] 14 }); 15 - blockLoading = true; 16 - collectedUrls.clear(); 17 } 18 }); 19 20 chrome.webRequest.onBeforeRequest.addListener( 21 async (details) => { 22 if (details.tabId === -1) return; 23 - if (starterUrls.some((url) => details.url.includes(url))) { 24 - console.log("Adding", details.url); 25 - collectedUrls.add(details.url); 26 - } 27 28 - if (collectedUrls.size === starterUrls.length) { 29 - if (doing) return; 30 - if (!blockLoading) return; 31 - doing = true; 32 - const urls = [...collectedUrls]; 33 - console.log("Doing", urls); 34 35 console.log("Running moonlight script"); 36 try { ··· 40 files: ["index.js"] 41 }); 42 } catch (e) { 43 - console.log(e); 44 } 45 46 console.log("Initializing moonlight"); ··· 52 try { 53 await window._moonlightBrowserInit(); 54 } catch (e) { 55 - console.log(e); 56 } 57 } 58 }); ··· 60 console.log(e); 61 } 62 63 - console.log("Updating rulesets"); 64 try { 65 - blockLoading = false; 66 await chrome.declarativeNetRequest.updateEnabledRulesets({ 67 disableRulesetIds: ["blockLoading"], 68 enableRulesetIds: ["modifyResponseHeaders"] 69 }); 70 } catch (e) { 71 - console.log(e); 72 } 73 74 console.log("Readding scripts"); ··· 76 await chrome.scripting.executeScript({ 77 target: { tabId: details.tabId }, 78 world: "MAIN", 79 - args: [urls], 80 - func: async (urls) => { 81 const scripts = [...document.querySelectorAll("script")].filter( 82 - (script) => 83 - script.src && urls.some((url) => url.includes(script.src)) 84 ); 85 86 - // backwards 87 - urls.reverse(); 88 - for (const url of urls) { 89 - const script = scripts.find((script) => url.includes(script.src)); 90 - console.log("adding new script", script); 91 92 const newScript = document.createElement("script"); 93 - for (const { name, value } of script.attributes) { 94 - newScript.setAttribute(name, value); 95 } 96 - 97 script.remove(); 98 document.documentElement.appendChild(newScript); 99 } 100 } 101 }); 102 } catch (e) { 103 - console.log(e); 104 } 105 - 106 - console.log("Done"); 107 - doing = false; 108 - collectedUrls.clear(); 109 } 110 }, 111 { 112 - urls: ["*://*.discord.com/assets/*.js"] 113 } 114 );
··· 1 /* eslint-disable no-console */ 2 /* eslint-disable no-undef */ 3 4 + const scriptUrls = ["web.", "sentry."]; 5 + let blockedScripts = new Set(); 6 7 chrome.webNavigation.onBeforeNavigate.addListener(async (details) => { 8 const url = new URL(details.url); 9 + if ( 10 + !url.searchParams.has("inj") && 11 + (url.hostname.endsWith("discord.com") || url.hostname.endsWith("discordapp.com")) 12 + ) { 13 + console.log("Enabling block ruleset"); 14 await chrome.declarativeNetRequest.updateEnabledRulesets({ 15 enableRulesetIds: ["modifyResponseHeaders", "blockLoading"] 16 }); 17 } 18 }); 19 20 chrome.webRequest.onBeforeRequest.addListener( 21 async (details) => { 22 if (details.tabId === -1) return; 23 + 24 + const url = new URL(details.url); 25 + const hasUrl = scriptUrls.some((scriptUrl) => { 26 + return ( 27 + details.url.includes(scriptUrl) && 28 + !url.searchParams.has("inj") && 29 + (url.hostname.endsWith("discord.com") || url.hostname.endsWith("discordapp.com")) 30 + ); 31 + }); 32 33 + if (hasUrl) blockedScripts.add(details.url); 34 + 35 + if (blockedScripts.size === scriptUrls.length) { 36 + const blockedScriptsCopy = Array.from(blockedScripts); 37 + blockedScripts.clear(); 38 39 console.log("Running moonlight script"); 40 try { ··· 44 files: ["index.js"] 45 }); 46 } catch (e) { 47 + console.error(e); 48 } 49 50 console.log("Initializing moonlight"); ··· 56 try { 57 await window._moonlightBrowserInit(); 58 } catch (e) { 59 + console.error(e); 60 } 61 } 62 }); ··· 64 console.log(e); 65 } 66 67 + console.log("Disabling block ruleset"); 68 try { 69 await chrome.declarativeNetRequest.updateEnabledRulesets({ 70 disableRulesetIds: ["blockLoading"], 71 enableRulesetIds: ["modifyResponseHeaders"] 72 }); 73 } catch (e) { 74 + console.error(e); 75 } 76 77 console.log("Readding scripts"); ··· 79 await chrome.scripting.executeScript({ 80 target: { tabId: details.tabId }, 81 world: "MAIN", 82 + args: [blockedScriptsCopy], 83 + func: async (blockedScripts) => { 84 const scripts = [...document.querySelectorAll("script")].filter( 85 + (script) => script.src && blockedScripts.some((url) => url.includes(script.src)) 86 ); 87 88 + blockedScripts.reverse(); 89 + for (const url of blockedScripts) { 90 + if (url.includes("/sentry.")) continue; 91 92 + const script = scripts.find((script) => url.includes(script.src)); 93 const newScript = document.createElement("script"); 94 + for (const attr of script.attributes) { 95 + if (attr.name === "src") attr.value += "?inj"; 96 + newScript.setAttribute(attr.name, attr.value); 97 } 98 script.remove(); 99 document.documentElement.appendChild(newScript); 100 } 101 } 102 }); 103 } catch (e) { 104 + console.error(e); 105 } 106 } 107 }, 108 { 109 + urls: ["*://*.discord.com/assets/*.js", "*://*.discordapp.com/assets/*.js"] 110 } 111 );
+93 -81
packages/browser/src/index.ts
··· 4 import { getExtensions } from "@moonlight-mod/core/extension"; 5 import { loadExtensions } from "@moonlight-mod/core/extension/loader"; 6 import { MoonlightBranch, MoonlightNode } from "@moonlight-mod/types"; 7 import { IndexedDB } from "@zenfs/dom"; 8 - import { configure } from "@zenfs/core"; 9 import * as fs from "@zenfs/core/promises"; 10 11 function getParts(path: string) { 12 if (path.startsWith("/")) path = path.substring(1); ··· 14 } 15 16 window._moonlightBrowserInit = async () => { 17 // Set up a virtual filesystem with IndexedDB 18 - await configure({ 19 - mounts: { 20 - "/": { 21 - backend: IndexedDB, 22 - // eslint-disable-next-line @typescript-eslint/ban-ts-comment 23 - // @ts-ignore tsc tweaking 24 - storeName: "moonlight-fs" 25 - } 26 - } 27 }); 28 29 - window.moonlightFS = { 30 - async readFile(path) { 31 - return new Uint8Array(await fs.readFile(path)); 32 - }, 33 - async readFileString(path) { 34 - const file = await this.readFile(path); 35 - return new TextDecoder().decode(file); 36 - }, 37 - async writeFile(path, data) { 38 - await fs.writeFile(path, data); 39 - }, 40 - async writeFileString(path, data) { 41 - const file = new TextEncoder().encode(data); 42 - await this.writeFile(path, file); 43 - }, 44 - async unlink(path) { 45 - await fs.unlink(path); 46 - }, 47 48 - async readdir(path) { 49 - return await fs.readdir(path); 50 - }, 51 - async mkdir(path) { 52 - const parts = getParts(path); 53 - for (let i = 0; i < parts.length; i++) { 54 - const path = this.join(...parts.slice(0, i + 1)); 55 - if (!(await this.exists(path))) await fs.mkdir(path); 56 - } 57 - }, 58 59 - async rmdir(path) { 60 - const entries = await this.readdir(path); 61 62 - for (const entry of entries) { 63 - const fullPath = this.join(path, entry); 64 - const isFile = await this.isFile(fullPath); 65 - if (isFile) { 66 - await this.unlink(fullPath); 67 - } else { 68 - await this.rmdir(fullPath); 69 } 70 - } 71 72 - await fs.rmdir(path); 73 - }, 74 75 - async exists(path) { 76 - return await fs.exists(path); 77 - }, 78 - async isFile(path) { 79 - return (await fs.stat(path)).isFile(); 80 - }, 81 82 - join(...parts) { 83 - let str = parts.join("/"); 84 - if (!str.startsWith("/")) str = "/" + str; 85 - return str; 86 }, 87 - dirname(path) { 88 - const parts = getParts(path); 89 - return "/" + parts.slice(0, parts.length - 1).join("/"); 90 - } 91 }; 92 93 // Actual loading begins here 94 - const config = await readConfig(); 95 initLogger(config); 96 97 const extensions = await getExtensions(); 98 const processedExtensions = await loadExtensions(extensions); 99 100 - function getConfig(ext: string) { 101 - const val = config.extensions[ext]; 102 - if (val == null || typeof val === "boolean") return undefined; 103 - return val.config; 104 - } 105 - 106 const moonlightNode: MoonlightNode = { 107 - config, 108 extensions, 109 processedExtensions, 110 nativesCache: {}, 111 isBrowser: true, 112 113 version: MOONLIGHT_VERSION, 114 branch: MOONLIGHT_BRANCH as MoonlightBranch, 115 116 - getConfig, 117 - getConfigOption: <T>(ext: string, name: string) => { 118 - const config = getConfig(ext); 119 - if (config == null) return undefined; 120 - const option = config[name]; 121 - if (option == null) return undefined; 122 - return option as T; 123 }, 124 getNatives: () => {}, 125 getLogger: (id: string) => { 126 return new Logger(id); ··· 133 return `/extensions/${ext}`; 134 }, 135 136 - writeConfig 137 }; 138 139 Object.assign(window, { ··· 141 }); 142 143 // This is set by web-preload for us 144 - await window._moonlightBrowserLoad(); 145 };
··· 4 import { getExtensions } from "@moonlight-mod/core/extension"; 5 import { loadExtensions } from "@moonlight-mod/core/extension/loader"; 6 import { MoonlightBranch, MoonlightNode } from "@moonlight-mod/types"; 7 + import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config"; 8 import { IndexedDB } from "@zenfs/dom"; 9 + import { configureSingle } from "@zenfs/core"; 10 import * as fs from "@zenfs/core/promises"; 11 + import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event"; 12 + import { createEventEmitter } from "@moonlight-mod/core/util/event"; 13 14 function getParts(path: string) { 15 if (path.startsWith("/")) path = path.substring(1); ··· 17 } 18 19 window._moonlightBrowserInit = async () => { 20 + delete window._moonlightBrowserInit; 21 + 22 // Set up a virtual filesystem with IndexedDB 23 + await configureSingle({ 24 + backend: IndexedDB, 25 + storeName: "moonlight-fs" 26 }); 27 28 + window.moonlightNodeSandboxed = { 29 + fs: { 30 + async readFile(path) { 31 + return new Uint8Array(await fs.readFile(path)); 32 + }, 33 + async readFileString(path) { 34 + const file = await this.readFile(path); 35 + return new TextDecoder().decode(file); 36 + }, 37 + async writeFile(path, data) { 38 + await fs.writeFile(path, data); 39 + }, 40 + async writeFileString(path, data) { 41 + const file = new TextEncoder().encode(data); 42 + await this.writeFile(path, file); 43 + }, 44 + async unlink(path) { 45 + await fs.unlink(path); 46 + }, 47 48 + async readdir(path) { 49 + return await fs.readdir(path); 50 + }, 51 + async mkdir(path) { 52 + const parts = getParts(path); 53 + for (let i = 0; i < parts.length; i++) { 54 + const path = this.join(...parts.slice(0, i + 1)); 55 + if (!(await this.exists(path))) await fs.mkdir(path); 56 + } 57 + }, 58 59 + async rmdir(path) { 60 + const entries = await this.readdir(path); 61 62 + for (const entry of entries) { 63 + const fullPath = this.join(path, entry); 64 + const isFile = await this.isFile(fullPath); 65 + if (isFile) { 66 + await this.unlink(fullPath); 67 + } else { 68 + await this.rmdir(fullPath); 69 + } 70 } 71 72 + await fs.rmdir(path); 73 + }, 74 75 + async exists(path) { 76 + return await fs.exists(path); 77 + }, 78 + async isFile(path) { 79 + return (await fs.stat(path)).isFile(); 80 + }, 81 + async isDir(path) { 82 + return (await fs.stat(path)).isDirectory(); 83 + }, 84 85 + join(...parts) { 86 + let str = parts.join("/"); 87 + if (!str.startsWith("/")) str = "/" + str; 88 + return str; 89 + }, 90 + dirname(path) { 91 + const parts = getParts(path); 92 + return "/" + parts.slice(0, parts.length - 1).join("/"); 93 + } 94 }, 95 + // TODO 96 + addCors(url) {}, 97 + addBlocked(url) {} 98 }; 99 100 // Actual loading begins here 101 + let config = await readConfig(); 102 initLogger(config); 103 104 const extensions = await getExtensions(); 105 const processedExtensions = await loadExtensions(extensions); 106 107 const moonlightNode: MoonlightNode = { 108 + get config() { 109 + return config; 110 + }, 111 extensions, 112 processedExtensions, 113 nativesCache: {}, 114 isBrowser: true, 115 + events: createEventEmitter<NodeEventType, NodeEventPayloads>(), 116 117 version: MOONLIGHT_VERSION, 118 branch: MOONLIGHT_BRANCH as MoonlightBranch, 119 120 + getConfig(ext) { 121 + return getConfig(ext, config); 122 + }, 123 + getConfigOption(ext, name) { 124 + const manifest = getManifest(extensions, ext); 125 + return getConfigOption(ext, name, config, manifest?.settings); 126 + }, 127 + async setConfigOption(ext, name, value) { 128 + setConfigOption(config, ext, name, value); 129 + await this.writeConfig(config); 130 }, 131 + 132 getNatives: () => {}, 133 getLogger: (id: string) => { 134 return new Logger(id); ··· 141 return `/extensions/${ext}`; 142 }, 143 144 + async writeConfig(newConfig) { 145 + await writeConfig(newConfig); 146 + config = newConfig; 147 + this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig); 148 + } 149 }; 150 151 Object.assign(window, { ··· 153 }); 154 155 // This is set by web-preload for us 156 + await window._moonlightWebLoad!(); 157 };
+1
packages/browser/tsconfig.json
··· 1 { 2 "extends": "../../tsconfig.json", 3 "compilerOptions": { 4 "module": "ES2022" 5 } 6 }
··· 1 { 2 "extends": "../../tsconfig.json", 3 "compilerOptions": { 4 + "lib": ["DOM", "ESNext", "ESNext.AsyncIterable"], 5 "module": "ES2022" 6 } 7 }
+7
packages/core/package.json
··· 4 "exports": { 5 "./*": "./src/*.ts" 6 }, 7 "dependencies": { 8 "@moonlight-mod/types": "workspace:*" 9 }
··· 4 "exports": { 5 "./*": "./src/*.ts" 6 }, 7 + "engineStrict": true, 8 + "engines": { 9 + "node": ">=22", 10 + "pnpm": ">=10", 11 + "npm": "pnpm", 12 + "yarn": "pnpm" 13 + }, 14 "dependencies": { 15 "@moonlight-mod/types": "workspace:*" 16 }
+4 -8
packages/core/src/config.ts
··· 6 const logger = new Logger("core/config"); 7 8 const defaultConfig: Config = { 9 extensions: { 10 moonbase: true, 11 disableSentry: true, ··· 18 export async function writeConfig(config: Config) { 19 try { 20 const configPath = await getConfigPath(); 21 - await moonlightFS.writeFileString( 22 - configPath, 23 - JSON.stringify(config, null, 2) 24 - ); 25 } catch (e) { 26 logger.error("Failed to write config", e); 27 } ··· 33 } 34 35 const configPath = await getConfigPath(); 36 - if (!(await moonlightFS.exists(configPath))) { 37 await writeConfig(defaultConfig); 38 return defaultConfig; 39 } else { 40 try { 41 - let config: Config = JSON.parse( 42 - await moonlightFS.readFileString(configPath) 43 - ); 44 // Assign the default values if they don't exist (newly added) 45 config = { ...defaultConfig, ...config }; 46 await writeConfig(config);
··· 6 const logger = new Logger("core/config"); 7 8 const defaultConfig: Config = { 9 + // If you're updating this, update `builtinExtensions` in constants as well 10 extensions: { 11 moonbase: true, 12 disableSentry: true, ··· 19 export async function writeConfig(config: Config) { 20 try { 21 const configPath = await getConfigPath(); 22 + await moonlightNodeSandboxed.fs.writeFileString(configPath, JSON.stringify(config, null, 2)); 23 } catch (e) { 24 logger.error("Failed to write config", e); 25 } ··· 31 } 32 33 const configPath = await getConfigPath(); 34 + if (!(await moonlightNodeSandboxed.fs.exists(configPath))) { 35 await writeConfig(defaultConfig); 36 return defaultConfig; 37 } else { 38 try { 39 + let config: Config = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(configPath)); 40 // Assign the default values if they don't exist (newly added) 41 config = { ...defaultConfig, ...config }; 42 await writeConfig(config);
+17
packages/core/src/cors.ts
···
··· 1 + const cors: string[] = []; 2 + const blocked: string[] = []; 3 + 4 + export function registerCors(url: string) { 5 + cors.push(url); 6 + } 7 + 8 + export function registerBlocked(url: string) { 9 + blocked.push(url); 10 + } 11 + 12 + export function getDynamicCors() { 13 + return { 14 + cors, 15 + blocked 16 + }; 17 + }
+62 -59
packages/core/src/extension/loader.ts
··· 13 import calculateDependencies from "../util/dependency"; 14 import { createEventEmitter } from "../util/event"; 15 import { registerStyles } from "../styles"; 16 - import { EventPayloads, EventType } from "@moonlight-mod/types/core/event"; 17 18 const logger = new Logger("core/extension/loader"); 19 20 - function loadExtWeb(ext: DetectedExtension) { 21 if (ext.scripts.web != null) { 22 - const source = ext.scripts.web; 23 - const fn = new Function("require", "module", "exports", source); 24 25 - const module = { id: ext.id, exports: {} }; 26 - fn.apply(window, [ 27 - () => { 28 - logger.warn("Attempted to require() from web"); 29 - }, 30 - module, 31 - module.exports 32 - ]); 33 34 - const exports: ExtensionWebExports = module.exports; 35 if (exports.patches != null) { 36 let idx = 0; 37 for (const patch of exports.patches) { 38 if (Array.isArray(patch.replace)) { 39 - for (const replacement of patch.replace) { 40 - const newPatch = Object.assign({}, patch, { 41 - replace: replacement 42 - }); 43 - 44 - registerPatch({ ...newPatch, ext: ext.id, id: idx }); 45 - idx++; 46 - } 47 } else { 48 - registerPatch({ ...patch, ext: ext.id, id: idx }); 49 - idx++; 50 } 51 } 52 } 53 54 if (exports.webpackModules != null) { 55 for (const [name, wp] of Object.entries(exports.webpackModules)) { 56 if (wp.run == null && ext.scripts.webpackModules?.[name] != null) { 57 - const func = new Function( 58 - "module", 59 - "exports", 60 - "require", 61 - ext.scripts.webpackModules[name]! 62 - ) as WebpackModuleFunc; 63 registerWebpackModule({ 64 ...wp, 65 ext: ext.id, ··· 73 } 74 75 if (exports.styles != null) { 76 - registerStyles( 77 - exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`) 78 - ); 79 } 80 } 81 } 82 83 async function loadExt(ext: DetectedExtension) { 84 webTarget: { 85 - loadExtWeb(ext); 86 } 87 88 nodePreload: { ··· 113 InvalidEnvironment 114 } 115 116 - export function checkExtensionCompat( 117 - manifest: ExtensionManifest 118 - ): ExtensionCompat { 119 let environment; 120 webTarget: { 121 environment = ExtensionEnvironment.Web; ··· 124 environment = ExtensionEnvironment.Desktop; 125 } 126 127 - if (manifest.apiLevel !== constants.apiLevel) 128 - return ExtensionCompat.InvalidApiLevel; 129 - if ( 130 - (manifest.environment ?? "both") !== "both" && 131 - manifest.environment !== environment 132 - ) 133 return ExtensionCompat.InvalidEnvironment; 134 return ExtensionCompat.Compatible; 135 } ··· 148 extensions fires an event on completion, which allows us to await the loading 149 of another extension, resolving dependencies & load order effectively. 150 */ 151 - export async function loadExtensions( 152 - exts: DetectedExtension[] 153 - ): Promise<ProcessedExtensions> { 154 - exts = exts.filter( 155 - (ext) => checkExtensionCompat(ext.manifest) === ExtensionCompat.Compatible 156 - ); 157 158 const config = await readConfig(); 159 const items = exts ··· 193 }; 194 } 195 196 - export async function loadProcessedExtensions({ 197 - extensions, 198 - dependencyGraph 199 - }: ProcessedExtensions) { 200 - const eventEmitter = createEventEmitter<EventType, EventPayloads>(); 201 const finished: Set<string> = new Set(); 202 203 logger.trace( ··· 219 } 220 221 function done() { 222 - eventEmitter.removeEventListener(EventType.ExtensionLoad, cb); 223 r(); 224 } 225 226 - eventEmitter.addEventListener(EventType.ExtensionLoad, cb); 227 if (finished.has(dep)) done(); 228 }) 229 ); 230 231 if (waitPromises.length > 0) { 232 - logger.debug( 233 - `Waiting on ${waitPromises.length} dependencies for "${ext.id}"` 234 - ); 235 await Promise.all(waitPromises); 236 } 237 ··· 239 await loadExt(ext); 240 241 finished.add(ext.id); 242 - eventEmitter.dispatchEvent(EventType.ExtensionLoad, ext.id); 243 logger.debug(`Loaded "${ext.id}"`); 244 } 245
··· 13 import calculateDependencies from "../util/dependency"; 14 import { createEventEmitter } from "../util/event"; 15 import { registerStyles } from "../styles"; 16 + import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event"; 17 18 const logger = new Logger("core/extension/loader"); 19 20 + function evalIIFE(id: string, source: string): ExtensionWebExports { 21 + const fn = new Function("require", "module", "exports", source); 22 + 23 + const module = { id, exports: {} }; 24 + fn.apply(window, [ 25 + () => { 26 + logger.warn("Attempted to require() from web"); 27 + }, 28 + module, 29 + module.exports 30 + ]); 31 + 32 + return module.exports; 33 + } 34 + 35 + async function evalEsm(source: string): Promise<ExtensionWebExports> { 36 + // Data URLs (`data:`) don't seem to work under the CSP, but object URLs do 37 + const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" })); 38 + 39 + const module = await import(url); 40 + 41 + URL.revokeObjectURL(url); 42 + 43 + return module; 44 + } 45 + 46 + async function loadExtWeb(ext: DetectedExtension) { 47 if (ext.scripts.web != null) { 48 + const source = ext.scripts.web + `\n//# sourceURL=${ext.id}/web.js`; 49 50 + let exports: ExtensionWebExports; 51 52 + try { 53 + exports = evalIIFE(ext.id, source); 54 + } catch { 55 + logger.trace(`Failed to load IIFE for extension ${ext.id}, trying ESM loading`); 56 + exports = await evalEsm(source); 57 + } 58 + 59 if (exports.patches != null) { 60 let idx = 0; 61 for (const patch of exports.patches) { 62 if (Array.isArray(patch.replace)) { 63 + registerPatch({ ...patch, ext: ext.id, id: idx }); 64 } else { 65 + registerPatch({ ...patch, replace: [patch.replace], ext: ext.id, id: idx }); 66 } 67 + idx++; 68 } 69 } 70 71 if (exports.webpackModules != null) { 72 for (const [name, wp] of Object.entries(exports.webpackModules)) { 73 if (wp.run == null && ext.scripts.webpackModules?.[name] != null) { 74 + const source = ext.scripts.webpackModules[name]! + `\n//# sourceURL=${ext.id}/webpackModules/${name}.js`; 75 + const func = new Function("module", "exports", "require", source) as WebpackModuleFunc; 76 registerWebpackModule({ 77 ...wp, 78 ext: ext.id, ··· 86 } 87 88 if (exports.styles != null) { 89 + registerStyles(exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`)); 90 + } 91 + if (ext.scripts.style != null) { 92 + registerStyles([`/* ${ext.id}#style.css */ ${ext.scripts.style}`]); 93 } 94 } 95 } 96 97 async function loadExt(ext: DetectedExtension) { 98 webTarget: { 99 + try { 100 + await loadExtWeb(ext); 101 + } catch (e) { 102 + logger.error(`Failed to load extension "${ext.id}"`, e); 103 + } 104 } 105 106 nodePreload: { ··· 131 InvalidEnvironment 132 } 133 134 + export function checkExtensionCompat(manifest: ExtensionManifest): ExtensionCompat { 135 let environment; 136 webTarget: { 137 environment = ExtensionEnvironment.Web; ··· 140 environment = ExtensionEnvironment.Desktop; 141 } 142 143 + if (manifest.apiLevel !== constants.apiLevel) return ExtensionCompat.InvalidApiLevel; 144 + if ((manifest.environment ?? "both") !== "both" && manifest.environment !== environment) 145 return ExtensionCompat.InvalidEnvironment; 146 return ExtensionCompat.Compatible; 147 } ··· 160 extensions fires an event on completion, which allows us to await the loading 161 of another extension, resolving dependencies & load order effectively. 162 */ 163 + export async function loadExtensions(exts: DetectedExtension[]): Promise<ProcessedExtensions> { 164 + exts = exts.filter((ext) => checkExtensionCompat(ext.manifest) === ExtensionCompat.Compatible); 165 166 const config = await readConfig(); 167 const items = exts ··· 201 }; 202 } 203 204 + export async function loadProcessedExtensions({ extensions, dependencyGraph }: ProcessedExtensions) { 205 + const eventEmitter = createEventEmitter<WebEventType, WebEventPayloads>(); 206 const finished: Set<string> = new Set(); 207 208 logger.trace( ··· 224 } 225 226 function done() { 227 + eventEmitter.removeEventListener(WebEventType.ExtensionLoad, cb); 228 r(); 229 } 230 231 + eventEmitter.addEventListener(WebEventType.ExtensionLoad, cb); 232 if (finished.has(dep)) done(); 233 }) 234 ); 235 236 if (waitPromises.length > 0) { 237 + logger.debug(`Waiting on ${waitPromises.length} dependencies for "${ext.id}"`); 238 await Promise.all(waitPromises); 239 } 240 ··· 242 await loadExt(ext); 243 244 finished.add(ext.id); 245 + eventEmitter.dispatchEvent(WebEventType.ExtensionLoad, ext.id); 246 logger.debug(`Loaded "${ext.id}"`); 247 } 248
+55 -78
packages/core/src/extension.ts
··· 1 - import { 2 - ExtensionManifest, 3 - DetectedExtension, 4 - ExtensionLoadSource, 5 - constants 6 - } from "@moonlight-mod/types"; 7 import { readConfig } from "./config"; 8 import { getCoreExtensionsPath, getExtensionsPath } from "./util/data"; 9 import Logger from "./util/logger"; ··· 13 async function findManifests(dir: string): Promise<string[]> { 14 const ret = []; 15 16 - if (await moonlightFS.exists(dir)) { 17 - for (const file of await moonlightFS.readdir(dir)) { 18 - const path = moonlightFS.join(dir, file); 19 if (file === "manifest.json") { 20 ret.push(path); 21 } 22 23 - if (!(await moonlightFS.isFile(path))) { 24 ret.push(...(await findManifests(path))); 25 } 26 } ··· 31 32 async function loadDetectedExtensions( 33 dir: string, 34 - type: ExtensionLoadSource 35 ): Promise<DetectedExtension[]> { 36 const ret: DetectedExtension[] = []; 37 38 const manifests = await findManifests(dir); 39 for (const manifestPath of manifests) { 40 try { 41 - if (!(await moonlightFS.exists(manifestPath))) continue; 42 - const dir = moonlightFS.dirname(manifestPath); 43 44 - const manifest: ExtensionManifest = JSON.parse( 45 - await moonlightFS.readFileString(manifestPath) 46 - ); 47 48 - const webPath = moonlightFS.join(dir, "index.js"); 49 - const nodePath = moonlightFS.join(dir, "node.js"); 50 - const hostPath = moonlightFS.join(dir, "host.js"); 51 52 // if none exist (empty manifest) don't give a shit 53 if ( 54 - !moonlightFS.exists(webPath) && 55 - !moonlightFS.exists(nodePath) && 56 - !moonlightFS.exists(hostPath) 57 ) { 58 continue; 59 } 60 61 - const web = (await moonlightFS.exists(webPath)) 62 - ? await moonlightFS.readFileString(webPath) 63 : undefined; 64 65 let url: string | undefined = undefined; 66 - const urlPath = moonlightFS.join(dir, constants.repoUrlFile); 67 - if ( 68 - type === ExtensionLoadSource.Normal && 69 - (await moonlightFS.exists(urlPath)) 70 - ) { 71 - url = await moonlightFS.readFileString(urlPath); 72 } 73 74 const wpModules: Record<string, string> = {}; 75 - const wpModulesPath = moonlightFS.join(dir, "webpackModules"); 76 - if (await moonlightFS.exists(wpModulesPath)) { 77 - const wpModulesFile = await moonlightFS.readdir(wpModulesPath); 78 79 for (const wpModuleFile of wpModulesFile) { 80 if (wpModuleFile.endsWith(".js")) { 81 - wpModules[wpModuleFile.replace(".js", "")] = 82 - await moonlightFS.readFileString( 83 - moonlightFS.join(wpModulesPath, wpModuleFile) 84 - ); 85 } 86 } 87 } 88 89 ret.push({ 90 id: manifest.id, ··· 97 web, 98 webPath: web != null ? webPath : undefined, 99 webpackModules: wpModules, 100 - nodePath: (await moonlightFS.exists(nodePath)) ? nodePath : undefined, 101 - hostPath: (await moonlightFS.exists(hostPath)) ? hostPath : undefined 102 } 103 }); 104 - } catch (e) { 105 - logger.error(e, "Failed to load extension"); 106 } 107 } 108 ··· 112 async function getExtensionsNative(): Promise<DetectedExtension[]> { 113 const config = await readConfig(); 114 const res = []; 115 116 - res.push( 117 - ...(await loadDetectedExtensions( 118 - getCoreExtensionsPath(), 119 - ExtensionLoadSource.Core 120 - )) 121 - ); 122 - 123 - res.push( 124 - ...(await loadDetectedExtensions( 125 - await getExtensionsPath(), 126 - ExtensionLoadSource.Normal 127 - )) 128 - ); 129 130 for (const devSearchPath of config.devSearchPaths ?? []) { 131 - res.push( 132 - ...(await loadDetectedExtensions( 133 - devSearchPath, 134 - ExtensionLoadSource.Developer 135 - )) 136 - ); 137 } 138 139 return res; 140 } 141 142 async function getExtensionsBrowser(): Promise<DetectedExtension[]> { 143 const ret: DetectedExtension[] = []; 144 145 - const coreExtensionsFs: Record<string, string> = JSON.parse( 146 - // @ts-expect-error shut up 147 - _moonlight_coreExtensionsStr 148 - ); 149 - const coreExtensions = Array.from( 150 - new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0])) 151 - ); 152 153 for (const ext of coreExtensions) { 154 if (!coreExtensionsFs[`${ext}/index.js`]) continue; ··· 159 const wpModulesPath = `${ext}/webpackModules`; 160 for (const wpModuleFile of Object.keys(coreExtensionsFs)) { 161 if (wpModuleFile.startsWith(wpModulesPath)) { 162 - wpModules[ 163 - wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "") 164 - ] = coreExtensionsFs[wpModuleFile]; 165 } 166 } 167 ··· 173 }, 174 scripts: { 175 web, 176 - webpackModules: wpModules 177 } 178 }); 179 } 180 181 - if (await moonlightFS.exists("/extensions")) { 182 - ret.push( 183 - ...(await loadDetectedExtensions( 184 - "/extensions", 185 - ExtensionLoadSource.Normal 186 - )) 187 - ); 188 } 189 190 return ret;
··· 1 + import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, constants } from "@moonlight-mod/types"; 2 import { readConfig } from "./config"; 3 import { getCoreExtensionsPath, getExtensionsPath } from "./util/data"; 4 import Logger from "./util/logger"; ··· 8 async function findManifests(dir: string): Promise<string[]> { 9 const ret = []; 10 11 + if (await moonlightNodeSandboxed.fs.exists(dir)) { 12 + for (const file of await moonlightNodeSandboxed.fs.readdir(dir)) { 13 + const path = moonlightNodeSandboxed.fs.join(dir, file); 14 if (file === "manifest.json") { 15 ret.push(path); 16 } 17 18 + if (!(await moonlightNodeSandboxed.fs.isFile(path))) { 19 ret.push(...(await findManifests(path))); 20 } 21 } ··· 26 27 async function loadDetectedExtensions( 28 dir: string, 29 + type: ExtensionLoadSource, 30 + seen: Set<string> 31 ): Promise<DetectedExtension[]> { 32 const ret: DetectedExtension[] = []; 33 34 const manifests = await findManifests(dir); 35 for (const manifestPath of manifests) { 36 try { 37 + if (!(await moonlightNodeSandboxed.fs.exists(manifestPath))) continue; 38 + const dir = moonlightNodeSandboxed.fs.dirname(manifestPath); 39 40 + const manifest: ExtensionManifest = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(manifestPath)); 41 + if (seen.has(manifest.id)) { 42 + logger.warn(`Duplicate extension found, skipping: ${manifest.id}`); 43 + continue; 44 + } 45 + seen.add(manifest.id); 46 47 + const webPath = moonlightNodeSandboxed.fs.join(dir, "index.js"); 48 + const nodePath = moonlightNodeSandboxed.fs.join(dir, "node.js"); 49 + const hostPath = moonlightNodeSandboxed.fs.join(dir, "host.js"); 50 51 // if none exist (empty manifest) don't give a shit 52 if ( 53 + !moonlightNodeSandboxed.fs.exists(webPath) && 54 + !moonlightNodeSandboxed.fs.exists(nodePath) && 55 + !moonlightNodeSandboxed.fs.exists(hostPath) 56 ) { 57 continue; 58 } 59 60 + const web = (await moonlightNodeSandboxed.fs.exists(webPath)) 61 + ? await moonlightNodeSandboxed.fs.readFileString(webPath) 62 : undefined; 63 64 let url: string | undefined = undefined; 65 + const urlPath = moonlightNodeSandboxed.fs.join(dir, constants.repoUrlFile); 66 + if (type === ExtensionLoadSource.Normal && (await moonlightNodeSandboxed.fs.exists(urlPath))) { 67 + url = await moonlightNodeSandboxed.fs.readFileString(urlPath); 68 } 69 70 const wpModules: Record<string, string> = {}; 71 + const wpModulesPath = moonlightNodeSandboxed.fs.join(dir, "webpackModules"); 72 + if (await moonlightNodeSandboxed.fs.exists(wpModulesPath)) { 73 + const wpModulesFile = await moonlightNodeSandboxed.fs.readdir(wpModulesPath); 74 75 for (const wpModuleFile of wpModulesFile) { 76 if (wpModuleFile.endsWith(".js")) { 77 + wpModules[wpModuleFile.replace(".js", "")] = await moonlightNodeSandboxed.fs.readFileString( 78 + moonlightNodeSandboxed.fs.join(wpModulesPath, wpModuleFile) 79 + ); 80 } 81 } 82 } 83 + 84 + const stylePath = moonlightNodeSandboxed.fs.join(dir, "style.css"); 85 86 ret.push({ 87 id: manifest.id, ··· 94 web, 95 webPath: web != null ? webPath : undefined, 96 webpackModules: wpModules, 97 + nodePath: (await moonlightNodeSandboxed.fs.exists(nodePath)) ? nodePath : undefined, 98 + hostPath: (await moonlightNodeSandboxed.fs.exists(hostPath)) ? hostPath : undefined, 99 + style: (await moonlightNodeSandboxed.fs.exists(stylePath)) 100 + ? await moonlightNodeSandboxed.fs.readFileString(stylePath) 101 + : undefined 102 } 103 }); 104 + } catch (err) { 105 + logger.error(`Failed to load extension from "${manifestPath}":`, err); 106 } 107 } 108 ··· 112 async function getExtensionsNative(): Promise<DetectedExtension[]> { 113 const config = await readConfig(); 114 const res = []; 115 + const seen = new Set<string>(); 116 117 + res.push(...(await loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core, seen))); 118 119 for (const devSearchPath of config.devSearchPaths ?? []) { 120 + res.push(...(await loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer, seen))); 121 } 122 123 + res.push(...(await loadDetectedExtensions(await getExtensionsPath(), ExtensionLoadSource.Normal, seen))); 124 + 125 return res; 126 } 127 128 async function getExtensionsBrowser(): Promise<DetectedExtension[]> { 129 const ret: DetectedExtension[] = []; 130 + const seen = new Set<string>(); 131 132 + const coreExtensionsFs: Record<string, string> = JSON.parse(_moonlight_coreExtensionsStr); 133 + const coreExtensions = Array.from(new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0]))); 134 135 for (const ext of coreExtensions) { 136 if (!coreExtensionsFs[`${ext}/index.js`]) continue; ··· 141 const wpModulesPath = `${ext}/webpackModules`; 142 for (const wpModuleFile of Object.keys(coreExtensionsFs)) { 143 if (wpModuleFile.startsWith(wpModulesPath)) { 144 + wpModules[wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "")] = coreExtensionsFs[wpModuleFile]; 145 } 146 } 147 ··· 153 }, 154 scripts: { 155 web, 156 + webpackModules: wpModules, 157 + style: coreExtensionsFs[`${ext}/style.css`] 158 } 159 }); 160 + seen.add(manifest.id); 161 } 162 163 + if (await moonlightNodeSandboxed.fs.exists("/extensions")) { 164 + ret.push(...(await loadDetectedExtensions("/extensions", ExtensionLoadSource.Normal, seen))); 165 } 166 167 return ret;
+3
packages/core/src/fs.ts
··· 39 async isFile(path) { 40 return fs.statSync(path).isFile(); 41 }, 42 43 join(...parts) { 44 return path.join(...parts);
··· 39 async isFile(path) { 40 return fs.statSync(path).isFile(); 41 }, 42 + async isDir(path) { 43 + return fs.statSync(path).isDirectory(); 44 + }, 45 46 join(...parts) { 47 return path.join(...parts);
+138 -122
packages/core/src/patch.ts
··· 11 } from "@moonlight-mod/types"; 12 import Logger from "./util/logger"; 13 import calculateDependencies, { Dependency } from "./util/dependency"; 14 - import WebpackRequire from "@moonlight-mod/types/discord/require"; 15 - import { EventType } from "@moonlight-mod/types/core/event"; 16 17 const logger = new Logger("core/patch"); 18 ··· 21 let webpackModules: Set<IdentifiedWebpackModule> = new Set(); 22 let webpackRequire: WebpackRequireType | null = null; 23 24 - const moduleLoadSubscriptions: Map<string, ((moduleId: string) => void)[]> = 25 - new Map(); 26 27 export function registerPatch(patch: IdentifiedPatch) { 28 patches.push(patch); 29 moonlight.unpatched.add(patch); 30 } ··· 36 } 37 } 38 39 - export function onModuleLoad( 40 - module: string | string[], 41 - callback: (moduleId: string) => void 42 - ): void { 43 let moduleIds = module; 44 45 if (typeof module === "string") { ··· 67 const moduleCache: Record<string, string> = {}; 68 const patched: Record<string, Array<string>> = {}; 69 70 - function patchModules(entry: WebpackJsonpEntry[1]) { 71 - function patchModule(id: string, patchId: string, replaced: string) { 72 - // Store what extensions patched what modules for easier debugging 73 - patched[id] = patched[id] || []; 74 - patched[id].push(patchId); 75 76 - // Webpack module arguments are minified, so we replace them with consistent names 77 - // We have to wrap it so things don't break, though 78 - const patchedStr = patched[id].sort().join(", "); 79 80 - const wrapped = 81 - `(${replaced}).apply(this, arguments)\n` + 82 - `// Patched by moonlight: ${patchedStr}\n` + 83 - `//# sourceURL=Webpack-Module-${id}`; 84 85 - try { 86 - const func = new Function( 87 - "module", 88 - "exports", 89 - "require", 90 - wrapped 91 - ) as WebpackModuleFunc; 92 - entry[id] = func; 93 - entry[id].__moonlight = true; 94 - return true; 95 - } catch (e) { 96 - logger.warn("Error constructing function for patch", patchId, e); 97 - patched[id].pop(); 98 - return false; 99 - } 100 } 101 102 // Populate the module cache 103 for (const [id, func] of Object.entries(entry)) { 104 if (!Object.hasOwn(moduleCache, id) && func.__moonlight !== true) { ··· 109 110 for (const [id, func] of Object.entries(entry)) { 111 if (func.__moonlight === true) continue; 112 - let moduleString = moduleCache[id]; 113 114 for (let i = 0; i < patches.length; i++) { 115 const patch = patches[i]; 116 if (patch.prerequisite != null && !patch.prerequisite()) { 117 continue; 118 } 119 ··· 122 patch.find.lastIndex = 0; 123 } 124 125 - // indexOf is faster than includes by 0.25% lmao 126 - const match = 127 - typeof patch.find === "string" 128 - ? moduleString.indexOf(patch.find) !== -1 129 - : patch.find.test(moduleString); 130 131 // Global regexes apply to all modules 132 - const shouldRemove = 133 - typeof patch.find === "string" ? true : !patch.find.global; 134 135 if (match) { 136 - moonlight.unpatched.delete(patch); 137 138 - // We ensured all arrays get turned into normal PatchReplace objects on register 139 - const replace = patch.replace as PatchReplace; 140 - 141 - if ( 142 - replace.type === undefined || 143 - replace.type === PatchReplaceType.Normal 144 - ) { 145 - // Add support for \i to match rspack's minified names 146 - if (typeof replace.match !== "string") { 147 - replace.match = new RegExp( 148 - replace.match.source.replace(/\\i/g, "[A-Za-z_$][\\w$]*"), 149 - replace.match.flags 150 - ); 151 - } 152 - // tsc fails to detect the overloads for this, so I'll just do this 153 - // Verbose, but it works 154 - let replaced; 155 - if (typeof replace.replacement === "string") { 156 - replaced = moduleString.replace(replace.match, replace.replacement); 157 - } else { 158 - replaced = moduleString.replace(replace.match, replace.replacement); 159 - } 160 161 - if (replaced === moduleString) { 162 - logger.warn("Patch replacement failed", id, patch); 163 - continue; 164 - } 165 166 - if (patchModule(id, `${patch.ext}#${patch.id}`, replaced)) { 167 - moduleString = replaced; 168 } 169 - } else if (replace.type === PatchReplaceType.Module) { 170 - // Directly replace the module with a new one 171 - const newModule = replace.replacement(moduleString); 172 - entry[id] = newModule; 173 - entry[id].__moonlight = true; 174 - moduleString = 175 - newModule.toString().replace(/\n/g, "") + 176 - `//# sourceURL=Webpack-Module-${id}`; 177 } 178 179 - if (shouldRemove) { 180 - patches.splice(i--, 1); 181 } 182 } 183 } 184 185 - moduleCache[id] = moduleString; 186 187 try { 188 const parsed = moonlight.lunast.parseScript(id, moduleString); 189 if (parsed != null) { 190 for (const [parsedId, parsedScript] of Object.entries(parsed)) { 191 - if (patchModule(parsedId, "lunast", parsedScript)) { 192 moduleCache[parsedId] = parsedScript; 193 } 194 } ··· 198 } 199 200 if (moonlightNode.config.patchAll === true) { 201 - if ( 202 - (typeof id !== "string" || !id.includes("_")) && 203 - !entry[id].__moonlight 204 - ) { 205 - const wrapped = 206 - `(${moduleCache[id]}).apply(this, arguments)\n` + 207 - `//# sourceURL=Webpack-Module-${id}`; 208 - entry[id] = new Function( 209 - "module", 210 - "exports", 211 - "require", 212 - wrapped 213 - ) as WebpackModuleFunc; 214 entry[id].__moonlight = true; 215 } 216 } ··· 246 function handleModuleDependencies() { 247 const modules = Array.from(webpackModules.values()); 248 249 - const dependencies: Dependency<string, IdentifiedWebpackModule>[] = 250 - modules.map((wp) => { 251 - return { 252 - id: depToString(wp), 253 - data: wp 254 - }; 255 - }); 256 257 const [sorted, _] = calculateDependencies(dependencies, { 258 fetchDep: (id) => { ··· 263 const deps = item.data?.dependencies ?? []; 264 return ( 265 deps.filter( 266 - (dep) => 267 - !(dep instanceof RegExp || typeof dep === "string") && 268 - dep.ext != null 269 ) as ExplicitExtensionDependency[] 270 ).map(depToString); 271 } ··· 297 if (dep.test(modStr)) deps.delete(dep); 298 } else if ( 299 dep.ext != null 300 - ? injectedWpModules.find( 301 - (x) => x.ext === dep.ext && x.id === dep.id 302 - ) 303 : injectedWpModules.find((x) => x.id === dep.id) 304 ) { 305 deps.delete(dep); 306 } 307 } 308 309 if (deps.size !== 0) { 310 - wpModule.dependencies = Array.from(deps); 311 continue; 312 } 313 - 314 - wpModule.dependencies = Array.from(deps); 315 } 316 } 317 ··· 324 if (wpModule.run) { 325 modules[id] = wpModule.run; 326 wpModule.run.__moonlight = true; 327 } 328 - if (wpModule.entrypoint) entrypoints.push(id); 329 } 330 if (!webpackModules.size) break; 331 } 332 333 - for (const [name, func] of Object.entries( 334 - moonlight.moonmap.getWebpackModules("window.moonlight.moonmap") 335 - )) { 336 injectedWpModules.push({ id: name, run: func }); 337 modules[name] = func; 338 inject = true; ··· 349 window.webpackChunkdiscord_app.push([ 350 [--chunkId], 351 modules, 352 - (require: typeof WebpackRequire) => entrypoints.map(require) 353 ]); 354 } 355 } ··· 399 const realPush = jsonp.push; 400 if (jsonp.push.__moonlight !== true) { 401 jsonp.push = (items) => { 402 - moonlight.events.dispatchEvent(EventType.ChunkLoad, { 403 chunkId: items[0], 404 modules: items[1], 405 require: items[2] ··· 447 set(modules: any) { 448 const { stack } = new Error(); 449 if (stack!.includes("/assets/") && !Array.isArray(modules)) { 450 - moonlight.events.dispatchEvent(EventType.ChunkLoad, { 451 modules: modules 452 }); 453 patchModules(modules); 454 455 - if (!window.webpackChunkdiscord_app) 456 - window.webpackChunkdiscord_app = []; 457 injectModules(modules); 458 } 459
··· 11 } from "@moonlight-mod/types"; 12 import Logger from "./util/logger"; 13 import calculateDependencies, { Dependency } from "./util/dependency"; 14 + import { WebEventType } from "@moonlight-mod/types/core/event"; 15 + import { processFind, processReplace, testFind } from "./util/patch"; 16 17 const logger = new Logger("core/patch"); 18 ··· 21 let webpackModules: Set<IdentifiedWebpackModule> = new Set(); 22 let webpackRequire: WebpackRequireType | null = null; 23 24 + const moduleLoadSubscriptions: Map<string, ((moduleId: string) => void)[]> = new Map(); 25 26 export function registerPatch(patch: IdentifiedPatch) { 27 + patch.find = processFind(patch.find); 28 + processReplace(patch.replace); 29 + 30 patches.push(patch); 31 moonlight.unpatched.add(patch); 32 } ··· 38 } 39 } 40 41 + export function onModuleLoad(module: string | string[], callback: (moduleId: string) => void): void { 42 let moduleIds = module; 43 44 if (typeof module === "string") { ··· 66 const moduleCache: Record<string, string> = {}; 67 const patched: Record<string, Array<string>> = {}; 68 69 + function createSourceURL(id: string) { 70 + const remapped = Object.entries(moonlight.moonmap.modules).find((m) => m[1] === id)?.[0]; 71 + 72 + if (remapped) { 73 + return `// Webpack Module: ${id}\n//# sourceURL=${remapped}`; 74 + } 75 + 76 + return `//# sourceURL=Webpack-Module/${id.slice(0, 3)}/${id}`; 77 + } 78 + 79 + function patchModule(id: string, patchId: string, replaced: string, entry: WebpackJsonpEntry[1]) { 80 + // Store what extensions patched what modules for easier debugging 81 + patched[id] = patched[id] ?? []; 82 + patched[id].push(patchId); 83 84 + // Webpack module arguments are minified, so we replace them with consistent names 85 + // We have to wrap it so things don't break, though 86 + const patchedStr = patched[id].sort().join(", "); 87 88 + const wrapped = 89 + `(${replaced}).apply(this, arguments)\n` + `// Patched by moonlight: ${patchedStr}\n` + createSourceURL(id); 90 91 + try { 92 + const func = new Function("module", "exports", "require", wrapped) as WebpackModuleFunc; 93 + entry[id] = func; 94 + entry[id].__moonlight = true; 95 + return true; 96 + } catch (e) { 97 + logger.warn("Error constructing function for patch", patchId, e); 98 + patched[id].pop(); 99 + return false; 100 } 101 + } 102 103 + function patchModules(entry: WebpackJsonpEntry[1]) { 104 // Populate the module cache 105 for (const [id, func] of Object.entries(entry)) { 106 if (!Object.hasOwn(moduleCache, id) && func.__moonlight !== true) { ··· 111 112 for (const [id, func] of Object.entries(entry)) { 113 if (func.__moonlight === true) continue; 114 + 115 + // Clone the module string so finds don't get messed up by other extensions 116 + const origModuleString = moduleCache[id]; 117 + let moduleString = origModuleString; 118 + const patchedStr = []; 119 + const mappedName = Object.entries(moonlight.moonmap.modules).find((m) => m[1] === id)?.[0]; 120 + let modified = false; 121 + let swappedModule = false; 122 + 123 + const exts = new Set<string>(); 124 125 for (let i = 0; i < patches.length; i++) { 126 const patch = patches[i]; 127 if (patch.prerequisite != null && !patch.prerequisite()) { 128 + moonlight.unpatched.delete(patch); 129 continue; 130 } 131 ··· 134 patch.find.lastIndex = 0; 135 } 136 137 + const match = testFind(origModuleString, patch.find) || patch.find === mappedName; 138 139 // Global regexes apply to all modules 140 + const shouldRemove = typeof patch.find === "string" ? true : !patch.find.global; 141 142 + let replaced = moduleString; 143 + let hardFailed = false; 144 if (match) { 145 + // We ensured normal PatchReplace objects get turned into arrays on register 146 + const replaces = patch.replace as PatchReplace[]; 147 148 + let isPatched = true; 149 + for (let i = 0; i < replaces.length; i++) { 150 + const replace = replaces[i]; 151 + let patchId = `${patch.ext}#${patch.id}`; 152 + if (replaces.length > 1) patchId += `#${i}`; 153 + patchedStr.push(patchId); 154 155 + if (replace.type === undefined || replace.type === PatchReplaceType.Normal) { 156 + // tsc fails to detect the overloads for this, so I'll just do this 157 + // Verbose, but it works 158 + if (typeof replace.replacement === "string") { 159 + replaced = replaced.replace(replace.match, replace.replacement); 160 + } else { 161 + replaced = replaced.replace(replace.match, replace.replacement); 162 + } 163 164 + if (replaced === moduleString) { 165 + logger.warn("Patch replacement failed", id, patchId, patch); 166 + isPatched = false; 167 + if (patch.hardFail) { 168 + hardFailed = true; 169 + break; 170 + } else { 171 + continue; 172 + } 173 + } 174 + } else if (replace.type === PatchReplaceType.Module) { 175 + // Directly replace the module with a new one 176 + const newModule = replace.replacement(replaced); 177 + entry[id] = newModule; 178 + entry[id].__moonlight = true; 179 + replaced = newModule.toString().replace(/\n/g, ""); 180 + swappedModule = true; 181 } 182 } 183 184 + if (!hardFailed) { 185 + moduleString = replaced; 186 + modified = true; 187 + exts.add(patch.ext); 188 } 189 + 190 + if (isPatched) moonlight.unpatched.delete(patch); 191 + if (shouldRemove) patches.splice(i--, 1); 192 } 193 } 194 195 + if (modified) { 196 + let shouldCache = true; 197 + if (!swappedModule) shouldCache = patchModule(id, patchedStr.join(", "), moduleString, entry); 198 + if (shouldCache) moduleCache[id] = moduleString; 199 + moonlight.patched.set(id, exts); 200 + } 201 202 try { 203 const parsed = moonlight.lunast.parseScript(id, moduleString); 204 if (parsed != null) { 205 for (const [parsedId, parsedScript] of Object.entries(parsed)) { 206 + if (patchModule(parsedId, "lunast", parsedScript, entry)) { 207 moduleCache[parsedId] = parsedScript; 208 } 209 } ··· 213 } 214 215 if (moonlightNode.config.patchAll === true) { 216 + if ((typeof id !== "string" || !id.includes("_")) && !entry[id].__moonlight) { 217 + const wrapped = `(${moduleCache[id]}).apply(this, arguments)\n` + createSourceURL(id); 218 + entry[id] = new Function("module", "exports", "require", wrapped) as WebpackModuleFunc; 219 entry[id].__moonlight = true; 220 } 221 } ··· 251 function handleModuleDependencies() { 252 const modules = Array.from(webpackModules.values()); 253 254 + const dependencies: Dependency<string, IdentifiedWebpackModule>[] = modules.map((wp) => { 255 + return { 256 + id: depToString(wp), 257 + data: wp 258 + }; 259 + }); 260 261 const [sorted, _] = calculateDependencies(dependencies, { 262 fetchDep: (id) => { ··· 267 const deps = item.data?.dependencies ?? []; 268 return ( 269 deps.filter( 270 + (dep) => !(dep instanceof RegExp || typeof dep === "string") && dep.ext != null 271 ) as ExplicitExtensionDependency[] 272 ).map(depToString); 273 } ··· 299 if (dep.test(modStr)) deps.delete(dep); 300 } else if ( 301 dep.ext != null 302 + ? injectedWpModules.find((x) => x.ext === dep.ext && x.id === dep.id) 303 : injectedWpModules.find((x) => x.id === dep.id) 304 ) { 305 deps.delete(dep); 306 } 307 } 308 309 + wpModule.dependencies = Array.from(deps); 310 if (deps.size !== 0) { 311 continue; 312 } 313 } 314 } 315 ··· 322 if (wpModule.run) { 323 modules[id] = wpModule.run; 324 wpModule.run.__moonlight = true; 325 + // @ts-expect-error hacks 326 + wpModule.run.call = function (self, module, exports, require) { 327 + try { 328 + wpModule.run!.apply(self, [module, exports, require]); 329 + } catch (err) { 330 + logger.error(`Failed to run module "${id}":`, err); 331 + } 332 + }; 333 + if (wpModule.entrypoint) entrypoints.push(id); 334 } 335 } 336 if (!webpackModules.size) break; 337 } 338 339 + for (const [name, func] of Object.entries(moonlight.moonmap.getWebpackModules("window.moonlight.moonmap"))) { 340 + // @ts-expect-error probably should fix the type on this idk 341 + func.__moonlight = true; 342 injectedWpModules.push({ id: name, run: func }); 343 modules[name] = func; 344 inject = true; ··· 355 window.webpackChunkdiscord_app.push([ 356 [--chunkId], 357 modules, 358 + (require: WebpackRequireType) => 359 + entrypoints.map((id) => { 360 + try { 361 + if (require.m[id] == null) { 362 + logger.error(`Failing to load entrypoint module "${id}" because it's not found in Webpack.`); 363 + } else { 364 + require(id); 365 + } 366 + } catch (err) { 367 + logger.error(`Failed to load entrypoint module "${id}":`, err); 368 + } 369 + }) 370 ]); 371 } 372 } ··· 416 const realPush = jsonp.push; 417 if (jsonp.push.__moonlight !== true) { 418 jsonp.push = (items) => { 419 + moonlight.events.dispatchEvent(WebEventType.ChunkLoad, { 420 chunkId: items[0], 421 modules: items[1], 422 require: items[2] ··· 464 set(modules: any) { 465 const { stack } = new Error(); 466 if (stack!.includes("/assets/") && !Array.isArray(modules)) { 467 + moonlight.events.dispatchEvent(WebEventType.ChunkLoad, { 468 modules: modules 469 }); 470 patchModules(modules); 471 472 + if (!window.webpackChunkdiscord_app) window.webpackChunkdiscord_app = []; 473 injectModules(modules); 474 } 475
+2 -12
packages/core/src/persist.ts
··· 1 import { join, dirname } from "node:path"; 2 - import { 3 - mkdirSync, 4 - renameSync, 5 - existsSync, 6 - copyFileSync, 7 - readdirSync 8 - } from "node:fs"; 9 import Logger from "./util/logger"; 10 11 const logger = new Logger("core/persist"); ··· 31 if (event === "host-updated") { 32 const versions = this.queryCurrentVersionsSync(); 33 34 - const newRootDir = join( 35 - this.rootPath, 36 - "app-" + 37 - versions.current_host.map((v: number) => v.toString()).join(".") 38 - ); 39 logger.info(`Persisting moonlight - new root dir: ${newRootDir}`); 40 41 const newResources = join(newRootDir, "resources");
··· 1 import { join, dirname } from "node:path"; 2 + import { mkdirSync, renameSync, existsSync, copyFileSync, readdirSync } from "node:fs"; 3 import Logger from "./util/logger"; 4 5 const logger = new Logger("core/persist"); ··· 25 if (event === "host-updated") { 26 const versions = this.queryCurrentVersionsSync(); 27 28 + const newRootDir = join(this.rootPath, "app-" + versions.current_host.map((v: number) => v.toString()).join(".")); 29 logger.info(`Persisting moonlight - new root dir: ${newRootDir}`); 30 31 const newResources = join(newRootDir, "resources");
+1 -4
packages/core/src/util/binary.ts
··· 55 return data; 56 } 57 58 - private _read<T>( 59 - func: (position: number, littleEndian?: boolean) => T, 60 - length: number 61 - ): T { 62 const result = func.call(this.view, this.position, true); 63 this.position += length; 64 return result;
··· 55 return data; 56 } 57 58 + private _read<T>(func: (position: number, littleEndian?: boolean) => T, length: number): T { 59 const result = func.call(this.view, this.position, true); 60 this.position += length; 61 return result;
+39
packages/core/src/util/config.ts
···
··· 1 + import type { Config, DetectedExtension, ExtensionManifest } from "@moonlight-mod/types"; 2 + 3 + export function getManifest(extensions: DetectedExtension[], ext: string) { 4 + return extensions.find((x) => x.id === ext)?.manifest; 5 + } 6 + 7 + export function getConfig(ext: string, config: Config) { 8 + const val = config.extensions[ext]; 9 + if (val == null || typeof val === "boolean") return undefined; 10 + return val.config; 11 + } 12 + 13 + export function getConfigOption<T>( 14 + ext: string, 15 + key: string, 16 + config: Config, 17 + settings?: ExtensionManifest["settings"] 18 + ): T | undefined { 19 + const defaultValue: T | undefined = structuredClone(settings?.[key]?.default); 20 + const cfg = getConfig(ext, config); 21 + if (cfg == null || typeof cfg === "boolean") return defaultValue; 22 + return cfg?.[key] ?? defaultValue; 23 + } 24 + 25 + export function setConfigOption<T>(config: Config, ext: string, key: string, value: T) { 26 + const oldConfig = config.extensions[ext]; 27 + const newConfig = 28 + typeof oldConfig === "boolean" 29 + ? { 30 + enabled: oldConfig, 31 + config: { [key]: value } 32 + } 33 + : { 34 + ...oldConfig, 35 + config: { ...(oldConfig?.config ?? {}), [key]: value } 36 + }; 37 + 38 + config.extensions[ext] = newConfig; 39 + }
+10 -15
packages/core/src/util/data.ts
··· 16 appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData); 17 } 18 19 - const dir = moonlightFS.join(appData, "moonlight-mod"); 20 - if (!(await moonlightFS.exists(dir))) await moonlightFS.mkdir(dir); 21 22 return dir; 23 } ··· 36 37 let configPath = ""; 38 39 - const buildInfoPath = moonlightFS.join( 40 - process.resourcesPath, 41 - "build_info.json" 42 - ); 43 - if (!(await moonlightFS.exists(buildInfoPath))) { 44 - configPath = moonlightFS.join(dir, "desktop.json"); 45 } else { 46 - const buildInfo: BuildInfo = JSON.parse( 47 - await moonlightFS.readFileString(buildInfoPath) 48 - ); 49 - configPath = moonlightFS.join(dir, buildInfo.releaseChannel + ".json"); 50 } 51 52 return configPath; ··· 55 async function getPathFromMoonlight(...names: string[]) { 56 const dir = await getMoonlightDir(); 57 58 - const target = moonlightFS.join(dir, ...names); 59 - if (!(await moonlightFS.exists(target))) await moonlightFS.mkdir(target); 60 61 return target; 62 } ··· 66 } 67 68 export function getCoreExtensionsPath(): string { 69 - return moonlightFS.join(__dirname, constants.coreExtensionsDir); 70 }
··· 16 appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData); 17 } 18 19 + const dir = moonlightNodeSandboxed.fs.join(appData, "moonlight-mod"); 20 + if (!(await moonlightNodeSandboxed.fs.exists(dir))) await moonlightNodeSandboxed.fs.mkdir(dir); 21 22 return dir; 23 } ··· 36 37 let configPath = ""; 38 39 + const buildInfoPath = moonlightNodeSandboxed.fs.join(process.resourcesPath, "build_info.json"); 40 + if (!(await moonlightNodeSandboxed.fs.exists(buildInfoPath))) { 41 + configPath = moonlightNodeSandboxed.fs.join(dir, "desktop.json"); 42 } else { 43 + const buildInfo: BuildInfo = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(buildInfoPath)); 44 + configPath = moonlightNodeSandboxed.fs.join(dir, buildInfo.releaseChannel + ".json"); 45 } 46 47 return configPath; ··· 50 async function getPathFromMoonlight(...names: string[]) { 51 const dir = await getMoonlightDir(); 52 53 + const target = moonlightNodeSandboxed.fs.join(dir, ...names); 54 + if (!(await moonlightNodeSandboxed.fs.exists(target))) await moonlightNodeSandboxed.fs.mkdir(target); 55 56 return target; 57 } ··· 61 } 62 63 export function getCoreExtensionsPath(): string { 64 + return moonlightNodeSandboxed.fs.join(__dirname, constants.coreExtensionsDir); 65 }
+4 -14
packages/core/src/util/dependency.ts
··· 35 const fullDeps: Set<T> = new Set(); 36 let failed = false; 37 38 - // eslint-disable-next-line no-inner-declarations 39 function resolveDeps(id: T, root: boolean) { 40 if (id === item.id && !root) { 41 logger.warn(`Circular dependency detected: "${item.id}"`); ··· 113 logger.trace("Enabled stage", itemsOrig); 114 const implicitlyEnabled: T[] = []; 115 116 - // eslint-disable-next-line no-inner-declarations 117 function validateDeps(dep: Dependency<T, D>) { 118 if (getEnabled!(dep)) { 119 const deps = dependencyGraphOrig.get(dep.id)!; ··· 122 validateDeps({ id, data }); 123 } 124 } else { 125 - const dependsOnMe = Array.from(dependencyGraphOrig.entries()).filter( 126 - ([, v]) => v?.has(dep.id) 127 - ); 128 129 if (dependsOnMe.length > 0) { 130 logger.debug("Implicitly enabling dependency", dep.id); ··· 134 } 135 136 for (const dep of itemsOrig) validateDeps(dep); 137 - itemsOrig = itemsOrig.filter( 138 - (x) => getEnabled(x) || implicitlyEnabled.includes(x.id) 139 - ); 140 } 141 142 if (getIncompatible != null) { ··· 176 dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id))); 177 } 178 179 - while ( 180 - Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0 181 - ) { 182 - const noDependents = items.filter( 183 - (e) => dependencyGraph.get(e.id)?.size === 0 184 - ); 185 186 if (noDependents.length === 0) { 187 logger.warn("Stuck dependency graph detected", dependencyGraph);
··· 35 const fullDeps: Set<T> = new Set(); 36 let failed = false; 37 38 function resolveDeps(id: T, root: boolean) { 39 if (id === item.id && !root) { 40 logger.warn(`Circular dependency detected: "${item.id}"`); ··· 112 logger.trace("Enabled stage", itemsOrig); 113 const implicitlyEnabled: T[] = []; 114 115 function validateDeps(dep: Dependency<T, D>) { 116 if (getEnabled!(dep)) { 117 const deps = dependencyGraphOrig.get(dep.id)!; ··· 120 validateDeps({ id, data }); 121 } 122 } else { 123 + const dependsOnMe = Array.from(dependencyGraphOrig.entries()).filter(([, v]) => v?.has(dep.id)); 124 125 if (dependsOnMe.length > 0) { 126 logger.debug("Implicitly enabling dependency", dep.id); ··· 130 } 131 132 for (const dep of itemsOrig) validateDeps(dep); 133 + itemsOrig = itemsOrig.filter((x) => getEnabled(x) || implicitlyEnabled.includes(x.id)); 134 } 135 136 if (getIncompatible != null) { ··· 170 dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id))); 171 } 172 173 + while (Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0) { 174 + const noDependents = items.filter((e) => dependencyGraph.get(e.id)?.size === 0); 175 176 if (noDependents.length === 0) { 177 logger.warn("Stuck dependency graph detected", dependencyGraph);
+7 -27
packages/core/src/util/event.ts
··· 9 const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); 10 11 return { 12 - dispatchEvent: <Id extends keyof EventData>( 13 - id: Id, 14 - data: EventData[Id] 15 - ) => { 16 - eventEmitter.dispatchEvent( 17 - new CustomEvent(id as string, { detail: data }) 18 - ); 19 }, 20 21 - addEventListener: <Id extends keyof EventData>( 22 - id: Id, 23 - cb: (data: EventData[Id]) => void 24 - ) => { 25 const untyped = cb as (data: EventData) => void; 26 if (listeners.has(untyped)) return; 27 ··· 34 eventEmitter.addEventListener(id as string, listener); 35 }, 36 37 - removeEventListener: <Id extends keyof EventData>( 38 - id: Id, 39 - cb: (data: EventData[Id]) => void 40 - ) => { 41 const untyped = cb as (data: EventData) => void; 42 const listener = listeners.get(untyped); 43 if (listener == null) return; ··· 53 const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); 54 55 return { 56 - dispatchEvent: <Id extends keyof EventData>( 57 - id: Id, 58 - data: EventData[Id] 59 - ) => { 60 eventEmitter.emit(id as string, data); 61 }, 62 63 - addEventListener: <Id extends keyof EventData>( 64 - id: Id, 65 - cb: (data: EventData[Id]) => void 66 - ) => { 67 const untyped = cb as (data: EventData) => void; 68 if (listeners.has(untyped)) return; 69 ··· 76 eventEmitter.on(id as string, listener); 77 }, 78 79 - removeEventListener: <Id extends keyof EventData>( 80 - id: Id, 81 - cb: (data: EventData[Id]) => void 82 - ) => { 83 const untyped = cb as (data: EventData) => void; 84 const listener = listeners.get(untyped); 85 if (listener == null) return;
··· 9 const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); 10 11 return { 12 + dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => { 13 + eventEmitter.dispatchEvent(new CustomEvent(id as string, { detail: data })); 14 }, 15 16 + addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => { 17 const untyped = cb as (data: EventData) => void; 18 if (listeners.has(untyped)) return; 19 ··· 26 eventEmitter.addEventListener(id as string, listener); 27 }, 28 29 + removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => { 30 const untyped = cb as (data: EventData) => void; 31 const listener = listeners.get(untyped); 32 if (listener == null) return; ··· 42 const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); 43 44 return { 45 + dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => { 46 eventEmitter.emit(id as string, data); 47 }, 48 49 + addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => { 50 const untyped = cb as (data: EventData) => void; 51 if (listeners.has(untyped)) return; 52 ··· 59 eventEmitter.on(id as string, listener); 60 }, 61 62 + removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => { 63 const untyped = cb as (data: EventData) => void; 64 const listener = listeners.get(untyped); 65 if (listener == null) return;
+3 -5
packages/core/src/util/import.ts
··· 9 cemented if import is passed a string literal. 10 */ 11 12 - const canRequire = ["path", "fs"] as const; 13 - type CanRequire = (typeof canRequire)[number]; 14 15 type ImportTypes = { 16 path: typeof import("path"); 17 fs: typeof import("fs"); 18 }; 19 20 - export default function requireImport<T extends CanRequire>( 21 - type: T 22 - ): Awaited<ImportTypes[T]> { 23 return require(type); 24 }
··· 9 cemented if import is passed a string literal. 10 */ 11 12 + const _canRequire = ["path", "fs"] as const; 13 + type CanRequire = (typeof _canRequire)[number]; 14 15 type ImportTypes = { 16 path: typeof import("path"); 17 fs: typeof import("fs"); 18 }; 19 20 + export default function requireImport<T extends CanRequire>(type: T): Awaited<ImportTypes[T]> { 21 return require(type); 22 }
+2 -8
packages/core/src/util/logger.ts
··· 50 if (maxLevel > level) return; 51 52 if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) { 53 - args = [ 54 - `%c[${logLevel}]`, 55 - `background-color: ${colors[level]}; color: #FFFFFF;`, 56 - `[${this.name}]`, 57 - ...obj 58 - ]; 59 } else { 60 args = [`[${logLevel}]`, `[${this.name}]`, ...obj]; 61 } ··· 87 88 export function initLogger(config: Config) { 89 if (config.loggerLevel != null) { 90 - const enumValue = 91 - LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel]; 92 if (enumValue != null) { 93 maxLevel = enumValue; 94 }
··· 50 if (maxLevel > level) return; 51 52 if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) { 53 + args = [`%c[${logLevel}]`, `background-color: ${colors[level]}; color: #FFFFFF;`, `[${this.name}]`, ...obj]; 54 } else { 55 args = [`[${logLevel}]`, `[${this.name}]`, ...obj]; 56 } ··· 82 83 export function initLogger(config: Config) { 84 if (config.loggerLevel != null) { 85 + const enumValue = LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel]; 86 if (enumValue != null) { 87 maxLevel = enumValue; 88 }
+30
packages/core/src/util/patch.ts
···
··· 1 + import { PatchReplace, PatchReplaceType } from "@moonlight-mod/types"; 2 + 3 + type SingleFind = string | RegExp; 4 + type Find = SingleFind | SingleFind[]; 5 + 6 + export function processFind<T extends Find>(find: T): T { 7 + if (Array.isArray(find)) { 8 + return find.map(processFind) as T; 9 + } else if (find instanceof RegExp) { 10 + // Add support for \i to match rspack's minified names 11 + return new RegExp(find.source.replace(/\\i/g, "[A-Za-z_$][\\w$]*"), find.flags) as T; 12 + } else { 13 + return find; 14 + } 15 + } 16 + 17 + export function processReplace(replace: PatchReplace | PatchReplace[]) { 18 + if (Array.isArray(replace)) { 19 + replace.forEach(processReplace); 20 + } else { 21 + if (replace.type === undefined || replace.type === PatchReplaceType.Normal) { 22 + replace.match = processFind(replace.match); 23 + } 24 + } 25 + } 26 + 27 + export function testFind(src: string, find: SingleFind) { 28 + // indexOf is faster than includes by 0.25% lmao 29 + return typeof find === "string" ? src.indexOf(find) !== -1 : find.test(src); 30 + }
+4 -1
packages/core/tsconfig.json
··· 1 { 2 - "extends": "../../tsconfig.json" 3 }
··· 1 { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["ESNext", "DOM"] 5 + } 6 }
+9 -1
packages/core-extensions/package.json
··· 1 { 2 "name": "@moonlight-mod/core-extensions", 3 "private": true, 4 "dependencies": { 5 "@moonlight-mod/core": "workspace:*", 6 "@moonlight-mod/types": "workspace:*", 7 - "nanotar": "^0.1.1" 8 } 9 }
··· 1 { 2 "name": "@moonlight-mod/core-extensions", 3 "private": true, 4 + "engineStrict": true, 5 + "engines": { 6 + "node": ">=22", 7 + "pnpm": ">=10", 8 + "npm": "pnpm", 9 + "yarn": "pnpm" 10 + }, 11 "dependencies": { 12 "@moonlight-mod/core": "workspace:*", 13 "@moonlight-mod/types": "workspace:*", 14 + "microdiff": "catalog:prod", 15 + "nanotar": "catalog:prod" 16 } 17 }
+19
packages/core-extensions/src/appPanels/index.ts
···
··· 1 + import type { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; 2 + 3 + export const patches: Patch[] = [ 4 + { 5 + find: 'setProperty("--custom-app-panels-height"', 6 + replace: [ 7 + { 8 + match: /\(0,.\.jsx\)\((.\..),{section:/, 9 + replacement: (prev, el) => `...require("appPanels_appPanels").default.getPanels(${el}),${prev}` 10 + } 11 + ] 12 + } 13 + ]; 14 + 15 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 16 + appPanels: { 17 + dependencies: [{ id: "react" }] 18 + } 19 + };
+11
packages/core-extensions/src/appPanels/manifest.json
···
··· 1 + { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 + "id": "appPanels", 4 + "apiLevel": 2, 5 + "meta": { 6 + "name": "App Panels", 7 + "tagline": "An API for adding panels around the user/voice controls", 8 + "authors": ["NotNite"], 9 + "tags": ["library"] 10 + } 11 + }
+23
packages/core-extensions/src/appPanels/webpackModules/appPanels.ts
···
··· 1 + import type { AppPanels as AppPanelsType } from "@moonlight-mod/types/coreExtensions/appPanels"; 2 + import React from "@moonlight-mod/wp/react"; 3 + 4 + const panels: Record<string, React.FC<any>> = {}; 5 + 6 + export const AppPanels: AppPanelsType = { 7 + addPanel(section, element) { 8 + panels[section] = element; 9 + }, 10 + getPanels(panel) { 11 + return Object.entries(panels).map(([section, element]) => 12 + React.createElement( 13 + panel, 14 + { 15 + section 16 + }, 17 + React.createElement(element) 18 + ) 19 + ); 20 + } 21 + }; 22 + 23 + export default AppPanels;
+85
packages/core-extensions/src/commands/index.ts
···
··· 1 + import { Patch, ExtensionWebpackModule } from "@moonlight-mod/types"; 2 + import { APPLICATION_ID } from "@moonlight-mod/types/coreExtensions/commands"; 3 + 4 + export const patches: Patch[] = [ 5 + { 6 + find: ".fI5MTU)", // COMMAND_SECTION_BUILT_IN_NAME 7 + replace: [ 8 + // inject commands 9 + { 10 + match: /return (\i)\.filter/, 11 + replacement: (orig, commands) => 12 + `return [...${commands},...require("commands_commands").default._getCommands()].filter` 13 + }, 14 + 15 + // section 16 + { 17 + match: /(?<=\i={)(?=\[\i\.\i\.BUILT_IN]:{id:\i\.\i\.BUILT_IN,type:(\i.\i\.BUILT_IN))/, 18 + replacement: (_, type) => 19 + `"${APPLICATION_ID}":{id:"${APPLICATION_ID}",type:${type},get name(){return "moonlight"}},` 20 + } 21 + ] 22 + }, 23 + 24 + // index our section 25 + { 26 + find: '"ApplicationCommandIndexStore"', 27 + replace: { 28 + match: /(?<=let \i=(\i)\((\i\.\i)\[\i\.\i\.BUILT_IN\],(\i),!0,!0,(\i)\);)null!=(\i)&&(\i)\.push\(\i\)/, 29 + replacement: (_, createSection, sections, deny, props, section, commands) => 30 + `null!=${section}&&(${section}.data=${section}.data.filter(c=>c.applicationId=="-1")); 31 + null!=${section}&&${commands}.push(${section}); 32 + const moonlightCommands=${createSection}(${sections}["${APPLICATION_ID}"],${deny},!0,!0,${props}); 33 + null!=moonlightCommands&&(moonlightCommands.data=moonlightCommands.data.filter(c=>c.applicationId=="${APPLICATION_ID}")); 34 + null!=moonlightCommands&&${commands}.push(moonlightCommands)` 35 + } 36 + }, 37 + 38 + // grab legacy commands (needed for adding actions that act like sed/plus reacting) 39 + { 40 + find: "={tts:{action:", 41 + replace: { 42 + match: /Object\.setPrototypeOf\((\i),null\)/, 43 + replacement: (_, legacyCommands) => `require("commands_commands")._getLegacyCommands(${legacyCommands})` 44 + } 45 + }, 46 + 47 + // add icon 48 + { 49 + find: ",hasSpaceTerminator:", 50 + replace: { 51 + match: /(\i)\.type===/, 52 + replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}` 53 + } 54 + }, 55 + { 56 + find: ".icon,bot:null==", 57 + replace: { 58 + match: /(\.useMemo\(\(\)=>{(var \i;)?)((return |if\()(\i)\.type)/, 59 + replacement: (_, before, beforeVar, after, afterIf, section) => `${before} 60 + if (${section}.id==="${APPLICATION_ID}") return "https://moonlight-mod.github.io/favicon.png"; 61 + ${after}` 62 + } 63 + }, 64 + // fix icon sizing because they expect built in to be 24 and others to be 32 65 + { 66 + find: ".builtInSeparator}):null]", 67 + replace: { 68 + match: /(\i)\.type===\i\.\i\.BUILT_IN/, 69 + replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}` 70 + } 71 + }, 72 + 73 + // tell it this app id is authorized 74 + { 75 + find: /let{customInstallUrl:\i,installParams:\i,integrationTypesConfig:\i}/, 76 + replace: { 77 + match: /\|\|(\i)===\i\.\i\.BUILT_IN/, 78 + replacement: (orig, id) => `${orig}||${id}==="${APPLICATION_ID}"` 79 + } 80 + } 81 + ]; 82 + 83 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 84 + commands: {} 85 + };
+11
packages/core-extensions/src/commands/manifest.json
···
··· 1 + { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 + "id": "commands", 4 + "apiLevel": 2, 5 + "meta": { 6 + "name": "Commands", 7 + "tagline": "A library to add commands", 8 + "authors": ["Cynosphere", "NotNite"], 9 + "tags": ["library"] 10 + } 11 + }
+71
packages/core-extensions/src/commands/webpackModules/commands.ts
···
··· 1 + import { 2 + APPLICATION_ID, 3 + Commands, 4 + LegacyCommand, 5 + RegisteredCommand 6 + } from "@moonlight-mod/types/coreExtensions/commands"; 7 + 8 + type LegacyCommands = Record<string, LegacyCommand>; 9 + let legacyCommands: LegacyCommands | undefined; 10 + let queuedLegacyCommands: Record<string, LegacyCommand> | null = {}; 11 + 12 + const registeredCommands: RegisteredCommand[] = []; 13 + 14 + export function _getLegacyCommands(commands: LegacyCommands) { 15 + legacyCommands = commands; 16 + if (queuedLegacyCommands != null) { 17 + for (const [key, value] of Object.entries(queuedLegacyCommands)) { 18 + legacyCommands[key] = value; 19 + } 20 + queuedLegacyCommands = null; 21 + } 22 + } 23 + 24 + export const commands: Commands = { 25 + registerCommand(command) { 26 + const registered: RegisteredCommand = { 27 + ...command, 28 + untranslatedName: command.id, 29 + displayName: command.id, 30 + applicationId: APPLICATION_ID, 31 + untranslatedDescription: command.description, 32 + displayDescription: command.description, 33 + options: command.options?.map((o) => ({ 34 + ...o, 35 + displayName: o.name, 36 + displayDescription: o.description 37 + })) 38 + }; 39 + registeredCommands.push(registered); 40 + }, 41 + 42 + registerLegacyCommand(id, command) { 43 + if (command.match) { 44 + if (command.match instanceof RegExp) { 45 + command.match = this.anyScopeRegex(command.match); 46 + } else if (command.match.regex && typeof command.match !== "function") { 47 + command.match = this.anyScopeRegex(command.match.regex); 48 + } 49 + } 50 + 51 + if (!legacyCommands) { 52 + queuedLegacyCommands![id] = command; 53 + } else { 54 + legacyCommands[id] = command; 55 + } 56 + }, 57 + 58 + anyScopeRegex(regex) { 59 + const out = function (str: string) { 60 + return regex.exec(str); 61 + }; 62 + out.regex = regex; 63 + return out; 64 + }, 65 + 66 + _getCommands() { 67 + return [...registeredCommands]; 68 + } 69 + }; 70 + 71 + export default commands;
+7 -5
packages/core-extensions/src/common/index.ts
··· 2 3 export const webpackModules: ExtensionWebExports["webpackModules"] = { 4 stores: { 5 - dependencies: [ 6 - { 7 - id: "discord/packages/flux" 8 - } 9 - ] 10 } 11 };
··· 2 3 export const webpackModules: ExtensionWebExports["webpackModules"] = { 4 stores: { 5 + dependencies: [{ id: "discord/packages/flux" }] 6 + }, 7 + ErrorBoundary: { 8 + dependencies: [{ id: "react" }] 9 + }, 10 + icons: { 11 + dependencies: [{ id: "react" }, { id: "discord/components/common/index" }] 12 } 13 };
+2 -1
packages/core-extensions/src/common/manifest.json
··· 1 { 2 "id": "common", 3 "apiLevel": 2, 4 "meta": { 5 "name": "Common", 6 - "tagline": "A *lot* of common clientmodding utilities from the Discord client", 7 "authors": ["Cynosphere", "NotNite"], 8 "tags": ["library"] 9 },
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "common", 4 "apiLevel": 2, 5 "meta": { 6 "name": "Common", 7 + "tagline": "Common client modding utilities for the Discord client", 8 "authors": ["Cynosphere", "NotNite"], 9 "tags": ["library"] 10 },
+27
packages/core-extensions/src/common/style.css
···
··· 1 + .moonlight-error-boundary { 2 + margin: 0 0 15px; 3 + padding: 10px; 4 + border-radius: 5px; 5 + font-size: 1rem; 6 + font-weight: 300; 7 + line-height: 22px; 8 + color: var(--text-normal, white); 9 + background: hsl(var(--red-400-hsl) / 0.1); 10 + border: 2px solid hsl(var(--red-400-hsl) / 0.5); 11 + 12 + .theme-light & { 13 + color: var(--text-normal, black) !important; 14 + } 15 + 16 + & > h3 { 17 + margin-bottom: 0.25rem; 18 + } 19 + 20 + & > .hljs { 21 + background: var(--background-secondary); 22 + border: 1px solid var(--background-tertiary); 23 + white-space: pre-wrap; 24 + font-family: var(--font-code); 25 + user-select: text; 26 + } 27 + }
+47
packages/core-extensions/src/common/webpackModules/ErrorBoundary.tsx
···
··· 1 + import React from "@moonlight-mod/wp/react"; 2 + import { ErrorBoundaryProps, ErrorBoundaryState } from "@moonlight-mod/types/coreExtensions/common"; 3 + 4 + const logger = moonlight.getLogger("ErrorBoundary"); 5 + 6 + class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> { 7 + constructor(props: ErrorBoundaryProps) { 8 + super(props); 9 + this.state = { 10 + errored: false, 11 + error: undefined, 12 + componentStack: undefined 13 + }; 14 + } 15 + 16 + static getDerivedStateFromError(error: Error) { 17 + return { 18 + errored: true, 19 + error 20 + }; 21 + } 22 + 23 + componentDidCatch(error: Error, { componentStack }: { componentStack: string }) { 24 + logger.error(`${error}\n\nComponent stack:\n${componentStack}`); 25 + this.setState({ error, componentStack }); 26 + } 27 + 28 + render() { 29 + const { noop, fallback: FallbackComponent, children, message } = this.props; 30 + const { errored, error, componentStack } = this.state; 31 + 32 + if (FallbackComponent) return <FallbackComponent children={children} {...this.state} />; 33 + 34 + if (errored) { 35 + return noop ? null : ( 36 + <div className={`moonlight-error-boundary`}> 37 + <h3>{message ?? "An error occurred rendering this component:"}</h3> 38 + <code className="hljs">{`${error}\n\nComponent stack:\n${componentStack}`}</code> 39 + </div> 40 + ); 41 + } 42 + 43 + return children; 44 + } 45 + } 46 + 47 + export default ErrorBoundary;
+31
packages/core-extensions/src/common/webpackModules/icons.ts
···
··· 1 + import { Icons, IconSize } from "@moonlight-mod/types/coreExtensions/common"; 2 + import { tokens } from "@moonlight-mod/wp/discord/components/common/index"; 3 + 4 + // This is defined in a Webpack module but we copy it here to be less breakage-prone 5 + const sizes: Partial<Record<IconSize, number>> = { 6 + xxs: 12, 7 + xs: 16, 8 + sm: 18, 9 + md: 24, 10 + lg: 32, 11 + refresh_sm: 20 12 + }; 13 + 14 + export const icons: Icons = { 15 + parseProps(props) { 16 + // NOTE: var() fallback is non-standard behavior, just for safety reasons 17 + const color = props?.color ?? tokens?.colors?.["INTERACTIVE_NORMAL"] ?? "var(--interactive-normal)"; 18 + 19 + const size = sizes[props?.size ?? "md"]; 20 + 21 + return { 22 + // note: this default size is also non-standard behavior, just for safety 23 + width: size ?? props?.width ?? sizes.md!, 24 + height: size ?? props?.width ?? sizes.md!, 25 + 26 + fill: typeof color === "string" ? color : color.css, 27 + className: props?.colorClass ?? "" 28 + }; 29 + } 30 + }; 31 + export default icons;
+83
packages/core-extensions/src/componentEditor/index.ts
···
··· 1 + import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; 2 + 3 + export const patches: Patch[] = [ 4 + // dm list 5 + { 6 + find: ".interactiveSystemDM]:", 7 + replace: [ 8 + { 9 + match: /decorators:(\i\.isSystemDM\(\)\?\(0,\i\.jsx\)\(.+?verified:!0}\):null)/, 10 + replacement: (_, decorators) => 11 + `decorators:require("componentEditor_dmList").default._patchDecorators([${decorators}],arguments[0])` 12 + }, 13 + { 14 + match: /(?<=selected:\i,)children:\[/, 15 + replacement: 'children:require("componentEditor_dmList").default._patchItems([' 16 + }, 17 + { 18 + match: /(?<=(onMouseDown|nameplate):\i}\))]/, 19 + replacement: "],arguments[0])" 20 + } 21 + ], 22 + hardFail: true 23 + }, 24 + 25 + // member list 26 + { 27 + find: ".lostPermission", 28 + replace: [ 29 + { 30 + match: /(?<=\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[\i\(\),.+?\i\(\)])/, 31 + replacement: (_, decorators) => 32 + `children:require("componentEditor_memberList").default._patchDecorators(${decorators},arguments[0])` 33 + }, 34 + { 35 + match: /name:null==\i\?\(0,\i\.jsx\)\("span"/, 36 + replacement: (orig: string) => 37 + `children:require("componentEditor_memberList").default._patchItems([],arguments[0]),${orig}` 38 + } 39 + ] 40 + }, 41 + 42 + // messages 43 + { 44 + find: '},"new-member")),', 45 + replace: [ 46 + { 47 + match: /(?<=\.BADGES](=|:))(\i)(;|})/, 48 + replacement: (_, leading, badges, trailing) => 49 + `require("componentEditor_messages").default._patchUsernameBadges(${badges},arguments[0])${trailing}` 50 + }, 51 + { 52 + match: /(?<=className:\i,)badges:(\i)/, 53 + replacement: (_, badges) => 54 + `badges:require("componentEditor_messages").default._patchBadges(${badges},arguments[0])` 55 + }, 56 + { 57 + match: /(?<=username:\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[.+?])}\),usernameSpanId:/, 58 + replacement: (_, elements) => 59 + `children:require("componentEditor_messages").default._patchUsername(${elements},arguments[0])}),usernameSpanId:` 60 + } 61 + ] 62 + }, 63 + { 64 + find: '.provider&&"Discord"===', 65 + replace: { 66 + match: /(?<=\.container\),)children:(\[.+?this\.renderSuppressConfirmModal\(\),.+?\])}\)/, 67 + replacement: (_, elements) => 68 + `children:require("componentEditor_messages").default._patchAccessories(${elements},this.props)})` 69 + } 70 + } 71 + ]; 72 + 73 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 74 + dmList: { 75 + dependencies: [{ id: "react" }] 76 + }, 77 + memberList: { 78 + dependencies: [{ id: "react" }] 79 + }, 80 + messages: { 81 + dependencies: [{ id: "react" }] 82 + } 83 + };
+11
packages/core-extensions/src/componentEditor/manifest.json
···
··· 1 + { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 + "id": "componentEditor", 4 + "apiLevel": 2, 5 + "meta": { 6 + "name": "Component Editor", 7 + "tagline": "A library to add to commonly patched components", 8 + "authors": ["Cynosphere"], 9 + "tags": ["library"] 10 + } 11 + }
+61
packages/core-extensions/src/componentEditor/webpackModules/dmList.tsx
···
··· 1 + import { 2 + DMList, 3 + DMListItem, 4 + DMListDecorator, 5 + DMListAnchorIndicies, 6 + DMListDecoratorAnchorIndicies 7 + } from "@moonlight-mod/types/coreExtensions/componentEditor"; 8 + import React from "@moonlight-mod/wp/react"; 9 + 10 + const items: Record<string, DMListItem> = {}; 11 + const decorators: Record<string, DMListDecorator> = {}; 12 + 13 + function addEntries( 14 + elements: React.ReactNode[], 15 + entries: Record<string, DMListItem | DMListDecorator>, 16 + indicies: Partial<Record<keyof typeof DMListAnchorIndicies | keyof typeof DMListDecoratorAnchorIndicies, number>>, 17 + props: any 18 + ) { 19 + const originalElements = [...elements]; 20 + for (const [id, entry] of Object.entries(entries)) { 21 + const component = <entry.component {...props} key={id} />; 22 + 23 + if (entry.anchor === undefined) { 24 + if (entry.before) { 25 + elements.splice(0, 0, component); 26 + } else { 27 + elements.push(component); 28 + } 29 + } else { 30 + const index = elements.indexOf(originalElements[indicies[entry.anchor]!]); 31 + elements.splice(index! + (entry.before ? 0 : 1), 0, component); 32 + } 33 + } 34 + } 35 + 36 + export const dmList: DMList = { 37 + addItem(id, component, anchor, before = false) { 38 + items[id] = { 39 + component, 40 + anchor, 41 + before 42 + }; 43 + }, 44 + addDecorator(id, component, anchor, before = false) { 45 + decorators[id] = { 46 + component, 47 + anchor, 48 + before 49 + }; 50 + }, 51 + _patchItems(elements, props) { 52 + addEntries(elements, items, DMListAnchorIndicies, props); 53 + return elements; 54 + }, 55 + _patchDecorators(elements, props) { 56 + addEntries(elements, decorators, DMListDecoratorAnchorIndicies, props); 57 + return elements; 58 + } 59 + }; 60 + 61 + export default dmList;
+50
packages/core-extensions/src/componentEditor/webpackModules/memberList.tsx
···
··· 1 + import { 2 + MemberList, 3 + MemberListDecorator, 4 + MemberListDecoratorAnchorIndicies 5 + } from "@moonlight-mod/types/coreExtensions/componentEditor"; 6 + import React from "@moonlight-mod/wp/react"; 7 + 8 + const items: Record<string, React.FC<any>> = {}; 9 + const decorators: Record<string, MemberListDecorator> = {}; 10 + 11 + export const memberList: MemberList = { 12 + addItem(id, component) { 13 + items[id] = component; 14 + }, 15 + addDecorator(id, component, anchor, before = false) { 16 + decorators[id] = { 17 + component, 18 + anchor, 19 + before 20 + }; 21 + }, 22 + _patchItems(elements, props) { 23 + for (const [id, Component] of Object.entries(items)) { 24 + elements.push(<Component {...props} key={id} />); 25 + } 26 + 27 + return elements; 28 + }, 29 + _patchDecorators(elements, props) { 30 + const originalElements = [...elements]; 31 + for (const [id, entry] of Object.entries(decorators)) { 32 + const component = <entry.component {...props} key={id} />; 33 + 34 + if (entry.anchor === undefined) { 35 + if (entry.before) { 36 + elements.splice(0, 0, component); 37 + } else { 38 + elements.push(component); 39 + } 40 + } else { 41 + const index = elements.indexOf(originalElements[MemberListDecoratorAnchorIndicies[entry.anchor]!]); 42 + elements.splice(index! + (entry.before ? 0 : 1), 0, component); 43 + } 44 + } 45 + 46 + return elements; 47 + } 48 + }; 49 + 50 + export default memberList;
+97
packages/core-extensions/src/componentEditor/webpackModules/messages.tsx
···
··· 1 + import { 2 + MessageBadge, 3 + MessageBadgeIndicies, 4 + Messages, 5 + MessageUsername, 6 + MessageUsernameBadge, 7 + MessageUsernameBadgeIndicies, 8 + MessageUsernameIndicies 9 + } from "@moonlight-mod/types/coreExtensions/componentEditor"; 10 + import React from "@moonlight-mod/wp/react"; 11 + 12 + const username: Record<string, MessageUsername> = {}; 13 + const usernameBadges: Record<string, MessageUsernameBadge> = {}; 14 + const badges: Record<string, MessageBadge> = {}; 15 + const accessories: Record<string, React.FC<any>> = {}; 16 + 17 + function addEntries( 18 + elements: React.ReactNode[], 19 + entries: Record<string, MessageUsername | MessageUsernameBadge | MessageBadge>, 20 + indicies: Partial< 21 + Record< 22 + | keyof typeof MessageUsernameIndicies 23 + | keyof typeof MessageUsernameBadgeIndicies 24 + | keyof typeof MessageBadgeIndicies, 25 + number 26 + > 27 + >, 28 + props: any 29 + ) { 30 + const originalElements = [...elements]; 31 + for (const [id, entry] of Object.entries(entries)) { 32 + const component = <entry.component {...props} key={id} />; 33 + 34 + if (entry.anchor === undefined) { 35 + if (entry.before) { 36 + elements.splice(0, 0, component); 37 + } else { 38 + elements.push(component); 39 + } 40 + } else { 41 + const index = elements.indexOf(originalElements[indicies[entry.anchor]!]); 42 + elements.splice(index! + (entry.before ? 0 : 1), 0, component); 43 + } 44 + } 45 + } 46 + 47 + function addComponents(elements: React.ReactNode[], components: Record<string, React.FC<any>>, props: any) { 48 + for (const [id, Component] of Object.entries(components)) { 49 + const component = <Component {...props} key={id} />; 50 + elements.push(component); 51 + } 52 + } 53 + 54 + export const messages: Messages = { 55 + addToUsername(id, component, anchor, before = false) { 56 + username[id] = { 57 + component, 58 + anchor, 59 + before 60 + }; 61 + }, 62 + addUsernameBadge(id, component, anchor, before = false) { 63 + usernameBadges[id] = { 64 + component, 65 + anchor, 66 + before 67 + }; 68 + }, 69 + addBadge(id, component, anchor, before = false) { 70 + badges[id] = { 71 + component, 72 + anchor, 73 + before 74 + }; 75 + }, 76 + addAccessory(id, component) { 77 + accessories[id] = component; 78 + }, 79 + _patchUsername(elements, props) { 80 + addEntries(elements, username, MessageUsernameIndicies, props); 81 + return elements; 82 + }, 83 + _patchUsernameBadges(elements, props) { 84 + addEntries(elements, usernameBadges, MessageUsernameBadgeIndicies, props); 85 + return elements; 86 + }, 87 + _patchBadges(elements, props) { 88 + addEntries(elements, badges, MessageBadgeIndicies, props); 89 + return elements; 90 + }, 91 + _patchAccessories(elements, props) { 92 + addComponents(elements, accessories, props); 93 + return elements; 94 + } 95 + }; 96 + 97 + export default messages;
+5 -14
packages/core-extensions/src/contextMenu/index.tsx
··· 5 find: "Menu API only allows Items and groups of Items as children.", 6 replace: [ 7 { 8 - match: 9 - /(?<=let{navId[^}]+?}=(.),(.)=function .\(.\){.+(?=,.=function))/, 10 - replacement: (_, props, items) => 11 - `,__contextMenu=!${props}.__contextMenu_evilMenu&&require("contextMenu_contextMenu")._patchMenu(${props}, ${items})` 12 } 13 ] 14 }, ··· 17 replace: [ 18 { 19 match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/, 20 - replacement: (render) => 21 - `require("contextMenu_contextMenu")._saveProps(this,${render})` 22 } 23 ] 24 } ··· 26 27 export const webpackModules: Record<string, ExtensionWebpackModule> = { 28 contextMenu: { 29 - dependencies: [ 30 - { ext: "spacepack", id: "spacepack" }, 31 - "Menu API only allows Items and groups of Items as children." 32 - ] 33 }, 34 evilMenu: { 35 - dependencies: [ 36 - { ext: "spacepack", id: "spacepack" }, 37 - "Menu API only allows Items and groups of Items as children." 38 - ] 39 } 40 };
··· 5 find: "Menu API only allows Items and groups of Items as children.", 6 replace: [ 7 { 8 + match: /(?<=let{navId[^}]+?}=(.),.=).+?(?=,)/, 9 + replacement: (items, props) => `require("contextMenu_contextMenu")._patchMenu(${props},${items})` 10 } 11 ] 12 }, ··· 15 replace: [ 16 { 17 match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/, 18 + replacement: (render) => `require("contextMenu_contextMenu")._saveProps(this,${render})` 19 } 20 ] 21 } ··· 23 24 export const webpackModules: Record<string, ExtensionWebpackModule> = { 25 contextMenu: { 26 + dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."] 27 }, 28 evilMenu: { 29 + dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."] 30 } 31 };
+1
packages/core-extensions/src/contextMenu/manifest.json
··· 1 { 2 "id": "contextMenu", 3 "apiLevel": 2, 4 "meta": {
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "contextMenu", 4 "apiLevel": 2, 5 "meta": {
+32 -31
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
··· 1 - import { 2 - InternalItem, 3 - MenuElement, 4 - MenuProps 5 - } from "@moonlight-mod/types/coreExtensions/contextMenu"; 6 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 7 import parser from "@moonlight-mod/wp/contextMenu_evilMenu"; 8 9 type Patch = { 10 navId: string; 11 - item: ( 12 - props: any 13 - ) => 14 - | React.ReactComponentElement<MenuElement> 15 - | React.ReactComponentElement<MenuElement>[]; 16 - anchorId: string; 17 before: boolean; 18 }; 19 20 - export function addItem<T>( 21 - navId: string, 22 - item: ( 23 - props: T 24 - ) => 25 - | React.ReactComponentElement<MenuElement> 26 - | React.ReactComponentElement<MenuElement>[], 27 - anchorId: string, 28 - before = false 29 - ) { 30 - patches.push({ navId, item, anchorId, before }); 31 } 32 33 - export const patches: Patch[] = []; 34 - export function _patchMenu(props: MenuProps, items: InternalItem[]) { 35 const matches = patches.filter((p) => p.navId === props.navId); 36 - if (!matches.length) return; 37 38 for (const patch of matches) { 39 - const idx = items.findIndex((i) => i.key === patch.anchorId); 40 if (idx === -1) continue; 41 - items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps))); 42 } 43 } 44 45 let menuProps: any; 46 - export function _saveProps(self: any, el: any) { 47 menuProps = el.props; 48 49 const original = self.props.closeContextMenu; ··· 55 return el; 56 } 57 58 // Unmangle Menu elements 59 const code = 60 spacepack.require.m[ 61 - spacepack.findByCode( 62 - "Menu API only allows Items and groups of Items as children." 63 - )[0].id 64 ].toString(); 65 66 let MangledMenu;
··· 1 + import { InternalItem, Menu, MenuElement } from "@moonlight-mod/types/coreExtensions/contextMenu"; 2 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 3 import parser from "@moonlight-mod/wp/contextMenu_evilMenu"; 4 5 + // NOTE: We originally had item as a function that returned this, but it didn't 6 + // quite know how to work out the type and thought it was a JSX element (it 7 + // *technically* was). This has less type safety, but a @ts-expect-error has 8 + // zero, so it's better than nothing. 9 + type ReturnType = MenuElement | MenuElement[]; 10 + 11 type Patch = { 12 navId: string; 13 + item: React.FC<any>; 14 + anchor: string | RegExp; 15 before: boolean; 16 }; 17 18 + function addItem<T = any>(navId: string, item: React.FC<T>, anchor: string | RegExp, before = false) { 19 + if (anchor instanceof RegExp && anchor.flags.includes("g")) 20 + throw new Error("anchor regular expression should not be global"); 21 + patches.push({ navId, item, anchor, before }); 22 } 23 24 + const patches: Patch[] = []; 25 + function _patchMenu(props: React.ComponentProps<Menu>, items: InternalItem[]) { 26 const matches = patches.filter((p) => p.navId === props.navId); 27 + if (!matches.length) return items; 28 29 for (const patch of matches) { 30 + const idx = items.findIndex((i) => 31 + typeof patch.anchor === "string" ? i.key === patch.anchor : patch.anchor.test(i.key!) 32 + ); 33 if (idx === -1) continue; 34 + items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps) as ReturnType)); 35 } 36 + 37 + return items; 38 } 39 40 let menuProps: any; 41 + function _saveProps(self: any, el: any) { 42 menuProps = el.props; 43 44 const original = self.props.closeContextMenu; ··· 50 return el; 51 } 52 53 + module.exports = { 54 + patches, 55 + addItem, 56 + _patchMenu, 57 + _saveProps 58 + }; 59 + 60 // Unmangle Menu elements 61 + // spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString(); 62 const code = 63 spacepack.require.m[ 64 + spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id 65 ].toString(); 66 67 let MangledMenu;
+11 -21
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
··· 1 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 3 let code = 4 spacepack.require.m[ 5 - spacepack.findByCode( 6 - "Menu API only allows Items and groups of Items as children." 7 - )[0].id 8 ].toString(); 9 - code = code.replace(/,.=(?=function .\(.\){.+?,.=function)/, ";return "); 10 - code = code.replace(/,(?=__contextMenu)/, ";let "); 11 - const mod = new Function( 12 - "module", 13 - "exports", 14 - "require", 15 - `(${code}).apply(this, arguments)` 16 - ); 17 const exp: any = {}; 18 mod({}, exp, require); 19 - const Menu = spacepack.findFunctionByStrings( 20 - exp, 21 - "Menu API only allows Items and groups of Items as children." 22 - )!; 23 - module.exports = (el: any) => { 24 - return Menu({ 25 - children: el, 26 - __contextMenu_evilMenu: true 27 - }); 28 - };
··· 1 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 3 + // spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString(); 4 let code = 5 spacepack.require.m[ 6 + spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id 7 ].toString(); 8 + 9 + const parserSym = code.match(/(?<=_patchMenu\(.,).+?(?=\()/)![0]; 10 + 11 + code = code.replace(/{(.):\(\)=>./, (orig, e) => `{${e}:()=>${parserSym}`); 12 + const mod = new Function("module", "exports", "require", `(${code}).apply(this, arguments)`); 13 + 14 const exp: any = {}; 15 mod({}, exp, require); 16 + 17 + const parser = spacepack.findFunctionByStrings(exp, "Menu API only allows Items and groups of Items as children.")!; 18 + module.exports = parser;
+19
packages/core-extensions/src/devToolsExtensions/host.ts
···
··· 1 + import { app, session } from "electron"; 2 + import { resolve } from "node:path"; 3 + import Logger from "@moonlight-mod/core/util/logger"; 4 + 5 + const logger = new Logger("DevTools Extensions"); 6 + 7 + app.whenReady().then(async () => { 8 + const paths = moonlightHost.getConfigOption<string[]>("devToolsExtensions", "paths") ?? []; 9 + 10 + for (const path of paths) { 11 + const resolved = resolve(path); 12 + 13 + try { 14 + await session.defaultSession.loadExtension(resolved); 15 + } catch (err) { 16 + logger.error(`Failed to load an extension in "${resolved}":`, err); 17 + } 18 + } 19 + });
+22
packages/core-extensions/src/devToolsExtensions/manifest.json
···
··· 1 + { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 + "id": "devToolsExtensions", 4 + "meta": { 5 + "name": "DevTools Extensions", 6 + "tagline": "Loads Chrome extensions into Electron DevTools", 7 + "authors": [ 8 + "Cynosphere" 9 + ], 10 + "tags": [ 11 + "development" 12 + ] 13 + }, 14 + "settings": { 15 + "paths": { 16 + "advice": "restart", 17 + "displayName": "Extension Paths", 18 + "type": "list" 19 + } 20 + }, 21 + "apiLevel": 2 22 + }
+4 -8
packages/core-extensions/src/disableSentry/host.ts
··· 5 6 if (moonlightHost.asarPath !== "moonlightDesktop") { 7 try { 8 - const hostSentryPath = require.resolve( 9 - join(moonlightHost.asarPath, "node_modules", "@sentry", "electron") 10 - ); 11 - require.cache[hostSentryPath] = new Module( 12 - hostSentryPath, 13 - require.cache[require.resolve(moonlightHost.asarPath)] 14 - ); 15 require.cache[hostSentryPath]!.exports = { 16 init: () => {}, 17 captureException: () => {}, 18 setTag: () => {}, 19 - setUser: () => {} 20 }; 21 logger.debug("Stubbed Sentry host side!"); 22 } catch (err) {
··· 5 6 if (moonlightHost.asarPath !== "moonlightDesktop") { 7 try { 8 + const hostSentryPath = require.resolve(join(moonlightHost.asarPath, "node_modules", "@sentry", "electron")); 9 + require.cache[hostSentryPath] = new Module(hostSentryPath, require.cache[require.resolve(moonlightHost.asarPath)]); 10 require.cache[hostSentryPath]!.exports = { 11 init: () => {}, 12 captureException: () => {}, 13 setTag: () => {}, 14 + setUser: () => {}, 15 + captureMessage: () => {} 16 }; 17 logger.debug("Stubbed Sentry host side!"); 18 } catch (err) {
+2 -2
packages/core-extensions/src/disableSentry/index.ts
··· 6 find: "profiledRootComponent:", 7 replace: { 8 type: PatchReplaceType.Normal, 9 - match: /(?<=\.Z=){.+?}}/, 10 - replacement: 'require("disableSentry_stub").proxy()' 11 } 12 }, 13 {
··· 6 find: "profiledRootComponent:", 7 replace: { 8 type: PatchReplaceType.Normal, 9 + match: /Z:\(\)=>\i/, 10 + replacement: 'Z:()=>require("disableSentry_stub").proxy()' 11 } 12 }, 13 {
+5 -1
packages/core-extensions/src/disableSentry/manifest.json
··· 1 { 2 "id": "disableSentry", 3 "apiLevel": 2, 4 "meta": { ··· 11 "https://*.sentry.io/*", 12 "https://*.discord.com/error-reporting-proxy/*", 13 "https://discord.com/assets/sentry.*.js", 14 - "https://*.discord.com/assets/sentry.*.js" 15 ] 16 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "disableSentry", 4 "apiLevel": 2, 5 "meta": { ··· 12 "https://*.sentry.io/*", 13 "https://*.discord.com/error-reporting-proxy/*", 14 "https://discord.com/assets/sentry.*.js", 15 + "https://*.discord.com/assets/sentry.*.js", 16 + "https://*.discordapp.com/error-reporting-proxy/*", 17 + "https://discordapp.com/assets/sentry.*.js", 18 + "https://*.discordapp.com/assets/sentry.*.js" 19 ] 20 }
+13 -19
packages/core-extensions/src/disableSentry/node.ts
··· 5 6 const logger = moonlightNode.getLogger("disableSentry"); 7 8 - if (!ipcRenderer.sendSync(constants.ipcGetIsMoonlightDesktop)) { 9 - const preloadPath = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath); 10 - try { 11 - const sentryPath = require.resolve( 12 - resolve(preloadPath, "..", "node_modules", "@sentry", "electron") 13 - ); 14 - require.cache[sentryPath] = new Module( 15 - sentryPath, 16 - require.cache[require.resolve(preloadPath)] 17 - ); 18 - require.cache[sentryPath]!.exports = { 19 - init: () => {}, 20 - setTag: () => {}, 21 - setUser: () => {} 22 - }; 23 - logger.debug("Stubbed Sentry node side!"); 24 - } catch (err) { 25 - logger.error("Failed to stub Sentry:", err); 26 - } 27 }
··· 5 6 const logger = moonlightNode.getLogger("disableSentry"); 7 8 + const preloadPath = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath); 9 + try { 10 + const sentryPath = require.resolve(resolve(preloadPath, "..", "node_modules", "@sentry", "electron")); 11 + require.cache[sentryPath] = new Module(sentryPath, require.cache[require.resolve(preloadPath)]); 12 + require.cache[sentryPath]!.exports = { 13 + init: () => {}, 14 + setTag: () => {}, 15 + setUser: () => {}, 16 + captureMessage: () => {} 17 + }; 18 + logger.debug("Stubbed Sentry node side!"); 19 + } catch (err) { 20 + logger.error("Failed to stub Sentry:", err); 21 }
+1 -2
packages/core-extensions/src/disableSentry/webpackModules/stub.ts
··· 23 throw Error("crash"); 24 }; 25 } else if (keys.includes(prop.toString())) { 26 - return (...args: any[]) => 27 - logger.debug(`Sentry calling "${prop.toString()}":`, ...args); 28 } else { 29 return undefined; 30 }
··· 23 throw Error("crash"); 24 }; 25 } else if (keys.includes(prop.toString())) { 26 + return (...args: any[]) => logger.debug(`Sentry calling "${prop.toString()}":`, ...args); 27 } else { 28 return undefined; 29 }
+39 -2
packages/core-extensions/src/experiments/index.ts
··· 11 { 12 find: '"scientist:triggered"', // Scientist? Triggered. 13 replace: { 14 - match: /(?<=personal_connection_id\|\|)!1/, 15 - replacement: "!0" 16 } 17 } 18 ];
··· 11 { 12 find: '"scientist:triggered"', // Scientist? Triggered. 13 replace: { 14 + match: ".personal_connection_id", 15 + replacement: ".personal_connection_id || true" 16 + } 17 + }, 18 + 19 + // Enable staff help menu 20 + { 21 + find: ".HEADER_BAR)", 22 + replace: { 23 + match: /&&\((.)\?\(0,/, 24 + replacement: (_, isStaff) => 25 + `&&(((moonlight.getConfigOption("experiments","devtools")??false)?true:${isStaff})?(0,` 26 + } 27 + }, 28 + // staff help menu - visual refresh 29 + { 30 + find: '("AppTitleBar")', 31 + replace: { 32 + match: /{hasBugReporterAccess:(\i)}=\i\.\i\.useExperiment\({location:"HeaderBar"},{autoTrackExposure:!1}\);/, 33 + replacement: (orig, isStaff) => 34 + `${orig}if(moonlight.getConfigOption("experiments","devtools")??false)${isStaff}=true;` 35 + } 36 + }, 37 + { 38 + find: 'navId:"staff-help-popout",', 39 + replace: { 40 + match: /isDiscordDeveloper:(\i)}\),/, 41 + replacement: (_, isStaff) => 42 + `isDiscordDeveloper:(moonlight.getConfigOption("experiments","devtools")??false)||${isStaff}}),` 43 + } 44 + }, 45 + 46 + // Enable further staff-locked options 47 + { 48 + find: "shouldShowLurkerModeUpsellPopout:", 49 + replace: { 50 + match: /\.useReducedMotion,isStaff:(.),/, 51 + replacement: (_, isStaff) => 52 + `.useReducedMotion,isStaff:(moonlight.getConfigOption("experiments","staffSettings")??false)?true:${isStaff},` 53 } 54 } 55 ];
+11 -2
packages/core-extensions/src/experiments/manifest.json
··· 1 { 2 "id": "experiments", 3 "apiLevel": 2, 4 "meta": { ··· 8 "tags": ["dangerZone"] 9 }, 10 "settings": { 11 - "sections": { 12 "displayName": "Allow access to other staff settings elsewhere", 13 - "type": "boolean" 14 } 15 } 16 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "experiments", 4 "apiLevel": 2, 5 "meta": { ··· 9 "tags": ["dangerZone"] 10 }, 11 "settings": { 12 + "devtools": { 13 + "advice": "reload", 14 + "displayName": "Enable staff help menu (DevTools)", 15 + "type": "boolean", 16 + "default": false 17 + }, 18 + "staffSettings": { 19 + "advice": "reload", 20 "displayName": "Allow access to other staff settings elsewhere", 21 + "type": "boolean", 22 + "default": false 23 } 24 } 25 }
+6 -18
packages/core-extensions/src/markdown/index.ts
··· 6 replace: [ 7 { 8 match: /={newline:(.+?)},(.{1,2})=\(0,/, 9 - replacement: (_, rules, RULES) => 10 - `=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,` 11 }, 12 { 13 match: /(?<=;(.{1,2}\.Z)={RULES:.+?})/, 14 - replacement: (_, rulesets) => 15 - `;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});` 16 } 17 ] 18 }, ··· 25 `__slateRules,${rulesDef}=__slateRules=require("markdown_markdown")._addSlateRules({link:{${rules}}),${syntaxBefore}=new Set` 26 }, 27 { 28 - match: 29 - /(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"subtext":{(.+?)}default:/, 30 - replacement: ( 31 - _, 32 - start, 33 - rule, 34 - body, 35 - plaintextReturn, 36 - otherRules, 37 - inlineStyleBody 38 - ) => 39 - `${start}if(${rule}.type.startsWith("__moonlight_")){if(__slateRules[${rule}.type].type=="inlineStyle"){${inlineStyleBody}}else{${plaintextReturn}}}${body}case"emoticon":${plaintextReturn}${otherRules}case"link":{${inlineStyleBody}}default:` 40 } 41 ] 42 }, ··· 44 find: '"Slate: Unknown decoration attribute: "', 45 replace: { 46 match: /=({strong:.+?});/, 47 - replacement: (_, rules) => 48 - `=require("markdown_markdown")._addSlateDecorators(${rules});` 49 } 50 } 51 ];
··· 6 replace: [ 7 { 8 match: /={newline:(.+?)},(.{1,2})=\(0,/, 9 + replacement: (_, rules, RULES) => `=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,` 10 }, 11 { 12 match: /(?<=;(.{1,2}\.Z)={RULES:.+?})/, 13 + replacement: (_, rulesets) => `;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});` 14 } 15 ] 16 }, ··· 23 `__slateRules,${rulesDef}=__slateRules=require("markdown_markdown")._addSlateRules({link:{${rules}}),${syntaxBefore}=new Set` 24 }, 25 { 26 + match: /(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"subtext":{(.+?)}default:/, 27 + replacement: (_, start, rule, body, plaintextReturn, otherRules, inlineStyleBody) => 28 + `${start}if(${rule}.type.startsWith("__moonlight_")){if(__slateRules[${rule}.type].type=="inlineStyle"){${inlineStyleBody}}else{${plaintextReturn}}}${body}case"emoticon":${plaintextReturn}${otherRules}case"subtext":{${inlineStyleBody}}default:` 29 } 30 ] 31 }, ··· 33 find: '"Slate: Unknown decoration attribute: "', 34 replace: { 35 match: /=({strong:.+?});/, 36 + replacement: (_, rules) => `=require("markdown_markdown")._addSlateDecorators(${rules});` 37 } 38 } 39 ];
+1
packages/core-extensions/src/markdown/manifest.json
··· 1 { 2 "id": "markdown", 3 "apiLevel": 2, 4 "meta": {
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "markdown", 4 "apiLevel": 2, 5 "meta": {
+4 -17
packages/core-extensions/src/markdown/webpackModules/markdown.ts
··· 1 - /* eslint-disable no-console */ 2 - import { 3 - MarkdownRule, 4 - Ruleset, 5 - SlateRule 6 - } from "@moonlight-mod/types/coreExtensions/markdown"; 7 8 - export const rules: Record< 9 - string, 10 - (rules: Record<string, MarkdownRule>) => MarkdownRule 11 - > = {}; 12 - export const slateRules: Record< 13 - string, 14 - (rules: Record<string, SlateRule>) => SlateRule 15 - > = {}; 16 export const slateDecorators: Record<string, string> = {}; 17 export const ruleBlacklists: Record<Ruleset, Record<string, boolean>> = { 18 RULES: {}, ··· 67 return originalRules; 68 } 69 70 - export function _applyRulesetBlacklist( 71 - rulesets: Record<Ruleset, Record<string, MarkdownRule>> 72 - ) { 73 for (const ruleset of Object.keys(rulesets) as Ruleset[]) { 74 if (ruleset === "RULES") continue; 75
··· 1 + import { MarkdownRule, Ruleset, SlateRule } from "@moonlight-mod/types/coreExtensions/markdown"; 2 3 + export const rules: Record<string, (rules: Record<string, MarkdownRule>) => MarkdownRule> = {}; 4 + export const slateRules: Record<string, (rules: Record<string, SlateRule>) => SlateRule> = {}; 5 export const slateDecorators: Record<string, string> = {}; 6 export const ruleBlacklists: Record<Ruleset, Record<string, boolean>> = { 7 RULES: {}, ··· 56 return originalRules; 57 } 58 59 + export function _applyRulesetBlacklist(rulesets: Record<Ruleset, Record<string, MarkdownRule>>) { 60 for (const ruleset of Object.keys(rulesets) as Ruleset[]) { 61 if (ruleset === "RULES") continue; 62
+108
packages/core-extensions/src/moonbase/host.ts
···
··· 1 + import * as electron from "electron"; 2 + import * as fs from "node:fs/promises"; 3 + import * as path from "node:path"; 4 + import getNatives from "./native"; 5 + import { MoonlightBranch } from "@moonlight-mod/types"; 6 + 7 + const natives = getNatives(); 8 + 9 + const confirm = (action: string) => 10 + electron.dialog 11 + .showMessageBox({ 12 + title: "Are you sure?", 13 + message: `Are you sure? This will ${action} and restart Discord.`, 14 + type: "warning", 15 + buttons: ["OK", "Cancel"] 16 + }) 17 + .then((r) => r.response === 0); 18 + 19 + async function updateAndRestart() { 20 + if (!(await confirm("update moonlight"))) return; 21 + const newVersion = await natives.checkForMoonlightUpdate(); 22 + 23 + if (newVersion === null) { 24 + electron.dialog.showMessageBox({ message: "You are already on the latest version of moonlight." }); 25 + return; 26 + } 27 + 28 + try { 29 + await natives.updateMoonlight(); 30 + await electron.dialog.showMessageBox({ message: "Update successful, restarting Discord." }); 31 + electron.app.relaunch(); 32 + electron.app.exit(0); 33 + } catch { 34 + await electron.dialog.showMessageBox({ 35 + message: "Failed to update moonlight. Please use the installer instead.", 36 + type: "error" 37 + }); 38 + } 39 + } 40 + 41 + async function resetConfig() { 42 + if (!(await confirm("reset your configuration"))) return; 43 + 44 + const config = await moonlightHost.getConfigPath(); 45 + const dir = path.dirname(config); 46 + const branch = path.basename(config, ".json"); 47 + await fs.rename(config, path.join(dir, `${branch}-backup-${Math.floor(Date.now() / 1000)}.json`)); 48 + 49 + await electron.dialog.showMessageBox({ message: "Configuration reset, restarting Discord." }); 50 + electron.app.relaunch(); 51 + electron.app.exit(0); 52 + } 53 + 54 + async function changeBranch(branch: MoonlightBranch) { 55 + if (moonlightHost.branch === branch) return; 56 + if (!(await confirm("switch branches"))) return; 57 + try { 58 + await natives.updateMoonlight(branch); 59 + await electron.dialog.showMessageBox({ message: "Branch switch successful, restarting Discord." }); 60 + electron.app.relaunch(); 61 + electron.app.exit(0); 62 + } catch (e) { 63 + await electron.dialog.showMessageBox({ message: "Failed to switch branches:\n" + e, type: "error" }); 64 + } 65 + } 66 + 67 + function showAbout() { 68 + electron.dialog.showMessageBox({ 69 + title: "About moonlight", 70 + message: `moonlight ${moonlightHost.branch} ${moonlightHost.version}` 71 + }); 72 + } 73 + 74 + electron.app.whenReady().then(() => { 75 + const original = electron.Menu.buildFromTemplate; 76 + electron.Menu.buildFromTemplate = function (entries) { 77 + const i = entries.findIndex((e) => e.label === "Check for Updates..."); 78 + if (i === -1) return original.call(this, entries); 79 + 80 + if (!entries.find((e) => e.label === "moonlight")) { 81 + const options: Electron.MenuItemConstructorOptions[] = [ 82 + { label: "Update and restart", click: updateAndRestart }, 83 + { label: "Reset config", click: resetConfig } 84 + ]; 85 + 86 + if (moonlightHost.branch !== MoonlightBranch.DEV) { 87 + options.push({ 88 + label: "Switch branch", 89 + submenu: [MoonlightBranch.STABLE, MoonlightBranch.NIGHTLY].map((branch) => ({ 90 + label: branch, 91 + type: "radio", 92 + checked: moonlightHost.branch === branch, 93 + click: () => changeBranch(branch) 94 + })) 95 + }); 96 + } 97 + 98 + options.push({ label: "About", click: showAbout }); 99 + 100 + entries.splice(i + 1, 0, { 101 + label: "moonlight", 102 + submenu: options 103 + }); 104 + } 105 + 106 + return original.call(this, entries); 107 + }; 108 + });
+59 -59
packages/core-extensions/src/moonbase/index.tsx
··· 1 - import { ExtensionWebpackModule } from "@moonlight-mod/types"; 2 3 export const webpackModules: Record<string, ExtensionWebpackModule> = { 4 stores: { 5 - dependencies: [ 6 - { id: "discord/packages/flux" }, 7 - { id: "discord/Dispatcher" } 8 - ] 9 }, 10 11 ui: { ··· 14 { id: "react" }, 15 { id: "discord/components/common/index" }, 16 { ext: "moonbase", id: "stores" }, 17 - { id: "discord/modules/guild_settings/IntegrationCard.css" }, 18 "Masks.PANEL_BUTTON", 19 '"Missing channel in Channel.openChannelContextMenu"', 20 ".forumOrHome]:" 21 ] 22 }, 23 24 settings: { 25 dependencies: [ 26 { ext: "spacepack", id: "spacepack" }, 27 { ext: "settings", id: "settings" }, 28 { id: "react" }, 29 - { ext: "moonbase", id: "ui" } 30 ], 31 entrypoint: true 32 }, ··· 35 dependencies: [ 36 { id: "react" }, 37 { ext: "moonbase", id: "stores" }, 38 { ext: "notices", id: "notices" }, 39 { 40 ext: "spacepack", 41 id: "spacepack" 42 - } 43 ], 44 entrypoint: true 45 }, 46 47 moonbase: { 48 dependencies: [{ ext: "moonbase", id: "stores" }] 49 } 50 }; 51 - 52 - const bg = "#222034"; 53 - const fg = "#FFFBA6"; 54 - 55 - export const styles = [ 56 - ` 57 - .moonbase-settings > :first-child { 58 - margin-top: 0px; 59 - } 60 - 61 - textarea.moonbase-resizeable { 62 - resize: vertical 63 - } 64 - 65 - .moonbase-updates-notice { 66 - background-color: ${bg}; 67 - color: ${fg}; 68 - line-height: unset; 69 - height: 36px; 70 - } 71 - 72 - .moonbase-updates-notice button { 73 - color: ${fg}; 74 - border-color: ${fg}; 75 - } 76 - 77 - .moonbase-updates-notice_text-wrapper { 78 - display: inline-flex; 79 - align-items: center; 80 - line-height: 36px; 81 - gap: 2px; 82 - } 83 - 84 - .moonbase-update-section { 85 - background-color: ${bg}; 86 - --info-help-foreground: ${fg}; 87 - border: none !important; 88 - color: ${fg}; 89 - 90 - display: flex; 91 - flex-direction: row; 92 - justify-content: space-between; 93 - } 94 - 95 - .moonbase-update-section > button { 96 - color: ${fg}; 97 - background-color: transparent; 98 - border-color: ${fg}; 99 - } 100 - `.trim() 101 - ];
··· 1 + import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; 2 + 3 + export const patches: Patch[] = [ 4 + { 5 + find: "window.DiscordErrors=", 6 + replace: [ 7 + // replace reporting line with update status 8 + { 9 + // CvQlAA mapped to ERRORS_ACTION_TO_TAKE 10 + // FIXME: Better patch find? 11 + match: /,(\(0,(\i)\.jsx\))\("p",{children:\i\.\i\.string\(\i\.\i\.CvQlAA\)}\)/, 12 + replacement: (_, createElement, ReactJSX) => 13 + `,${createElement}(require("moonbase_crashScreen")?.UpdateText??${ReactJSX}.Fragment,{state:this.state,setState:this.setState.bind(this)})` 14 + }, 15 + 16 + // wrap actions field to display error details 17 + { 18 + match: /(?<=return(\(0,(\i)\.jsx\))\(.+?,)action:(\i),className:/, 19 + replacement: (_, createElement, ReactJSX, action) => 20 + `action:require("moonbase_crashScreen")?.wrapAction?${createElement}(require("moonbase_crashScreen").wrapAction,{action:${action},state:this.state}):${action},className:` 21 + }, 22 + 23 + // add update button 24 + // +hivLS -> ERRORS_RELOAD 25 + { 26 + match: /(?<=\["\+hivLS"\]\)}\),(\(0,(\i)\.jsx\))\(\i,{}\))/, 27 + replacement: (_, createElement, ReactJSX) => 28 + `,${createElement}(require("moonbase_crashScreen")?.UpdateButton??${ReactJSX}.Fragment,{state:this.state,setState:this.setState.bind(this)})` 29 + } 30 + ] 31 + } 32 + ]; 33 34 export const webpackModules: Record<string, ExtensionWebpackModule> = { 35 stores: { 36 + dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }] 37 }, 38 39 ui: { ··· 42 { id: "react" }, 43 { id: "discord/components/common/index" }, 44 { ext: "moonbase", id: "stores" }, 45 + { ext: "moonbase", id: "ThemeDarkIcon" }, 46 + { id: "discord/modules/guild_settings/web/AppCard.css" }, 47 + { ext: "contextMenu", id: "contextMenu" }, 48 + { id: "discord/modules/modals/Modals" }, 49 "Masks.PANEL_BUTTON", 50 '"Missing channel in Channel.openChannelContextMenu"', 51 ".forumOrHome]:" 52 ] 53 }, 54 55 + ThemeDarkIcon: { 56 + dependencies: [{ ext: "common", id: "icons" }, { id: "react" }] 57 + }, 58 + 59 settings: { 60 dependencies: [ 61 { ext: "spacepack", id: "spacepack" }, 62 { ext: "settings", id: "settings" }, 63 { id: "react" }, 64 + { ext: "moonbase", id: "ui" }, 65 + { ext: "contextMenu", id: "contextMenu" }, 66 + ':"USER_SETTINGS_MODAL_SET_SECTION"' 67 ], 68 entrypoint: true 69 }, ··· 72 dependencies: [ 73 { id: "react" }, 74 { ext: "moonbase", id: "stores" }, 75 + { ext: "moonbase", id: "ThemeDarkIcon" }, 76 { ext: "notices", id: "notices" }, 77 { 78 ext: "spacepack", 79 id: "spacepack" 80 + }, 81 + { id: "discord/Constants" }, 82 + { id: "discord/components/common/index" } 83 ], 84 entrypoint: true 85 }, 86 87 moonbase: { 88 dependencies: [{ ext: "moonbase", id: "stores" }] 89 + }, 90 + 91 + crashScreen: { 92 + dependencies: [ 93 + { ext: "spacepack", id: "spacepack" }, 94 + { id: "react" }, 95 + { ext: "moonbase", id: "stores" }, 96 + { id: "discord/packages/flux" }, 97 + { id: "discord/components/common/index" }, 98 + /tabBar:"tabBar_[a-z0-9]+",tabBarItem:"tabBarItem_[a-z0-9]+"/ 99 + ] 100 } 101 };
+18 -5
packages/core-extensions/src/moonbase/manifest.json
··· 1 { 2 "id": "moonbase", 3 "apiLevel": 2, 4 "meta": { ··· 6 "tagline": "The official settings UI for moonlight", 7 "authors": ["Cynosphere", "NotNite", "redstonekasi"] 8 }, 9 - "dependencies": ["spacepack", "settings", "common", "notices"], 10 "settings": { 11 "sections": { 12 "displayName": "Split into sections", 13 "description": "Show the Moonbase tabs as separate sections", 14 - "type": "boolean" 15 }, 16 "saveFilter": { 17 "displayName": "Persist filter", 18 "description": "Save extension filter in config", 19 - "type": "boolean" 20 }, 21 "updateChecking": { 22 "displayName": "Automatic update checking", 23 "description": "Checks for updates to moonlight", 24 "type": "boolean", 25 - "default": "true" 26 }, 27 "updateBanner": { 28 "displayName": "Show update banner", 29 "description": "Shows a banner for moonlight and extension updates", 30 "type": "boolean", 31 - "default": "true" 32 } 33 }, 34 "cors": [
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "moonbase", 4 "apiLevel": 2, 5 "meta": { ··· 7 "tagline": "The official settings UI for moonlight", 8 "authors": ["Cynosphere", "NotNite", "redstonekasi"] 9 }, 10 + "dependencies": ["spacepack", "settings", "common", "notices", "contextMenu"], 11 "settings": { 12 "sections": { 13 + "advice": "reload", 14 "displayName": "Split into sections", 15 "description": "Show the Moonbase tabs as separate sections", 16 + "type": "boolean", 17 + "default": false 18 + }, 19 + "oldLocation": { 20 + "advice": "reload", 21 + "displayName": "Put Moonbase back at the bottom", 22 + "type": "boolean", 23 + "default": false 24 }, 25 "saveFilter": { 26 + "advice": "none", 27 "displayName": "Persist filter", 28 "description": "Save extension filter in config", 29 + "type": "boolean", 30 + "default": false 31 }, 32 "updateChecking": { 33 + "advice": "none", 34 "displayName": "Automatic update checking", 35 "description": "Checks for updates to moonlight", 36 "type": "boolean", 37 + "default": true 38 }, 39 "updateBanner": { 40 + "advice": "none", 41 "displayName": "Show update banner", 42 "description": "Shows a banner for moonlight and extension updates", 43 "type": "boolean", 44 + "default": true 45 } 46 }, 47 "cors": [
+46 -71
packages/core-extensions/src/moonbase/native.ts
··· 1 import { MoonlightBranch } from "@moonlight-mod/types"; 2 import type { MoonbaseNatives, RepositoryManifest } from "./types"; 3 import extractAsar from "@moonlight-mod/core/asar"; 4 - import { 5 - distDir, 6 - repoUrlFile, 7 - installedVersionFile 8 - } from "@moonlight-mod/types/constants"; 9 import { parseTarGzip } from "nanotar"; 10 11 const githubRepo = "moonlight-mod/moonlight"; 12 const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`; ··· 15 const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; 16 const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz"; 17 18 - export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; 19 20 async function getStableRelease(): Promise<{ 21 name: string; ··· 26 }> { 27 const req = await fetch(githubApiUrl, { 28 cache: "no-store", 29 - headers: { 30 - "User-Agent": userAgent 31 - } 32 }); 33 return await req.json(); 34 } 35 36 export default function getNatives(): MoonbaseNatives { 37 - const logger = moonlightNode.getLogger("moonbase/natives"); 38 39 return { 40 async checkForMoonlightUpdate() { 41 try { 42 - if (moonlightNode.branch === MoonlightBranch.STABLE) { 43 const json = await getStableRelease(); 44 - return json.name !== moonlightNode.version ? json.name : null; 45 - } else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) { 46 const req = await fetch(nightlyRefUrl, { 47 cache: "no-store", 48 - headers: { 49 - "User-Agent": userAgent 50 - } 51 }); 52 const ref = (await req.text()).split("\n")[0]; 53 - return ref !== moonlightNode.version ? ref : null; 54 } 55 56 return null; ··· 60 } 61 }, 62 63 - async updateMoonlight() { 64 // Note: this won't do anything on browser, we should probably disable it 65 // entirely when running in browser. 66 async function downloadStable(): Promise<[ArrayBuffer, string]> { ··· 71 logger.debug(`Downloading ${asset.browser_download_url}`); 72 const req = await fetch(asset.browser_download_url, { 73 cache: "no-store", 74 - headers: { 75 - "User-Agent": userAgent 76 - } 77 }); 78 79 return [await req.arrayBuffer(), json.name]; ··· 83 logger.debug(`Downloading ${nightlyZipUrl}`); 84 const zipReq = await fetch(nightlyZipUrl, { 85 cache: "no-store", 86 - headers: { 87 - "User-Agent": userAgent 88 - } 89 }); 90 91 const refReq = await fetch(nightlyRefUrl, { 92 cache: "no-store", 93 - headers: { 94 - "User-Agent": userAgent 95 - } 96 }); 97 const ref = (await refReq.text()).split("\n")[0]; 98 ··· 100 } 101 102 const [tar, ref] = 103 - moonlightNode.branch === MoonlightBranch.STABLE 104 ? await downloadStable() 105 - : moonlightNode.branch === MoonlightBranch.NIGHTLY 106 ? await downloadNightly() 107 : [null, null]; 108 109 if (!tar || !ref) return; 110 111 - const dist = moonlightFS.join(moonlightNode.getMoonlightDir(), distDir); 112 - if (await moonlightFS.exists(dist)) await moonlightFS.rmdir(dist); 113 - await moonlightFS.mkdir(dist); 114 115 logger.debug("Extracting update"); 116 const files = await parseTarGzip(tar); ··· 119 // @ts-expect-error What do you mean their own types are wrong 120 if (file.type !== "file") continue; 121 122 - const fullFile = moonlightFS.join(dist, file.name); 123 - const fullDir = moonlightFS.dirname(fullFile); 124 - if (!(await moonlightFS.exists(fullDir))) 125 - await moonlightFS.mkdir(fullDir); 126 - await moonlightFS.writeFile(fullFile, file.data); 127 } 128 129 logger.debug("Writing version file:", ref); 130 - const versionFile = moonlightFS.join( 131 - moonlightNode.getMoonlightDir(), 132 - installedVersionFile 133 - ); 134 - await moonlightFS.writeFileString(versionFile, ref.trim()); 135 136 logger.debug("Update extracted"); 137 }, ··· 143 try { 144 const req = await fetch(repo, { 145 cache: "no-store", 146 - headers: { 147 - "User-Agent": userAgent 148 - } 149 }); 150 const json = await req.json(); 151 ret[repo] = json; ··· 159 160 async installExtension(manifest, url, repo) { 161 const req = await fetch(url, { 162 - headers: { 163 - "User-Agent": userAgent 164 - } 165 }); 166 167 - const dir = moonlightNode.getExtensionDir(manifest.id); 168 // remake it in case of updates 169 - if (await moonlightFS.exists(dir)) await moonlightFS.rmdir(dir); 170 - await moonlightFS.mkdir(dir); 171 172 const buffer = await req.arrayBuffer(); 173 const files = extractAsar(buffer); 174 for (const [file, buf] of Object.entries(files)) { 175 - const fullFile = moonlightFS.join(dir, file); 176 - const fullDir = moonlightFS.dirname(fullFile); 177 178 - if (!(await moonlightFS.exists(fullDir))) 179 - await moonlightFS.mkdir(fullDir); 180 - await moonlightFS.writeFile(moonlightFS.join(dir, file), buf); 181 } 182 183 - await moonlightFS.writeFileString( 184 - moonlightFS.join(dir, repoUrlFile), 185 - repo 186 - ); 187 }, 188 189 async deleteExtension(id) { 190 - const dir = moonlightNode.getExtensionDir(id); 191 - await moonlightFS.rmdir(dir); 192 - }, 193 - 194 - getExtensionConfig(id, key) { 195 - const config = moonlightNode.config.extensions[id]; 196 - if (typeof config === "object") { 197 - return config.config?.[key]; 198 - } 199 - 200 - return undefined; 201 } 202 }; 203 }
··· 1 import { MoonlightBranch } from "@moonlight-mod/types"; 2 import type { MoonbaseNatives, RepositoryManifest } from "./types"; 3 import extractAsar from "@moonlight-mod/core/asar"; 4 + import { distDir, repoUrlFile, installedVersionFile } from "@moonlight-mod/types/constants"; 5 import { parseTarGzip } from "nanotar"; 6 + 7 + const moonlightGlobal = globalThis.moonlightHost ?? globalThis.moonlightNode; 8 9 const githubRepo = "moonlight-mod/moonlight"; 10 const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`; ··· 13 const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; 14 const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz"; 15 16 + export const userAgent = `moonlight/${moonlightGlobal.version} (https://github.com/moonlight-mod/moonlight)`; 17 + 18 + // User-Agent header causes trouble on Firefox 19 + const isBrowser = globalThis.moonlightNode != null && globalThis.moonlightNode.isBrowser; 20 + const sharedHeaders: Record<string, string> = {}; 21 + if (!isBrowser) sharedHeaders["User-Agent"] = userAgent; 22 23 async function getStableRelease(): Promise<{ 24 name: string; ··· 29 }> { 30 const req = await fetch(githubApiUrl, { 31 cache: "no-store", 32 + headers: sharedHeaders 33 }); 34 return await req.json(); 35 } 36 37 export default function getNatives(): MoonbaseNatives { 38 + const logger = moonlightGlobal.getLogger("moonbase/natives"); 39 40 return { 41 async checkForMoonlightUpdate() { 42 try { 43 + if (moonlightGlobal.branch === MoonlightBranch.STABLE) { 44 const json = await getStableRelease(); 45 + return json.name !== moonlightGlobal.version ? json.name : null; 46 + } else if (moonlightGlobal.branch === MoonlightBranch.NIGHTLY) { 47 const req = await fetch(nightlyRefUrl, { 48 cache: "no-store", 49 + headers: sharedHeaders 50 }); 51 const ref = (await req.text()).split("\n")[0]; 52 + return ref !== moonlightGlobal.version ? ref : null; 53 } 54 55 return null; ··· 59 } 60 }, 61 62 + async updateMoonlight(overrideBranch?: MoonlightBranch) { 63 + const branch = overrideBranch ?? moonlightGlobal.branch; 64 + 65 // Note: this won't do anything on browser, we should probably disable it 66 // entirely when running in browser. 67 async function downloadStable(): Promise<[ArrayBuffer, string]> { ··· 72 logger.debug(`Downloading ${asset.browser_download_url}`); 73 const req = await fetch(asset.browser_download_url, { 74 cache: "no-store", 75 + headers: sharedHeaders 76 }); 77 78 return [await req.arrayBuffer(), json.name]; ··· 82 logger.debug(`Downloading ${nightlyZipUrl}`); 83 const zipReq = await fetch(nightlyZipUrl, { 84 cache: "no-store", 85 + headers: sharedHeaders 86 }); 87 88 const refReq = await fetch(nightlyRefUrl, { 89 cache: "no-store", 90 + headers: sharedHeaders 91 }); 92 const ref = (await refReq.text()).split("\n")[0]; 93 ··· 95 } 96 97 const [tar, ref] = 98 + branch === MoonlightBranch.STABLE 99 ? await downloadStable() 100 + : branch === MoonlightBranch.NIGHTLY 101 ? await downloadNightly() 102 : [null, null]; 103 104 if (!tar || !ref) return; 105 106 + const dist = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), distDir); 107 + if (await moonlightNodeSandboxed.fs.exists(dist)) await moonlightNodeSandboxed.fs.rmdir(dist); 108 + await moonlightNodeSandboxed.fs.mkdir(dist); 109 110 logger.debug("Extracting update"); 111 const files = await parseTarGzip(tar); ··· 114 // @ts-expect-error What do you mean their own types are wrong 115 if (file.type !== "file") continue; 116 117 + const fullFile = moonlightNodeSandboxed.fs.join(dist, file.name); 118 + const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile); 119 + if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir); 120 + await moonlightNodeSandboxed.fs.writeFile(fullFile, file.data); 121 } 122 123 logger.debug("Writing version file:", ref); 124 + const versionFile = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), installedVersionFile); 125 + await moonlightNodeSandboxed.fs.writeFileString(versionFile, ref.trim()); 126 127 logger.debug("Update extracted"); 128 }, ··· 134 try { 135 const req = await fetch(repo, { 136 cache: "no-store", 137 + headers: sharedHeaders 138 }); 139 const json = await req.json(); 140 ret[repo] = json; ··· 148 149 async installExtension(manifest, url, repo) { 150 const req = await fetch(url, { 151 + cache: "no-store", 152 + headers: sharedHeaders 153 }); 154 155 + const dir = moonlightGlobal.getExtensionDir(manifest.id); 156 // remake it in case of updates 157 + if (await moonlightNodeSandboxed.fs.exists(dir)) await moonlightNodeSandboxed.fs.rmdir(dir); 158 + await moonlightNodeSandboxed.fs.mkdir(dir); 159 160 const buffer = await req.arrayBuffer(); 161 const files = extractAsar(buffer); 162 for (const [file, buf] of Object.entries(files)) { 163 + const fullFile = moonlightNodeSandboxed.fs.join(dir, file); 164 + const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile); 165 166 + if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir); 167 + await moonlightNodeSandboxed.fs.writeFile(moonlightNodeSandboxed.fs.join(dir, file), buf); 168 } 169 170 + await moonlightNodeSandboxed.fs.writeFileString(moonlightNodeSandboxed.fs.join(dir, repoUrlFile), repo); 171 }, 172 173 async deleteExtension(id) { 174 + const dir = moonlightGlobal.getExtensionDir(id); 175 + await moonlightNodeSandboxed.fs.rmdir(dir); 176 } 177 }; 178 }
+269
packages/core-extensions/src/moonbase/style.css
···
··· 1 + :root { 2 + --moonbase-bg: #222034; 3 + --moonbase-fg: #fffba6; 4 + } 5 + 6 + .moonbase-settings > :first-child { 7 + margin-top: 0px; 8 + } 9 + 10 + .moonbase-retry-button { 11 + padding: 8px; 12 + margin-right: 8px; 13 + } 14 + 15 + textarea.moonbase-resizeable { 16 + resize: vertical; 17 + } 18 + 19 + .moonbase-link-buttons { 20 + border-bottom: 2px solid var(--background-modifier-accent); 21 + margin-bottom: -2px; 22 + margin-left: 0 !important; 23 + padding-right: 20px; 24 + gap: 1rem; 25 + } 26 + 27 + .moonbase-speen { 28 + animation: moonbase-speen-animation 0.25s linear infinite; 29 + } 30 + 31 + @keyframes moonbase-speen-animation { 32 + from { 33 + transform: rotate(0deg); 34 + } 35 + to { 36 + transform: rotate(360deg); 37 + } 38 + } 39 + 40 + /* Update notice at the top of the client */ 41 + .moonbase-updates-notice { 42 + background-color: var(--moonbase-bg); 43 + color: var(--moonbase-fg); 44 + --custom-notice-text: var(--moonbase-fg); 45 + line-height: unset; 46 + height: 36px; 47 + } 48 + 49 + .moonbase-updates-notice button { 50 + color: var(--moonbase-fg); 51 + border-color: var(--moonbase-fg); 52 + } 53 + 54 + .moonbase-updates-notice_text-wrapper { 55 + display: inline-flex; 56 + align-items: center; 57 + line-height: 36px; 58 + gap: 2px; 59 + } 60 + 61 + /* Help messages in Moonbase UI */ 62 + .moonbase-help-message { 63 + display: flex; 64 + flex-direction: row; 65 + justify-content: space-between; 66 + } 67 + 68 + .moonbase-help-message-sticky { 69 + position: sticky; 70 + top: 24px; 71 + z-index: 10; 72 + background-color: var(--background-primary); 73 + } 74 + 75 + .moonbase-extension-update-section { 76 + margin-top: 15px; 77 + } 78 + 79 + .moonbase-update-section { 80 + background-color: var(--moonbase-bg); 81 + --info-help-foreground: var(--moonbase-fg); 82 + border: none !important; 83 + color: var(--moonbase-fg); 84 + } 85 + 86 + .moonbase-update-section button { 87 + --info-help-foreground: var(--moonbase-fg); 88 + color: var(--moonbase-fg); 89 + background-color: transparent; 90 + border-color: var(--moonbase-fg); 91 + } 92 + 93 + .moonbase-help-message-buttons { 94 + display: flex; 95 + flex-direction: row; 96 + gap: 8px; 97 + align-items: center; 98 + } 99 + 100 + .moonbase-update-divider { 101 + margin: 32px 0; 102 + } 103 + 104 + .moonlight-card-info-header { 105 + margin-bottom: 0.25rem; 106 + } 107 + 108 + .moonlight-card-badge { 109 + border-radius: 0.1875rem; 110 + padding: 0 0.275rem; 111 + margin-right: 0.4em; 112 + background-color: var(--badge-color, var(--bg-mod-strong)); 113 + } 114 + 115 + /* Crash screen */ 116 + .moonbase-crash-wrapper > [class^="buttons_"] { 117 + gap: 1rem; 118 + } 119 + 120 + .moonbase-crash-wrapper { 121 + display: flex; 122 + flex-direction: column; 123 + align-items: center; 124 + gap: 1rem; 125 + height: 50%; 126 + width: 50vw; 127 + max-height: 50%; 128 + max-width: 50vw; 129 + } 130 + 131 + .moonbase-crash-tabs { 132 + width: 100%; 133 + } 134 + 135 + .moonbase-crash-details-wrapper { 136 + overflow-y: scroll; 137 + color: var(--text-normal); 138 + background: var(--background-secondary); 139 + border: 1px solid var(--background-tertiary); 140 + border-radius: 4px; 141 + padding: 0.5em; 142 + 143 + &::-webkit-scrollbar { 144 + width: 8px; 145 + height: 8px; 146 + } 147 + 148 + &::-webkit-scrollbar-thumb { 149 + background-clip: padding-box; 150 + border: 2px solid transparent; 151 + border-radius: 4px; 152 + background-color: var(--scrollbar-thin-thumb); 153 + min-height: 40px; 154 + } 155 + 156 + &::-webkit-scrollbar-track { 157 + border: 2px solid var(--scrollbar-thin-track); 158 + background-color: var(--scrollbar-thin-track); 159 + border-color: var(--scrollbar-thin-track); 160 + } 161 + } 162 + 163 + .moonbase-crash-details { 164 + box-sizing: border-box; 165 + padding: 0; 166 + font-family: var(--font-code); 167 + font-size: 0.75rem; 168 + line-height: 1rem; 169 + margin: 6px; 170 + white-space: pre-wrap; 171 + background-clip: border-box; 172 + 173 + & > code { 174 + font-size: 0.875rem; 175 + line-height: 1.125rem; 176 + text-indent: 0; 177 + white-space: pre-wrap; 178 + text-size-adjust: none; 179 + display: block; 180 + user-select: text; 181 + } 182 + } 183 + 184 + .moonbase-crash-extensions { 185 + overflow-y: scroll; 186 + display: grid; 187 + grid-auto-columns: 25vw; 188 + gap: 8px; 189 + 190 + &::-webkit-scrollbar { 191 + width: 8px; 192 + height: 8px; 193 + } 194 + 195 + &::-webkit-scrollbar-thumb { 196 + background-clip: padding-box; 197 + border: 2px solid transparent; 198 + border-radius: 4px; 199 + background-color: var(--scrollbar-thin-thumb); 200 + min-height: 40px; 201 + } 202 + 203 + &::-webkit-scrollbar-track { 204 + border: 2px solid var(--scrollbar-thin-track); 205 + background-color: var(--scrollbar-thin-track); 206 + border-color: var(--scrollbar-thin-track); 207 + } 208 + } 209 + 210 + .moonbase-crash-extensionCard { 211 + color: var(--text-normal); 212 + background: var(--background-secondary); 213 + border: 1px solid var(--background-tertiary); 214 + border-radius: 4px; 215 + padding: 0.5em; 216 + display: flex; 217 + } 218 + 219 + .moonbase-crash-extensionCard-meta { 220 + display: flex; 221 + flex-direction: column; 222 + flex-grow: 1; 223 + } 224 + 225 + .moonbase-crash-extensionCard-title { 226 + color: var(--text-normal); 227 + font-family: var(--font-primary); 228 + font-size: 16px; 229 + line-height: 1.25; 230 + font-weight: 600; 231 + } 232 + 233 + .moonbase-crash-extensionCard-version { 234 + color: var(--text-muted); 235 + font-family: var(--font-primary); 236 + font-size: 14px; 237 + line-height: 1.286; 238 + font-weight: 400; 239 + } 240 + 241 + /* About page */ 242 + .moonbase-wordmark { 243 + width: 100%; 244 + } 245 + 246 + .moonbase-devs { 247 + width: 100%; 248 + display: flex; 249 + justify-content: center; 250 + gap: 0rem 0.5rem; 251 + padding-top: 0.5rem; 252 + } 253 + 254 + .moonbase-dev { 255 + height: 4rem; 256 + } 257 + 258 + .moonbase-dev-avatar { 259 + width: 2rem; 260 + border-radius: 50%; 261 + } 262 + 263 + .moonbase-gap { 264 + gap: 0.5rem; 265 + } 266 + 267 + .moonbase-about-page { 268 + gap: 1rem; 269 + }
+21 -11
packages/core-extensions/src/moonbase/types.ts
··· 1 import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 2 - import { DetectedExtension, ExtensionManifest } from "@moonlight-mod/types"; 3 4 export type MoonbaseNatives = { 5 checkForMoonlightUpdate(): Promise<string | null>; 6 - updateMoonlight(): Promise<void>; 7 8 - fetchRepositories( 9 - repos: string[] 10 - ): Promise<Record<string, RepositoryManifest[]>>; 11 - installExtension( 12 - manifest: RepositoryManifest, 13 - url: string, 14 - repo: string 15 - ): Promise<void>; 16 deleteExtension(id: string): Promise<void>; 17 - getExtensionConfig(id: string, key: string): any; 18 }; 19 20 export type RepositoryManifest = ExtensionManifest & { ··· 35 state: ExtensionState; 36 compat: ExtensionCompat; 37 hasUpdate: boolean; 38 };
··· 1 import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 2 + import { DetectedExtension, ExtensionManifest, MoonlightBranch } from "@moonlight-mod/types"; 3 4 export type MoonbaseNatives = { 5 checkForMoonlightUpdate(): Promise<string | null>; 6 + updateMoonlight(overrideBranch?: MoonlightBranch): Promise<void>; 7 8 + fetchRepositories(repos: string[]): Promise<Record<string, RepositoryManifest[]>>; 9 + installExtension(manifest: RepositoryManifest, url: string, repo: string): Promise<void>; 10 deleteExtension(id: string): Promise<void>; 11 }; 12 13 export type RepositoryManifest = ExtensionManifest & { ··· 28 state: ExtensionState; 29 compat: ExtensionCompat; 30 hasUpdate: boolean; 31 + changelog?: string; 32 + settingsOverride?: ExtensionManifest["settings"]; 33 }; 34 + 35 + export enum UpdateState { 36 + Ready, 37 + Working, 38 + Installed, 39 + Failed 40 + } 41 + 42 + // Ordered in terms of priority 43 + export enum RestartAdvice { 44 + NotNeeded, // No action is needed 45 + ReloadSuggested, // A reload might be needed 46 + ReloadNeeded, // A reload is needed 47 + RestartNeeded // A restart is needed 48 + }
+36
packages/core-extensions/src/moonbase/webpackModules/ThemeDarkIcon.tsx
···
··· 1 + // RIP to ThemeDarkIcon ????-2025 2 + // <Cynthia> Failed to remap "ThemeDarkIcon" in "discord/components/common/index" 3 + // <NotNite> bro are you fucking kidding me 4 + // <NotNite> that's literally the icon we use for the update banner 5 + 6 + import React from "@moonlight-mod/wp/react"; 7 + import icons from "@moonlight-mod/wp/common_icons"; 8 + import type { IconProps } from "@moonlight-mod/types/coreExtensions/common"; 9 + 10 + export default function ThemeDarkIcon(props?: IconProps) { 11 + const parsed = icons.parseProps(props); 12 + 13 + return ( 14 + <svg 15 + aria-hidden="true" 16 + role="img" 17 + xmlns="http://www.w3.org/2000/svg" 18 + width={parsed.width} 19 + height={parsed.height} 20 + fill="none" 21 + viewBox="0 0 24 24" 22 + > 23 + <path 24 + fill={parsed.fill} 25 + className={parsed.className} 26 + d="M20.52 18.96c.32-.4-.01-.96-.52-.96A11 11 0 0 1 9.77 2.94c.31-.78-.3-1.68-1.1-1.43a11 11 0 1 0 11.85 17.45Z" 27 + /> 28 + 29 + <path 30 + fill={parsed.fill} 31 + className={parsed.className} 32 + d="m17.73 9.27-.76-2.02a.5.5 0 0 0-.94 0l-.76 2.02-2.02.76a.5.5 0 0 0 0 .94l2.02.76.76 2.02a.5.5 0 0 0 .94 0l.76-2.02 2.02-.76a.5.5 0 0 0 0-.94l-2.02-.76ZM19.73 2.62l.45 1.2 1.2.45c.21.08.21.38 0 .46l-1.2.45-.45 1.2a.25.25 0 0 1-.46 0l-.45-1.2-1.2-.45a.25.25 0 0 1 0-.46l1.2-.45.45-1.2a.25.25 0 0 1 .46 0Z" 33 + /> 34 + </svg> 35 + ); 36 + }
+263
packages/core-extensions/src/moonbase/webpackModules/crashScreen.tsx
···
··· 1 + import React from "@moonlight-mod/wp/react"; 2 + import { Button, TabBar } from "@moonlight-mod/wp/discord/components/common/index"; 3 + import { useStateFromStores, useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; 4 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 5 + import { RepositoryManifest, UpdateState } from "../types"; 6 + import { ConfigExtension, DetectedExtension } from "@moonlight-mod/types"; 7 + import DiscoveryClasses from "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css"; 8 + 9 + const MODULE_REGEX = /Webpack-Module\/(\d+)\/(\d+)/g; 10 + 11 + const logger = moonlight.getLogger("moonbase/crashScreen"); 12 + 13 + type ErrorState = { 14 + error: Error; 15 + info: { 16 + componentStack: string; 17 + }; 18 + __moonlight_update?: UpdateState; 19 + }; 20 + 21 + type WrapperProps = { 22 + action: React.ReactNode; 23 + state: ErrorState; 24 + }; 25 + 26 + type UpdateCardProps = { 27 + id: number; 28 + ext: { 29 + version: string; 30 + download: string; 31 + updateManifest: RepositoryManifest; 32 + }; 33 + }; 34 + 35 + const updateStrings: Record<UpdateState, string> = { 36 + [UpdateState.Ready]: "A new version of moonlight is available.", 37 + [UpdateState.Working]: "Updating moonlight...", 38 + [UpdateState.Installed]: "Updated moonlight. Click Reload to apply changes.", 39 + [UpdateState.Failed]: "Failed to update moonlight. Please use the installer." 40 + }; 41 + const buttonStrings: Record<UpdateState, string> = { 42 + [UpdateState.Ready]: "Update moonlight", 43 + [UpdateState.Working]: "Updating moonlight...", 44 + [UpdateState.Installed]: "", 45 + [UpdateState.Failed]: "Update failed" 46 + }; 47 + const extensionButtonStrings: Record<UpdateState, string> = { 48 + [UpdateState.Ready]: "Update", 49 + [UpdateState.Working]: "Updating...", 50 + [UpdateState.Installed]: "Updated", 51 + [UpdateState.Failed]: "Update failed" 52 + }; 53 + 54 + function ExtensionUpdateCard({ id, ext }: UpdateCardProps) { 55 + const [state, setState] = React.useState(UpdateState.Ready); 56 + const installed = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.getExtension(id), [id]); 57 + 58 + return ( 59 + <div className="moonbase-crash-extensionCard"> 60 + <div className="moonbase-crash-extensionCard-meta"> 61 + <div className="moonbase-crash-extensionCard-title"> 62 + {ext.updateManifest.meta?.name ?? ext.updateManifest.id} 63 + </div> 64 + <div className="moonbase-crash-extensionCard-version">{`v${installed?.manifest?.version ?? "???"} -> v${ 65 + ext.version 66 + }`}</div> 67 + </div> 68 + <div className="moonbase-crash-extensionCard-button"> 69 + <Button 70 + color={Button.Colors.GREEN} 71 + disabled={state !== UpdateState.Ready} 72 + onClick={() => { 73 + setState(UpdateState.Working); 74 + MoonbaseSettingsStore.installExtension(id) 75 + .then(() => setState(UpdateState.Installed)) 76 + .catch(() => setState(UpdateState.Failed)); 77 + }} 78 + > 79 + {extensionButtonStrings[state]} 80 + </Button> 81 + </div> 82 + </div> 83 + ); 84 + } 85 + 86 + function ExtensionDisableCard({ ext }: { ext: DetectedExtension }) { 87 + function disableWithDependents() { 88 + const disable = new Set<string>(); 89 + disable.add(ext.id); 90 + for (const [id, dependencies] of moonlightNode.processedExtensions.dependencyGraph) { 91 + if (dependencies?.has(ext.id)) disable.add(id); 92 + } 93 + 94 + const config = structuredClone(moonlightNode.config); 95 + for (const id in config.extensions) { 96 + if (!disable.has(id)) continue; 97 + if (typeof config.extensions[id] === "boolean") config.extensions[id] = false; 98 + else (config.extensions[id] as ConfigExtension).enabled = false; 99 + } 100 + 101 + let msg = `Are you sure you want to disable "${ext.manifest.meta?.name ?? ext.id}"`; 102 + if (disable.size > 1) { 103 + msg += ` and its ${disable.size - 1} dependent${disable.size - 1 === 1 ? "" : "s"}`; 104 + } 105 + msg += "?"; 106 + 107 + if (confirm(msg)) { 108 + moonlightNode.writeConfig(config); 109 + window.location.reload(); 110 + } 111 + } 112 + 113 + return ( 114 + <div className="moonbase-crash-extensionCard"> 115 + <div className="moonbase-crash-extensionCard-meta"> 116 + <div className="moonbase-crash-extensionCard-title">{ext.manifest.meta?.name ?? ext.id}</div> 117 + <div className="moonbase-crash-extensionCard-version">{`v${ext.manifest.version ?? "???"}`}</div> 118 + </div> 119 + <div className="moonbase-crash-extensionCard-button"> 120 + <Button color={Button.Colors.RED} onClick={disableWithDependents}> 121 + Disable 122 + </Button> 123 + </div> 124 + </div> 125 + ); 126 + } 127 + 128 + export function wrapAction({ action, state }: WrapperProps) { 129 + const [tab, setTab] = React.useState("crash"); 130 + 131 + const { updates, updateCount } = useStateFromStoresObject([MoonbaseSettingsStore], () => { 132 + const { updates } = MoonbaseSettingsStore; 133 + return { 134 + updates: Object.entries(updates), 135 + updateCount: Object.keys(updates).length 136 + }; 137 + }); 138 + 139 + const causes = React.useMemo(() => { 140 + const causes = new Set<string>(); 141 + if (state.error.stack) { 142 + for (const [, , id] of state.error.stack.matchAll(MODULE_REGEX)) 143 + for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 144 + } 145 + for (const [, , id] of state.info.componentStack.matchAll(MODULE_REGEX)) 146 + for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 147 + 148 + for (const [path, id] of Object.entries(moonlight.moonmap.modules)) { 149 + const MAPPING_REGEX = new RegExp( 150 + // @ts-expect-error Only Firefox has RegExp.escape 151 + `(${RegExp.escape ? RegExp.escape(path) : path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, 152 + "g" 153 + ); 154 + 155 + if (state.error.stack) { 156 + for (const match of state.error.stack.matchAll(MAPPING_REGEX)) 157 + if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 158 + } 159 + for (const match of state.info.componentStack.matchAll(MAPPING_REGEX)) 160 + if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 161 + } 162 + 163 + return [...causes]; 164 + }, []); 165 + 166 + return ( 167 + <div className="moonbase-crash-wrapper"> 168 + {action} 169 + <TabBar 170 + className={`${DiscoveryClasses.tabBar} moonbase-crash-tabs`} 171 + type="top" 172 + selectedItem={tab} 173 + onItemSelect={(v) => setTab(v)} 174 + > 175 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id="crash"> 176 + Crash details 177 + </TabBar.Item> 178 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id="extensions" disabled={updateCount === 0}> 179 + {`Extension updates (${updateCount})`} 180 + </TabBar.Item> 181 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id="causes" disabled={causes.length === 0}> 182 + {`Possible causes (${causes.length})`} 183 + </TabBar.Item> 184 + </TabBar> 185 + {tab === "crash" ? ( 186 + <div className="moonbase-crash-details-wrapper"> 187 + <pre className="moonbase-crash-details"> 188 + <code> 189 + {state.error.stack} 190 + {"\n\nComponent stack:"} 191 + {state.info.componentStack} 192 + </code> 193 + </pre> 194 + </div> 195 + ) : null} 196 + {tab === "extensions" ? ( 197 + <div className="moonbase-crash-extensions"> 198 + {updates.map(([id, ext]) => ( 199 + <ExtensionUpdateCard id={Number(id)} ext={ext} /> 200 + ))} 201 + </div> 202 + ) : null} 203 + {tab === "causes" ? ( 204 + <div className="moonbase-crash-extensions"> 205 + {causes 206 + .map((ext) => moonlightNode.extensions.find((e) => e.id === ext)!) 207 + .map((ext) => ( 208 + <ExtensionDisableCard ext={ext} /> 209 + ))} 210 + </div> 211 + ) : null} 212 + </div> 213 + ); 214 + } 215 + 216 + export function UpdateText({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) { 217 + if (!state.__moonlight_update) { 218 + setState({ 219 + ...state, 220 + __moonlight_update: UpdateState.Ready 221 + }); 222 + } 223 + const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion); 224 + 225 + return newVersion == null ? null : ( 226 + <p>{state.__moonlight_update !== undefined ? updateStrings[state.__moonlight_update] : ""}</p> 227 + ); 228 + } 229 + 230 + export function UpdateButton({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) { 231 + const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion); 232 + return newVersion == null || 233 + state.__moonlight_update === UpdateState.Installed || 234 + state.__moonlight_update === undefined ? null : ( 235 + <Button 236 + size={Button.Sizes.LARGE} 237 + disabled={state.__moonlight_update !== UpdateState.Ready} 238 + onClick={() => { 239 + setState({ 240 + ...state, 241 + __moonlight_update: UpdateState.Working 242 + }); 243 + 244 + MoonbaseSettingsStore.updateMoonlight() 245 + .then(() => { 246 + setState({ 247 + ...state, 248 + __moonlight_update: UpdateState.Installed 249 + }); 250 + }) 251 + .catch((e) => { 252 + logger.error(e); 253 + setState({ 254 + ...state, 255 + __moonlight_update: UpdateState.Failed 256 + }); 257 + }); 258 + }} 259 + > 260 + {state.__moonlight_update !== undefined ? buttonStrings[state.__moonlight_update] : ""} 261 + </Button> 262 + ); 263 + }
+25 -38
packages/core-extensions/src/moonbase/webpackModules/settings.tsx
··· 1 import settings from "@moonlight-mod/wp/settings_settings"; 2 import React from "@moonlight-mod/wp/react"; 3 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 4 - import { Moonbase, pages } from "@moonlight-mod/wp/moonbase_ui"; 5 - 6 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 7 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 8 - 9 - import Update from "./ui/update"; 10 - 11 - const { MenuItem, Text, Breadcrumbs } = Components; 12 - 13 - const Margins = spacepack.require("discord/styles/shared/Margins.css"); 14 - 15 - const { open } = spacepack.findByExports("setSection", "clearSubsection")[0] 16 - .exports.Z; 17 18 const notice = { 19 stores: [MoonbaseSettingsStore], 20 element: () => { 21 // Require it here because lazy loading SUX 22 - const SettingsNotice = spacepack.findByCode( 23 - "onSaveButtonColor", 24 - "FocusRingScope" 25 - )[0].exports.Z; 26 return ( 27 <SettingsNotice 28 submitting={MoonbaseSettingsStore.submitting} ··· 37 } 38 }; 39 40 - function addSection( 41 - id: string, 42 - name: string, 43 - element: React.FunctionComponent 44 - ) { 45 - settings.addSection(`moonbase-${id}`, name, element, null, -2, notice); 46 } 47 48 // FIXME: move to component types ··· 53 54 function renderBreadcrumb(crumb: Breadcrumb, last: boolean) { 55 return ( 56 - <Text 57 - variant="heading-lg/semibold" 58 - tag="h2" 59 - color={last ? "header-primary" : "header-secondary"} 60 - > 61 {crumb.label} 62 </Text> 63 ); 64 } 65 66 - if ( 67 - MoonbaseSettingsStore.getExtensionConfigRaw<boolean>( 68 - "moonbase", 69 - "sections", 70 - false 71 - ) 72 - ) { 73 - settings.addHeader("Moonbase", -2); 74 75 - for (const page of pages) { 76 addSection(page.id, page.name, () => { 77 const breadcrumbs = [ 78 { id: "moonbase", label: "Moonbase" }, ··· 89 {page.name} 90 </Breadcrumbs> 91 92 <Update /> 93 94 <page.element /> ··· 96 ); 97 }); 98 } 99 } else { 100 - settings.addSection("moonbase", "Moonbase", Moonbase, null, -2, notice); 101 102 settings.addSectionMenuItems( 103 "moonbase", ··· 106 key={page.id} 107 id={`moonbase-${page.id}`} 108 label={page.name} 109 - action={() => open("moonbase", i)} 110 /> 111 )) 112 );
··· 1 import settings from "@moonlight-mod/wp/settings_settings"; 2 import React from "@moonlight-mod/wp/react"; 3 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 4 + import { Moonbase, pages, RestartAdviceMessage, Update } from "@moonlight-mod/wp/moonbase_ui"; 5 + import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators"; 6 + import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css"; 7 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 8 + import { Text, Breadcrumbs } from "@moonlight-mod/wp/discord/components/common/index"; 9 + import { MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu"; 10 11 const notice = { 12 stores: [MoonbaseSettingsStore], 13 element: () => { 14 // Require it here because lazy loading SUX 15 + const SettingsNotice = spacepack.require("discord/components/common/SettingsNotice").default; 16 return ( 17 <SettingsNotice 18 submitting={MoonbaseSettingsStore.submitting} ··· 27 } 28 }; 29 30 + const oldLocation = MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "oldLocation", false); 31 + const position = oldLocation ? -2 : -9999; 32 + 33 + function addSection(id: string, name: string, element: React.FunctionComponent) { 34 + settings.addSection(`moonbase-${id}`, name, element, null, position, notice); 35 } 36 37 // FIXME: move to component types ··· 42 43 function renderBreadcrumb(crumb: Breadcrumb, last: boolean) { 44 return ( 45 + <Text variant="heading-lg/semibold" tag="h2" color={last ? "header-primary" : "header-secondary"}> 46 {crumb.label} 47 </Text> 48 ); 49 } 50 51 + if (!oldLocation) { 52 + settings.addDivider(position); 53 + } 54 + 55 + if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "sections", false)) { 56 + if (oldLocation) settings.addHeader("Moonbase", position); 57 58 + const _pages = oldLocation ? pages : pages.reverse(); 59 + for (const page of _pages) { 60 addSection(page.id, page.name, () => { 61 const breadcrumbs = [ 62 { id: "moonbase", label: "Moonbase" }, ··· 73 {page.name} 74 </Breadcrumbs> 75 76 + <RestartAdviceMessage /> 77 <Update /> 78 79 <page.element /> ··· 81 ); 82 }); 83 } 84 + 85 + if (!oldLocation) settings.addHeader("Moonbase", position); 86 } else { 87 + settings.addSection("moonbase", "Moonbase", Moonbase, null, position, notice); 88 89 settings.addSectionMenuItems( 90 "moonbase", ··· 93 key={page.id} 94 id={`moonbase-${page.id}`} 95 label={page.name} 96 + action={() => UserSettingsModalActionCreators.open("moonbase", i.toString())} 97 /> 98 )) 99 );
+245 -138
packages/core-extensions/src/moonbase/webpackModules/stores.ts
··· 1 - import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; 2 import { 3 ExtensionState, 4 MoonbaseExtension, 5 MoonbaseNatives, 6 - RepositoryManifest 7 } from "../types"; 8 import { Store } from "@moonlight-mod/wp/discord/packages/flux"; 9 import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; 10 import getNatives from "../native"; 11 import { mainRepo } from "@moonlight-mod/types/constants"; 12 - import { 13 - checkExtensionCompat, 14 - ExtensionCompat 15 - } from "@moonlight-mod/core/extension/loader"; 16 import { CustomComponent } from "@moonlight-mod/types/coreExtensions/moonbase"; 17 18 const logger = moonlight.getLogger("moonbase"); 19 ··· 21 if (moonlightNode.isBrowser) natives = getNatives(); 22 23 class MoonbaseSettingsStore extends Store<any> { 24 - private origConfig: Config; 25 private config: Config; 26 private extensionIndex: number; 27 - private configComponents: Record<string, Record<string, CustomComponent>> = 28 - {}; 29 30 modified: boolean; 31 submitting: boolean; 32 installing: boolean; 33 34 newVersion: string | null; 35 shouldShowNotice: boolean; 36 37 extensions: { [id: number]: MoonbaseExtension }; 38 updates: { 39 [id: number]: { ··· 46 constructor() { 47 super(Dispatcher); 48 49 - this.origConfig = moonlightNode.config; 50 - this.config = this.clone(this.origConfig); 51 this.extensionIndex = 0; 52 53 this.modified = false; ··· 64 this.extensions[uniqueId] = { 65 ...ext, 66 uniqueId, 67 - state: moonlight.enabledExtensions.has(ext.id) 68 - ? ExtensionState.Enabled 69 - : ExtensionState.Disabled, 70 compat: checkExtensionCompat(ext.manifest), 71 hasUpdate: false 72 }; 73 } 74 75 - natives! 76 - .fetchRepositories(this.config.repositories) 77 - .then((ret) => { 78 - for (const [repo, exts] of Object.entries(ret)) { 79 - try { 80 - for (const ext of exts) { 81 - const uniqueId = this.extensionIndex++; 82 - const extensionData = { 83 - id: ext.id, 84 - uniqueId, 85 - manifest: ext, 86 - source: { type: ExtensionLoadSource.Normal, url: repo }, 87 - state: ExtensionState.NotDownloaded, 88 - compat: ExtensionCompat.Compatible, 89 - hasUpdate: false 90 - }; 91 92 - // Don't present incompatible updates 93 - if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) 94 - continue; 95 96 - const existing = this.getExisting(extensionData); 97 - if (existing != null) { 98 - // Make sure the download URL is properly updated 99 - for (const [id, e] of Object.entries(this.extensions)) { 100 - if (e.id === ext.id && e.source.url === repo) { 101 - this.extensions[parseInt(id)].manifest = { 102 - ...e.manifest, 103 - download: ext.download 104 - }; 105 - break; 106 - } 107 - } 108 109 - if (this.hasUpdate(extensionData)) { 110 - this.updates[existing.uniqueId] = { 111 - version: ext.version!, 112 - download: ext.download, 113 - updateManifest: ext 114 - }; 115 - existing.hasUpdate = true; 116 - } 117 118 - continue; 119 - } 120 121 - this.extensions[uniqueId] = extensionData; 122 - } 123 - } catch (e) { 124 - logger.error(`Error processing repository ${repo}`, e); 125 } 126 } 127 128 - this.emitChange(); 129 - }) 130 - .then(() => 131 - this.getExtensionConfigRaw("moonbase", "updateChecking", true) 132 - ? natives!.checkForMoonlightUpdate() 133 - : new Promise<null>((resolve) => resolve(null)) 134 - ) 135 - .then((version) => { 136 - this.newVersion = version; 137 - this.emitChange(); 138 - }) 139 - .then(() => { 140 - this.shouldShowNotice = 141 - this.newVersion != null || Object.keys(this.updates).length > 0; 142 - this.emitChange(); 143 - }); 144 } 145 146 private getExisting(ext: MoonbaseExtension) { 147 - return Object.values(this.extensions).find( 148 - (e) => e.id === ext.id && e.source.url === ext.source.url 149 - ); 150 } 151 152 private hasUpdate(ext: MoonbaseExtension) { 153 - const existing = Object.values(this.extensions).find( 154 - (e) => e.id === ext.id && e.source.url === ext.source.url 155 - ); 156 if (existing == null) return false; 157 158 - return ( 159 - existing.manifest.version !== ext.manifest.version && 160 - existing.state !== ExtensionState.NotDownloaded 161 - ); 162 } 163 164 // Jank 165 private isModified() { 166 - const orig = JSON.stringify(this.origConfig); 167 const curr = JSON.stringify(this.config); 168 return orig !== curr; 169 } ··· 172 return this.submitting || this.installing; 173 } 174 175 showNotice() { 176 return this.modified; 177 } ··· 181 } 182 183 getExtensionUniqueId(id: string) { 184 - return Object.values(this.extensions).find((ext) => ext.id === id) 185 - ?.uniqueId; 186 } 187 188 getExtensionConflicting(uniqueId: number) { 189 const ext = this.getExtension(uniqueId); 190 if (ext.state !== ExtensionState.NotDownloaded) return false; 191 return Object.values(this.extensions).some( 192 - (e) => 193 - e.id === ext.id && 194 - e.uniqueId !== uniqueId && 195 - e.state !== ExtensionState.NotDownloaded 196 ); 197 } 198 ··· 215 216 getExtensionConfig<T>(uniqueId: number, key: string): T | undefined { 217 const ext = this.getExtension(uniqueId); 218 - const defaultValue = ext.manifest.settings?.[key]?.default; 219 - const clonedDefaultValue = this.clone(defaultValue); 220 - const cfg = this.config.extensions[ext.id]; 221 - 222 - if (cfg == null || typeof cfg === "boolean") return clonedDefaultValue; 223 - return cfg.config?.[key] ?? clonedDefaultValue; 224 } 225 226 - getExtensionConfigRaw<T>( 227 - id: string, 228 - key: string, 229 - defaultValue: T | undefined 230 - ): T | undefined { 231 const cfg = this.config.extensions[id]; 232 - 233 if (cfg == null || typeof cfg === "boolean") return defaultValue; 234 return cfg.config?.[key] ?? defaultValue; 235 } 236 237 getExtensionConfigName(uniqueId: number, key: string) { 238 const ext = this.getExtension(uniqueId); 239 - return ext.manifest.settings?.[key]?.displayName ?? key; 240 } 241 242 getExtensionConfigDescription(uniqueId: number, key: string) { 243 const ext = this.getExtension(uniqueId); 244 - return ext.manifest.settings?.[key]?.description; 245 } 246 247 setExtensionConfig(id: string, key: string, value: any) { 248 - const oldConfig = this.config.extensions[id]; 249 - const newConfig = 250 - typeof oldConfig === "boolean" 251 - ? { 252 - enabled: oldConfig, 253 - config: { [key]: value } 254 - } 255 - : { 256 - ...oldConfig, 257 - config: { ...(oldConfig?.config ?? {}), [key]: value } 258 - }; 259 - 260 - this.config.extensions[id] = newConfig; 261 this.modified = this.isModified(); 262 this.emitChange(); 263 } ··· 284 this.emitChange(); 285 } 286 287 async installExtension(uniqueId: number) { 288 const ext = this.getExtension(uniqueId); 289 if (!("download" in ext.manifest)) { ··· 299 this.extensions[uniqueId].state = ExtensionState.Disabled; 300 } 301 302 - if (update != null) 303 - this.extensions[uniqueId].compat = checkExtensionCompat( 304 - update.updateManifest 305 - ); 306 307 delete this.updates[uniqueId]; 308 } catch (e) { ··· 310 } 311 312 this.installing = false; 313 this.emitChange(); 314 } 315 ··· 335 336 const deps: Record<string, MoonbaseExtension[]> = {}; 337 for (const dep of missingDeps) { 338 - const candidates = Object.values(this.extensions).filter( 339 - (e) => e.id === dep 340 - ); 341 342 deps[dep] = candidates.sort((a, b) => { 343 const aRank = this.getRank(a); 344 const bRank = this.getRank(b); 345 if (aRank === bRank) { 346 - const repoIndex = this.config.repositories.indexOf(a.source.url!); 347 - const otherRepoIndex = this.config.repositories.indexOf( 348 - b.source.url! 349 - ); 350 return repoIndex - otherRepoIndex; 351 } else { 352 return bRank - aRank; ··· 370 } 371 372 this.installing = false; 373 this.emitChange(); 374 } 375 376 async updateMoonlight() { 377 - await natives.updateMoonlight(); 378 } 379 380 getConfigOption<K extends keyof Config>(key: K): Config[K] { ··· 392 return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id; 393 } 394 395 - registerConfigComponent( 396 - ext: string, 397 - name: string, 398 - component: CustomComponent 399 - ) { 400 if (!(ext in this.configComponents)) this.configComponents[ext] = {}; 401 this.configComponents[ext][name] = component; 402 } ··· 405 return this.configComponents[ext]?.[name]; 406 } 407 408 writeConfig() { 409 this.submitting = true; 410 411 moonlightNode.writeConfig(this.config); 412 - this.origConfig = this.clone(this.config); 413 414 this.submitting = false; 415 this.modified = false; 416 this.emitChange(); 417 } 418 419 reset() { 420 this.submitting = false; 421 this.modified = false; 422 - this.config = this.clone(this.origConfig); 423 this.emitChange(); 424 } 425 426 // Required because electron likes to make it immutable sometimes.
··· 1 + import { Config, ExtensionEnvironment, ExtensionLoadSource, ExtensionSettingsAdvice } from "@moonlight-mod/types"; 2 import { 3 ExtensionState, 4 MoonbaseExtension, 5 MoonbaseNatives, 6 + RepositoryManifest, 7 + RestartAdvice, 8 + UpdateState 9 } from "../types"; 10 import { Store } from "@moonlight-mod/wp/discord/packages/flux"; 11 import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; 12 import getNatives from "../native"; 13 import { mainRepo } from "@moonlight-mod/types/constants"; 14 + import { checkExtensionCompat, ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 15 import { CustomComponent } from "@moonlight-mod/types/coreExtensions/moonbase"; 16 + import { getConfigOption, setConfigOption } from "@moonlight-mod/core/util/config"; 17 + import diff from "microdiff"; 18 19 const logger = moonlight.getLogger("moonbase"); 20 ··· 22 if (moonlightNode.isBrowser) natives = getNatives(); 23 24 class MoonbaseSettingsStore extends Store<any> { 25 + private initialConfig: Config; 26 + private savedConfig: Config; 27 private config: Config; 28 private extensionIndex: number; 29 + private configComponents: Record<string, Record<string, CustomComponent>> = {}; 30 31 modified: boolean; 32 submitting: boolean; 33 installing: boolean; 34 35 + #updateState = UpdateState.Ready; 36 + get updateState() { 37 + return this.#updateState; 38 + } 39 newVersion: string | null; 40 shouldShowNotice: boolean; 41 42 + restartAdvice = RestartAdvice.NotNeeded; 43 + 44 extensions: { [id: number]: MoonbaseExtension }; 45 updates: { 46 [id: number]: { ··· 53 constructor() { 54 super(Dispatcher); 55 56 + this.initialConfig = moonlightNode.config; 57 + this.savedConfig = moonlightNode.config; 58 + this.config = this.clone(this.savedConfig); 59 this.extensionIndex = 0; 60 61 this.modified = false; ··· 72 this.extensions[uniqueId] = { 73 ...ext, 74 uniqueId, 75 + state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled : ExtensionState.Disabled, 76 compat: checkExtensionCompat(ext.manifest), 77 hasUpdate: false 78 }; 79 } 80 81 + this.checkUpdates(); 82 + } 83 84 + async checkUpdates() { 85 + await Promise.all([this.checkExtensionUpdates(), this.checkMoonlightUpdates()]); 86 + this.shouldShowNotice = this.newVersion != null || Object.keys(this.updates).length > 0; 87 + this.emitChange(); 88 + } 89 90 + private async checkExtensionUpdates() { 91 + const repositories = await natives!.fetchRepositories(this.savedConfig.repositories); 92 93 + // Reset update state 94 + for (const id in this.extensions) { 95 + const ext = this.extensions[id]; 96 + ext.hasUpdate = false; 97 + ext.changelog = undefined; 98 + } 99 + this.updates = {}; 100 101 + for (const [repo, exts] of Object.entries(repositories)) { 102 + for (const ext of exts) { 103 + const uniqueId = this.extensionIndex++; 104 + const extensionData = { 105 + id: ext.id, 106 + uniqueId, 107 + manifest: ext, 108 + source: { type: ExtensionLoadSource.Normal, url: repo }, 109 + state: ExtensionState.NotDownloaded, 110 + compat: ExtensionCompat.Compatible, 111 + hasUpdate: false 112 + }; 113 114 + // Don't present incompatible updates 115 + if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue; 116 + 117 + const existing = this.getExisting(extensionData); 118 + if (existing != null) { 119 + // Make sure the download URL is properly updated 120 + existing.manifest = { 121 + ...existing.manifest, 122 + download: ext.download 123 + }; 124 + 125 + if (this.hasUpdate(extensionData)) { 126 + this.updates[existing.uniqueId] = { 127 + version: ext.version!, 128 + download: ext.download, 129 + updateManifest: ext 130 + }; 131 + existing.hasUpdate = true; 132 + existing.changelog = ext.meta?.changelog; 133 } 134 + } else { 135 + this.extensions[uniqueId] = extensionData; 136 } 137 + } 138 + } 139 + } 140 141 + private async checkMoonlightUpdates() { 142 + this.newVersion = this.getExtensionConfigRaw("moonbase", "updateChecking", true) 143 + ? await natives!.checkForMoonlightUpdate() 144 + : null; 145 } 146 147 private getExisting(ext: MoonbaseExtension) { 148 + return Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 149 } 150 151 private hasUpdate(ext: MoonbaseExtension) { 152 + const existing = Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 153 if (existing == null) return false; 154 155 + return existing.manifest.version !== ext.manifest.version && existing.state !== ExtensionState.NotDownloaded; 156 } 157 158 // Jank 159 private isModified() { 160 + const orig = JSON.stringify(this.savedConfig); 161 const curr = JSON.stringify(this.config); 162 return orig !== curr; 163 } ··· 166 return this.submitting || this.installing; 167 } 168 169 + // Required for the settings store contract 170 showNotice() { 171 return this.modified; 172 } ··· 176 } 177 178 getExtensionUniqueId(id: string) { 179 + return Object.values(this.extensions).find((ext) => ext.id === id)?.uniqueId; 180 } 181 182 getExtensionConflicting(uniqueId: number) { 183 const ext = this.getExtension(uniqueId); 184 if (ext.state !== ExtensionState.NotDownloaded) return false; 185 return Object.values(this.extensions).some( 186 + (e) => e.id === ext.id && e.uniqueId !== uniqueId && e.state !== ExtensionState.NotDownloaded 187 ); 188 } 189 ··· 206 207 getExtensionConfig<T>(uniqueId: number, key: string): T | undefined { 208 const ext = this.getExtension(uniqueId); 209 + const settings = ext.settingsOverride ?? ext.manifest.settings; 210 + return getConfigOption(ext.id, key, this.config, settings); 211 } 212 213 + getExtensionConfigRaw<T>(id: string, key: string, defaultValue: T | undefined): T | undefined { 214 const cfg = this.config.extensions[id]; 215 if (cfg == null || typeof cfg === "boolean") return defaultValue; 216 return cfg.config?.[key] ?? defaultValue; 217 } 218 219 getExtensionConfigName(uniqueId: number, key: string) { 220 const ext = this.getExtension(uniqueId); 221 + const settings = ext.settingsOverride ?? ext.manifest.settings; 222 + return settings?.[key]?.displayName ?? key; 223 } 224 225 getExtensionConfigDescription(uniqueId: number, key: string) { 226 const ext = this.getExtension(uniqueId); 227 + const settings = ext.settingsOverride ?? ext.manifest.settings; 228 + return settings?.[key]?.description; 229 } 230 231 setExtensionConfig(id: string, key: string, value: any) { 232 + setConfigOption(this.config, id, key, value); 233 this.modified = this.isModified(); 234 this.emitChange(); 235 } ··· 256 this.emitChange(); 257 } 258 259 + dismissAllExtensionUpdates() { 260 + for (const id in this.extensions) { 261 + this.extensions[id].hasUpdate = false; 262 + } 263 + this.emitChange(); 264 + } 265 + 266 + async updateAllExtensions() { 267 + for (const id of Object.keys(this.updates)) { 268 + try { 269 + await this.installExtension(parseInt(id)); 270 + } catch (e) { 271 + logger.error("Error bulk updating extension", id, e); 272 + } 273 + } 274 + } 275 + 276 async installExtension(uniqueId: number) { 277 const ext = this.getExtension(uniqueId); 278 if (!("download" in ext.manifest)) { ··· 288 this.extensions[uniqueId].state = ExtensionState.Disabled; 289 } 290 291 + if (update != null) { 292 + const existing = this.extensions[uniqueId]; 293 + existing.settingsOverride = update.updateManifest.settings; 294 + existing.compat = checkExtensionCompat(update.updateManifest); 295 + existing.manifest = update.updateManifest; 296 + existing.changelog = update.updateManifest.meta?.changelog; 297 + } 298 299 delete this.updates[uniqueId]; 300 } catch (e) { ··· 302 } 303 304 this.installing = false; 305 + this.restartAdvice = this.#computeRestartAdvice(); 306 this.emitChange(); 307 } 308 ··· 328 329 const deps: Record<string, MoonbaseExtension[]> = {}; 330 for (const dep of missingDeps) { 331 + const candidates = Object.values(this.extensions).filter((e) => e.id === dep); 332 333 deps[dep] = candidates.sort((a, b) => { 334 const aRank = this.getRank(a); 335 const bRank = this.getRank(b); 336 if (aRank === bRank) { 337 + const repoIndex = this.savedConfig.repositories.indexOf(a.source.url!); 338 + const otherRepoIndex = this.savedConfig.repositories.indexOf(b.source.url!); 339 return repoIndex - otherRepoIndex; 340 } else { 341 return bRank - aRank; ··· 359 } 360 361 this.installing = false; 362 + this.restartAdvice = this.#computeRestartAdvice(); 363 this.emitChange(); 364 } 365 366 async updateMoonlight() { 367 + this.#updateState = UpdateState.Working; 368 + this.emitChange(); 369 + 370 + await natives 371 + .updateMoonlight() 372 + .then(() => (this.#updateState = UpdateState.Installed)) 373 + .catch((e) => { 374 + logger.error(e); 375 + this.#updateState = UpdateState.Failed; 376 + }); 377 + 378 + this.emitChange(); 379 } 380 381 getConfigOption<K extends keyof Config>(key: K): Config[K] { ··· 393 return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id; 394 } 395 396 + registerConfigComponent(ext: string, name: string, component: CustomComponent) { 397 if (!(ext in this.configComponents)) this.configComponents[ext] = {}; 398 this.configComponents[ext][name] = component; 399 } ··· 402 return this.configComponents[ext]?.[name]; 403 } 404 405 + #computeRestartAdvice() { 406 + // If moonlight update needs a restart, always hide advice. 407 + if (this.#updateState === UpdateState.Installed) return RestartAdvice.NotNeeded; 408 + 409 + const i = this.initialConfig; // Initial config, from startup 410 + const n = this.config; // New config about to be saved 411 + 412 + let returnedAdvice = RestartAdvice.NotNeeded; 413 + const updateAdvice = (r: RestartAdvice) => (returnedAdvice < r ? (returnedAdvice = r) : returnedAdvice); 414 + 415 + // Top-level keys, repositories is not needed here because Moonbase handles it. 416 + if (i.patchAll !== n.patchAll) updateAdvice(RestartAdvice.ReloadNeeded); 417 + if (i.loggerLevel !== n.loggerLevel) updateAdvice(RestartAdvice.ReloadNeeded); 418 + if (diff(i.devSearchPaths ?? [], n.devSearchPaths ?? [], { cyclesFix: false }).length !== 0) 419 + return updateAdvice(RestartAdvice.RestartNeeded); 420 + 421 + // Extension specific logic 422 + for (const id in n.extensions) { 423 + // Installed extension (might not be detected yet) 424 + const ext = Object.values(this.extensions).find((e) => e.id === id && e.state !== ExtensionState.NotDownloaded); 425 + // Installed and detected extension 426 + const detected = moonlightNode.extensions.find((e) => e.id === id); 427 + 428 + // If it's not installed at all, we don't care 429 + if (!ext) continue; 430 + 431 + const initState = i.extensions[id]; 432 + const newState = n.extensions[id]; 433 + 434 + const newEnabled = typeof newState === "boolean" ? newState : newState.enabled; 435 + // If it's enabled but not detected yet, restart. 436 + if (newEnabled && !detected) { 437 + return updateAdvice(RestartAdvice.RestartNeeded); 438 + } 439 + 440 + // Toggling extensions specifically wants to rely on the initial state, 441 + // that's what was considered when loading extensions. 442 + const initEnabled = initState && (typeof initState === "boolean" ? initState : initState.enabled); 443 + if (initEnabled !== newEnabled || detected?.manifest.version !== ext.manifest.version) { 444 + // If we have the extension locally, we confidently know if it has host/preload scripts. 445 + // If not, we have to respect the environment specified in the manifest. 446 + // If that is the default, we can't know what's needed. 447 + 448 + if (detected?.scripts.hostPath || detected?.scripts.nodePath) { 449 + return updateAdvice(RestartAdvice.RestartNeeded); 450 + } 451 + 452 + switch (ext.manifest.environment) { 453 + case ExtensionEnvironment.Both: 454 + case ExtensionEnvironment.Web: 455 + updateAdvice(RestartAdvice.ReloadNeeded); 456 + continue; 457 + case ExtensionEnvironment.Desktop: 458 + return updateAdvice(RestartAdvice.RestartNeeded); 459 + default: 460 + updateAdvice(RestartAdvice.ReloadNeeded); 461 + continue; 462 + } 463 + } 464 + 465 + const initConfig = typeof initState === "boolean" ? {} : { ...initState?.config }; 466 + const newConfig = typeof newState === "boolean" ? {} : { ...newState?.config }; 467 + 468 + const def = ext.manifest.settings; 469 + if (!def) continue; 470 + 471 + for (const key in def) { 472 + const defaultValue = def[key].default; 473 + 474 + initConfig[key] ??= defaultValue; 475 + newConfig[key] ??= defaultValue; 476 + } 477 + 478 + const changedKeys = diff(initConfig, newConfig, { cyclesFix: false }).map((c) => c.path[0]); 479 + for (const key in def) { 480 + if (!changedKeys.includes(key)) continue; 481 + 482 + const advice = def[key].advice; 483 + switch (advice) { 484 + case ExtensionSettingsAdvice.None: 485 + updateAdvice(RestartAdvice.NotNeeded); 486 + continue; 487 + case ExtensionSettingsAdvice.Reload: 488 + updateAdvice(RestartAdvice.ReloadNeeded); 489 + continue; 490 + case ExtensionSettingsAdvice.Restart: 491 + updateAdvice(RestartAdvice.RestartNeeded); 492 + continue; 493 + default: 494 + updateAdvice(RestartAdvice.ReloadSuggested); 495 + } 496 + } 497 + } 498 + 499 + return returnedAdvice; 500 + } 501 + 502 writeConfig() { 503 this.submitting = true; 504 + this.restartAdvice = this.#computeRestartAdvice(); 505 + const modifiedRepos = diff(this.savedConfig.repositories, this.config.repositories); 506 507 moonlightNode.writeConfig(this.config); 508 + this.savedConfig = this.clone(this.config); 509 510 this.submitting = false; 511 this.modified = false; 512 this.emitChange(); 513 + 514 + if (modifiedRepos.length !== 0) this.checkUpdates(); 515 } 516 517 reset() { 518 this.submitting = false; 519 this.modified = false; 520 + this.config = this.clone(this.savedConfig); 521 this.emitChange(); 522 + } 523 + 524 + restartDiscord() { 525 + if (moonlightNode.isBrowser) { 526 + window.location.reload(); 527 + } else { 528 + // @ts-expect-error TODO: DiscordNative 529 + window.DiscordNative.app.relaunch(); 530 + } 531 } 532 533 // Required because electron likes to make it immutable sometimes.
+47
packages/core-extensions/src/moonbase/webpackModules/ui/HelpMessage.tsx
···
··· 1 + import React from "@moonlight-mod/wp/react"; 2 + import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 3 + import { Text } from "@moonlight-mod/wp/discord/components/common/index"; 4 + import HelpMessageClasses from "@moonlight-mod/wp/discord/components/common/HelpMessage.css"; 5 + import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css"; 6 + 7 + // reimpl of HelpMessage but with a custom icon 8 + export default function HelpMessage({ 9 + className, 10 + text, 11 + icon, 12 + children, 13 + type = "info" 14 + }: { 15 + className?: string; 16 + text: string; 17 + icon: React.ComponentType<any>; 18 + type?: "warning" | "positive" | "error" | "info"; 19 + children?: React.ReactNode; 20 + }) { 21 + return ( 22 + <div 23 + className={`${Margins.marginBottom20} ${HelpMessageClasses[type]} ${HelpMessageClasses.container} moonbase-help-message ${className}`} 24 + > 25 + <Flex direction={Flex.Direction.HORIZONTAL}> 26 + <div 27 + className={HelpMessageClasses.iconDiv} 28 + style={{ 29 + alignItems: "center" 30 + }} 31 + > 32 + {React.createElement(icon, { 33 + size: "sm", 34 + color: "currentColor", 35 + className: HelpMessageClasses.icon 36 + })} 37 + </div> 38 + 39 + <Text variant="text-sm/medium" color="currentColor" className={HelpMessageClasses.text}> 40 + {text} 41 + </Text> 42 + 43 + {children} 44 + </Flex> 45 + </div> 46 + ); 47 + }
+43
packages/core-extensions/src/moonbase/webpackModules/ui/RestartAdvice.tsx
···
··· 1 + import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 2 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 3 + import { Button, CircleWarningIcon } from "@moonlight-mod/wp/discord/components/common/index"; 4 + import React from "@moonlight-mod/wp/react"; 5 + import { RestartAdvice } from "../../types"; 6 + import HelpMessage from "./HelpMessage"; 7 + 8 + const strings: Record<RestartAdvice, string> = { 9 + [RestartAdvice.NotNeeded]: "how did you even", 10 + [RestartAdvice.ReloadSuggested]: "A reload might be needed to apply some of the changed options.", 11 + [RestartAdvice.ReloadNeeded]: "A reload is needed to apply some of the changed options.", 12 + [RestartAdvice.RestartNeeded]: "A restart is needed to apply some of the changed options." 13 + }; 14 + 15 + const buttonStrings: Record<RestartAdvice, string> = { 16 + [RestartAdvice.NotNeeded]: "huh?", 17 + [RestartAdvice.ReloadSuggested]: "Reload", 18 + [RestartAdvice.ReloadNeeded]: "Reload", 19 + [RestartAdvice.RestartNeeded]: "Restart" 20 + }; 21 + 22 + const actions: Record<RestartAdvice, () => void> = { 23 + [RestartAdvice.NotNeeded]: () => {}, 24 + [RestartAdvice.ReloadSuggested]: () => window.location.reload(), 25 + [RestartAdvice.ReloadNeeded]: () => window.location.reload(), 26 + [RestartAdvice.RestartNeeded]: () => MoonbaseSettingsStore.restartDiscord() 27 + }; 28 + 29 + export default function RestartAdviceMessage() { 30 + const restartAdvice = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.restartAdvice); 31 + 32 + if (restartAdvice === RestartAdvice.NotNeeded) return null; 33 + 34 + return ( 35 + <div className="moonbase-help-message-sticky"> 36 + <HelpMessage text={strings[restartAdvice]} icon={CircleWarningIcon} type="warning"> 37 + <Button color={Button.Colors.YELLOW} size={Button.Sizes.TINY} onClick={actions[restartAdvice]}> 38 + {buttonStrings[restartAdvice]} 39 + </Button> 40 + </HelpMessage> 41 + </div> 42 + ); 43 + }
+110
packages/core-extensions/src/moonbase/webpackModules/ui/about.tsx
···
··· 1 + import { 2 + Text, 3 + useThemeContext, 4 + Button, 5 + AngleBracketsIcon, 6 + BookCheckIcon, 7 + ClydeIcon 8 + } from "@moonlight-mod/wp/discord/components/common/index"; 9 + import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 10 + import React from "@moonlight-mod/wp/react"; 11 + import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils"; 12 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 13 + 14 + const wordmark = "https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/heads/main/img/wordmark.png"; 15 + const wordmarkLight = 16 + "https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/heads/main/img/wordmark-light.png"; 17 + 18 + function parse(str: string) { 19 + return MarkupUtils.parse(str, true, { 20 + allowHeading: true, 21 + allowLinks: true, 22 + allowList: true 23 + }); 24 + } 25 + 26 + function Dev({ name, picture, link }: { name: string; picture: string; link: string }) { 27 + return ( 28 + <Button onClick={() => window.open(link)} color={Button.Colors.PRIMARY} className="moonbase-dev"> 29 + <Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap"> 30 + <img src={picture} alt={name} className="moonbase-dev-avatar" /> 31 + 32 + <Text variant="text-md/semibold">{name}</Text> 33 + </Flex> 34 + </Button> 35 + ); 36 + } 37 + 38 + function IconButton({ 39 + text, 40 + link, 41 + icon, 42 + openInClient 43 + }: { 44 + text: string; 45 + link: string; 46 + icon: React.FC<any>; 47 + openInClient?: boolean; 48 + }) { 49 + return ( 50 + <Button 51 + onClick={() => { 52 + if (openInClient) { 53 + try { 54 + const { handleClick } = spacepack.require("discord/utils/MaskedLinkUtils"); 55 + handleClick({ href: link }); 56 + } catch { 57 + window.open(link); 58 + } 59 + } else { 60 + // Will open externally in the user's browser 61 + window.open(link); 62 + } 63 + }} 64 + > 65 + <Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap"> 66 + {React.createElement(icon, { 67 + size: "sm", 68 + color: "currentColor" 69 + })} 70 + {text} 71 + </Flex> 72 + </Button> 73 + ); 74 + } 75 + 76 + export default function AboutPage() { 77 + const darkTheme = useThemeContext()?.theme !== "light"; 78 + 79 + return ( 80 + <Flex direction={Flex.Direction.VERTICAL} align={Flex.Align.CENTER} className="moonbase-about-page"> 81 + <img src={darkTheme ? wordmarkLight : wordmark} alt="moonlight wordmark" className="moonbase-wordmark" /> 82 + 83 + <Text variant="heading-lg/medium">created by:</Text> 84 + <div className="moonbase-devs"> 85 + <Dev name="Cynosphere" picture="https://github.com/Cynosphere.png" link="https://github.com/Cynosphere" /> 86 + <Dev name="NotNite" picture="https://github.com/NotNite.png" link="https://github.com/NotNite" /> 87 + <Dev name="adryd" picture="https://github.com/adryd325.png" link="https://github.com/adryd325" /> 88 + <Dev name="redstonekasi" picture="https://github.com/redstonekasi.png" link="https://github.com/redstonekasi" /> 89 + </div> 90 + 91 + <Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap"> 92 + <IconButton text="View source" icon={AngleBracketsIcon} link="https://github.com/moonlight-mod/moonlight" /> 93 + <IconButton text="Open the docs" icon={BookCheckIcon} link="https://moonlight-mod.github.io/" /> 94 + <IconButton text="Join the server" icon={ClydeIcon} link="https://discord.gg/FdZBTFCP6F" openInClient={true} /> 95 + </Flex> 96 + 97 + <Flex direction={Flex.Direction.VERTICAL} align={Flex.Align.START}> 98 + <Text variant="text-sm/normal"> 99 + {parse(`moonlight \`${window.moonlight.version}\` on \`${window.moonlight.branch}\``)} 100 + </Text> 101 + 102 + <Text variant="text-sm/normal"> 103 + {parse( 104 + "moonlight is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.html) (`LGPL-3.0-or-later`)." 105 + )} 106 + </Text> 107 + </Flex> 108 + </Flex> 109 + ); 110 + }
+18 -43
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
··· 1 import { LogLevel } from "@moonlight-mod/types"; 2 3 - const logLevels = Object.values(LogLevel).filter( 4 - (v) => typeof v === "string" 5 - ) as string[]; 6 7 import React from "@moonlight-mod/wp/react"; 8 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; ··· 18 Clickable 19 } from "@moonlight-mod/wp/discord/components/common/index"; 20 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 21 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 22 23 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 24 25 - const FormClasses = spacepack.findByCode("dividerDefault:")[0].exports; 26 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 27 - 28 - let RemoveButtonClasses: any; 29 spacepack 30 .lazyLoad( 31 "renderArtisanalHack", ··· 34 ) 35 .then( 36 () => 37 - (RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0] 38 - .exports) 39 ); 40 41 - // FIXME: type component keys 42 - const { CircleXIcon } = Components; 43 - 44 function RemoveEntryButton({ onClick }: { onClick: () => void }) { 45 return ( 46 - <div className={RemoveButtonClasses.removeButtonContainer}> 47 <Tooltip text="Remove entry" position="top"> 48 {(props: any) => ( 49 - <Clickable 50 - {...props} 51 - className={RemoveButtonClasses.removeButton} 52 - onClick={onClick} 53 - > 54 <CircleXIcon width={24} height={24} /> 55 </Clickable> 56 )} ··· 59 ); 60 } 61 62 - function ArrayFormItem({ 63 - config 64 - }: { 65 - config: "repositories" | "devSearchPaths"; 66 - }) { 67 const items = MoonbaseSettingsStore.getConfigOption(config) ?? []; 68 return ( 69 <Flex ··· 123 <> 124 <FormSwitch 125 className={Margins.marginTop20} 126 - value={MoonbaseSettingsStore.getExtensionConfigRaw<boolean>( 127 - "moonbase", 128 - "updateChecking", 129 - true 130 - )} 131 onChange={(value: boolean) => { 132 - MoonbaseSettingsStore.setExtensionConfig( 133 - "moonbase", 134 - "updateChecking", 135 - value 136 - ); 137 }} 138 note="Checks for updates to moonlight" 139 > 140 Automatic update checking 141 </FormSwitch> 142 <FormItem title="Repositories"> 143 - <FormText className={Margins.marginBottom4}> 144 - A list of remote repositories to display extensions from 145 - </FormText> 146 <ArrayFormItem config="repositories" /> 147 </FormItem> 148 - <FormDivider className={FormClasses.dividerDefault} /> 149 <FormItem title="Extension search paths" className={Margins.marginTop20}> 150 <FormText className={Margins.marginBottom4}> 151 A list of local directories to search for built extensions 152 </FormText> 153 <ArrayFormItem config="devSearchPaths" /> 154 </FormItem> 155 - <FormDivider className={FormClasses.dividerDefault} /> 156 <FormSwitch 157 className={Margins.marginTop20} 158 - value={MoonbaseSettingsStore.getConfigOption("patchAll")} 159 onChange={(value: boolean) => { 160 MoonbaseSettingsStore.setConfigOption("patchAll", value); 161 }} ··· 172 value: o.toLowerCase(), 173 label: o[0] + o.slice(1).toLowerCase() 174 }))} 175 - onChange={(v) => 176 - MoonbaseSettingsStore.setConfigOption("loggerLevel", v) 177 - } 178 /> 179 </FormItem> 180 </>
··· 1 import { LogLevel } from "@moonlight-mod/types"; 2 3 + const logLevels = Object.values(LogLevel).filter((v) => typeof v === "string") as string[]; 4 5 import React from "@moonlight-mod/wp/react"; 6 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; ··· 16 Clickable 17 } from "@moonlight-mod/wp/discord/components/common/index"; 18 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 19 + import { CircleXIcon } from "@moonlight-mod/wp/discord/components/common/index"; 20 + import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css"; 21 + import FormSwitchClasses from "@moonlight-mod/wp/discord/components/common/FormSwitch.css"; 22 23 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 24 25 + let GuildSettingsRoleEditClasses: any; 26 spacepack 27 .lazyLoad( 28 "renderArtisanalHack", ··· 31 ) 32 .then( 33 () => 34 + (GuildSettingsRoleEditClasses = spacepack.require( 35 + "discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css" 36 + )) 37 ); 38 39 function RemoveEntryButton({ onClick }: { onClick: () => void }) { 40 return ( 41 + <div className={GuildSettingsRoleEditClasses.removeButtonContainer}> 42 <Tooltip text="Remove entry" position="top"> 43 {(props: any) => ( 44 + <Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}> 45 <CircleXIcon width={24} height={24} /> 46 </Clickable> 47 )} ··· 50 ); 51 } 52 53 + function ArrayFormItem({ config }: { config: "repositories" | "devSearchPaths" }) { 54 const items = MoonbaseSettingsStore.getConfigOption(config) ?? []; 55 return ( 56 <Flex ··· 110 <> 111 <FormSwitch 112 className={Margins.marginTop20} 113 + value={MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "updateChecking", true) ?? true} 114 onChange={(value: boolean) => { 115 + MoonbaseSettingsStore.setExtensionConfig("moonbase", "updateChecking", value); 116 }} 117 note="Checks for updates to moonlight" 118 > 119 Automatic update checking 120 </FormSwitch> 121 <FormItem title="Repositories"> 122 + <FormText className={Margins.marginBottom4}>A list of remote repositories to display extensions from</FormText> 123 <ArrayFormItem config="repositories" /> 124 </FormItem> 125 + <FormDivider className={FormSwitchClasses.dividerDefault} /> 126 <FormItem title="Extension search paths" className={Margins.marginTop20}> 127 <FormText className={Margins.marginBottom4}> 128 A list of local directories to search for built extensions 129 </FormText> 130 <ArrayFormItem config="devSearchPaths" /> 131 </FormItem> 132 + <FormDivider className={FormSwitchClasses.dividerDefault} /> 133 <FormSwitch 134 className={Margins.marginTop20} 135 + value={MoonbaseSettingsStore.getConfigOption("patchAll") ?? false} 136 onChange={(value: boolean) => { 137 MoonbaseSettingsStore.setConfigOption("patchAll", value); 138 }} ··· 149 value: o.toLowerCase(), 150 label: o[0] + o.slice(1).toLowerCase() 151 }))} 152 + onChange={(v) => MoonbaseSettingsStore.setConfigOption("loggerLevel", v)} 153 /> 154 </FormItem> 155 </>
+253 -194
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
··· 1 import { ExtensionState } from "../../../types"; 2 - import { ExtensionLoadSource } from "@moonlight-mod/types"; 3 import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 4 - 5 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 6 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 7 import React from "@moonlight-mod/wp/react"; 8 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 9 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 10 import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils"; 11 - import IntegrationCard from "@moonlight-mod/wp/discord/modules/guild_settings/IntegrationCard.css"; 12 - 13 import ExtensionInfo from "./info"; 14 import Settings from "./settings"; 15 - import installWithDependencyPopup from "./popup"; 16 17 export enum ExtensionPage { 18 Info, 19 Description, 20 Settings 21 } 22 23 - import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 24 - 25 - const { BeakerIcon, DownloadIcon, TrashIcon, CircleWarningIcon, Tooltip } = 26 - Components; 27 - 28 - const PanelButton = spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.Z; 29 - const TabBarClasses = spacepack.findByExports( 30 - "tabBar", 31 - "tabBarItem", 32 - "headerContentWrapper" 33 - )[0].exports; 34 - const MarkupClasses = spacepack.findByExports("markup", "inlineFormat")[0] 35 - .exports; 36 - 37 - const BuildOverrideClasses = spacepack.findByExports( 38 - "disabledButtonOverride" 39 - )[0].exports; 40 - 41 const COMPAT_TEXT_MAP: Record<ExtensionCompat, string> = { 42 [ExtensionCompat.Compatible]: "huh?", 43 [ExtensionCompat.InvalidApiLevel]: "Incompatible API level", 44 [ExtensionCompat.InvalidEnvironment]: "Incompatible platform" 45 }; 46 - 47 - export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { 48 - const [tab, setTab] = React.useState(ExtensionPage.Info); 49 - const [restartNeeded, setRestartNeeded] = React.useState(false); 50 51 - const { ext, enabled, busy, update, conflicting } = useStateFromStores( 52 - [MoonbaseSettingsStore], 53 - () => { 54 - return { 55 - ext: MoonbaseSettingsStore.getExtension(uniqueId), 56 - enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId), 57 - busy: MoonbaseSettingsStore.busy, 58 - update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId), 59 - conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId) 60 - }; 61 - } 62 ); 63 64 - // Why it work like that :sob: 65 - if (ext == null) return <></>; 66 67 - const { Card, Text, FormSwitch, TabBar, Button } = Components; 68 69 const tagline = ext.manifest?.meta?.tagline; 70 - const settings = ext.manifest?.settings; 71 const description = ext.manifest?.meta?.description; 72 const enabledDependants = useStateFromStores([MoonbaseSettingsStore], () => 73 Object.keys(MoonbaseSettingsStore.extensions) 74 .filter((uniqueId) => { 75 - const potentialDependant = MoonbaseSettingsStore.getExtension( 76 - parseInt(uniqueId) 77 - ); 78 79 return ( 80 - potentialDependant.manifest.dependencies?.includes(ext.id) && 81 MoonbaseSettingsStore.getExtensionEnabled(parseInt(uniqueId)) 82 ); 83 }) ··· 85 ); 86 const implicitlyEnabled = enabledDependants.length > 0; 87 88 - return ( 89 - <Card editable={true} className={IntegrationCard.card}> 90 - <div className={IntegrationCard.cardHeader}> 91 <Flex direction={Flex.Direction.VERTICAL}> 92 <Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER}> 93 - <Text variant="text-md/semibold"> 94 - {ext.manifest?.meta?.name ?? ext.id} 95 - </Text> 96 {ext.source.type === ExtensionLoadSource.Developer && ( 97 <Tooltip text="This is a local extension" position="top"> 98 - {(props: any) => ( 99 - <BeakerIcon 100 - {...props} 101 - class={BuildOverrideClasses.infoIcon} 102 - size="xs" 103 - /> 104 - )} 105 </Tooltip> 106 )} 107 </Flex> 108 109 - {tagline != null && ( 110 - <Text variant="text-sm/normal">{MarkupUtils.parse(tagline)}</Text> 111 - )} 112 </Flex> 113 114 - <Flex 115 - direction={Flex.Direction.HORIZONTAL} 116 - align={Flex.Align.END} 117 - justify={Flex.Justify.END} 118 - > 119 - {ext.state === ExtensionState.NotDownloaded ? ( 120 - <Tooltip 121 - text={COMPAT_TEXT_MAP[ext.compat]} 122 - shouldShow={ext.compat !== ExtensionCompat.Compatible} 123 - > 124 - {(props: any) => ( 125 - <Button 126 - {...props} 127 - color={Button.Colors.BRAND} 128 - submitting={busy} 129 - disabled={ 130 - ext.compat !== ExtensionCompat.Compatible || conflicting 131 } 132 - onClick={async () => { 133 - await installWithDependencyPopup(uniqueId); 134 }} 135 - > 136 - Install 137 - </Button> 138 - )} 139 - </Tooltip> 140 - ) : ( 141 - <div 142 - // too lazy to learn how <Flex /> works lmao 143 style={{ 144 - display: "flex", 145 - alignItems: "center", 146 - gap: "1rem" 147 }} 148 > 149 - {ext.source.type === ExtensionLoadSource.Normal && ( 150 - <PanelButton 151 - icon={TrashIcon} 152 - tooltipText="Delete" 153 - onClick={() => { 154 - MoonbaseSettingsStore.deleteExtension(uniqueId); 155 - }} 156 - /> 157 )} 158 159 - {update != null && ( 160 - <PanelButton 161 - icon={DownloadIcon} 162 - tooltipText="Update" 163 - onClick={() => { 164 - MoonbaseSettingsStore.installExtension(uniqueId); 165 - }} 166 - /> 167 )} 168 169 - {restartNeeded && ( 170 - <PanelButton 171 - icon={() => ( 172 - <CircleWarningIcon 173 - color={Components.tokens.colors.STATUS_DANGER} 174 - /> 175 - )} 176 - onClick={() => window.location.reload()} 177 - tooltipText="You will need to reload/restart your client for this extension to work properly." 178 - /> 179 )} 180 - 181 - <FormSwitch 182 - value={ 183 - ext.compat === ExtensionCompat.Compatible && 184 - (enabled || implicitlyEnabled) 185 - } 186 - disabled={ 187 - implicitlyEnabled || ext.compat !== ExtensionCompat.Compatible 188 - } 189 - hideBorder={true} 190 - style={{ marginBottom: "0px" }} 191 - tooltipNote={ 192 - ext.compat !== ExtensionCompat.Compatible 193 - ? COMPAT_TEXT_MAP[ext.compat] 194 - : implicitlyEnabled 195 - ? `This extension is a dependency of the following enabled extension${ 196 - enabledDependants.length > 1 ? "s" : "" 197 - }: ${enabledDependants 198 - .map((a) => a.manifest.meta?.name ?? a.id) 199 - .join(", ")}` 200 - : undefined 201 - } 202 - onChange={() => { 203 - setRestartNeeded(true); 204 - MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled); 205 - }} 206 - /> 207 - </div> 208 - )} 209 - </Flex> 210 - </div> 211 212 - <div> 213 - {(description != null || settings != null) && ( 214 - <TabBar 215 - selectedItem={tab} 216 - type="top" 217 - onItemSelect={setTab} 218 - className={TabBarClasses.tabBar} 219 - style={{ 220 - padding: "0 20px" 221 - }} 222 - > 223 - <TabBar.Item 224 - className={TabBarClasses.tabBarItem} 225 - id={ExtensionPage.Info} 226 > 227 - Info 228 - </TabBar.Item> 229 - 230 - {description != null && ( 231 - <TabBar.Item 232 - className={TabBarClasses.tabBarItem} 233 - id={ExtensionPage.Description} 234 - > 235 - Description 236 - </TabBar.Item> 237 - )} 238 - 239 - {settings != null && ( 240 - <TabBar.Item 241 - className={TabBarClasses.tabBarItem} 242 - id={ExtensionPage.Settings} 243 - > 244 - Settings 245 - </TabBar.Item> 246 - )} 247 - </TabBar> 248 )} 249 250 <Flex 251 justify={Flex.Justify.START} 252 wrap={Flex.Wrap.WRAP} 253 style={{ 254 - padding: "16px 16px" 255 }} 256 > 257 - {tab === ExtensionPage.Info && <ExtensionInfo ext={ext} />} 258 {tab === ExtensionPage.Description && ( 259 - <Text 260 - variant="text-md/normal" 261 - class={MarkupClasses.markup} 262 - style={{ width: "100%" }} 263 - > 264 {MarkupUtils.parse(description ?? "*No description*", true, { 265 allowHeading: true, 266 allowLinks: true, ··· 268 })} 269 </Text> 270 )} 271 - {tab === ExtensionPage.Settings && <Settings ext={ext} />} 272 </Flex> 273 </div> 274 </Card>
··· 1 import { ExtensionState } from "../../../types"; 2 + import { constants, ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types"; 3 + 4 import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 5 + import { 6 + ScienceIcon, 7 + DownloadIcon, 8 + TrashIcon, 9 + AngleBracketsIcon, 10 + Tooltip, 11 + Card, 12 + Text, 13 + FormSwitch, 14 + TabBar, 15 + Button, 16 + ChannelListIcon, 17 + HeartIcon, 18 + WindowTopOutlineIcon, 19 + WarningIcon 20 + } from "@moonlight-mod/wp/discord/components/common/index"; 21 import React from "@moonlight-mod/wp/react"; 22 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 23 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 24 import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils"; 25 + import AppCardClasses from "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCard.css"; 26 + import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton"; 27 + import DiscoveryClasses from "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css"; 28 + import MarkupClasses from "@moonlight-mod/wp/discord/modules/messages/web/Markup.css"; 29 + import BuildOverrideClasses from "@moonlight-mod/wp/discord/modules/build_overrides/web/BuildOverride.css"; 30 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 31 + import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary"; 32 import ExtensionInfo from "./info"; 33 import Settings from "./settings"; 34 + import { doGenericExtensionPopup, doMissingExtensionPopup } from "./popup"; 35 36 export enum ExtensionPage { 37 Info, 38 Description, 39 + Changelog, 40 Settings 41 } 42 43 const COMPAT_TEXT_MAP: Record<ExtensionCompat, string> = { 44 [ExtensionCompat.Compatible]: "huh?", 45 [ExtensionCompat.InvalidApiLevel]: "Incompatible API level", 46 [ExtensionCompat.InvalidEnvironment]: "Incompatible platform" 47 }; 48 + const CONFLICTING_TEXT = "This extension is already installed from another source."; 49 50 + function PanelLinkButton({ icon, tooltip, link }: { icon: React.ReactNode; tooltip: string; link: string }) { 51 + return ( 52 + <PanelButton 53 + icon={icon} 54 + tooltipText={tooltip} 55 + onClick={() => { 56 + window.open(link); 57 + }} 58 + /> 59 ); 60 + } 61 62 + export default function ExtensionCard({ uniqueId, selectTag }: { uniqueId: number; selectTag: (tag: string) => void }) { 63 + const { ext, enabled, busy, update, conflicting } = useStateFromStores([MoonbaseSettingsStore], () => { 64 + return { 65 + ext: MoonbaseSettingsStore.getExtension(uniqueId), 66 + enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId), 67 + busy: MoonbaseSettingsStore.busy, 68 + update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId), 69 + conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId) 70 + }; 71 + }); 72 73 + const [tab, setTab] = React.useState( 74 + update != null && ext?.changelog != null ? ExtensionPage.Changelog : ExtensionPage.Info 75 + ); 76 77 const tagline = ext.manifest?.meta?.tagline; 78 + const settings = ext.settingsOverride ?? ext.manifest?.settings; 79 const description = ext.manifest?.meta?.description; 80 + const changelog = ext.changelog; 81 + const linkButtons = [ 82 + ext?.manifest?.meta?.source && ( 83 + <PanelLinkButton icon={<AngleBracketsIcon />} tooltip="View source" link={ext.manifest.meta.source} /> 84 + ), 85 + ext?.source?.url && <PanelLinkButton icon={<ChannelListIcon />} tooltip="View repository" link={ext.source.url} />, 86 + ext?.manifest?.meta?.donate && ( 87 + <PanelLinkButton icon={<HeartIcon />} tooltip="Donate" link={ext.manifest.meta.donate} /> 88 + ) 89 + ].filter((x) => x != null); 90 + 91 const enabledDependants = useStateFromStores([MoonbaseSettingsStore], () => 92 Object.keys(MoonbaseSettingsStore.extensions) 93 .filter((uniqueId) => { 94 + const potentialDependant = MoonbaseSettingsStore.getExtension(parseInt(uniqueId)); 95 96 return ( 97 + potentialDependant.manifest.dependencies?.includes(ext?.id) && 98 MoonbaseSettingsStore.getExtensionEnabled(parseInt(uniqueId)) 99 ); 100 }) ··· 102 ); 103 const implicitlyEnabled = enabledDependants.length > 0; 104 105 + const hasDuplicateEntry = useStateFromStores([MoonbaseSettingsStore], () => 106 + Object.entries(MoonbaseSettingsStore.extensions).some( 107 + ([otherUniqueId, otherExt]) => 108 + otherExt != null && otherExt?.id === ext?.id && parseInt(otherUniqueId) !== uniqueId 109 + ) 110 + ); 111 + 112 + return ext == null ? ( 113 + <></> 114 + ) : ( 115 + <Card editable={true} className={AppCardClasses.card}> 116 + <div className={AppCardClasses.cardHeader}> 117 <Flex direction={Flex.Direction.VERTICAL}> 118 <Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER}> 119 + <Text variant="text-md/semibold">{ext.manifest?.meta?.name ?? ext.id}</Text> 120 {ext.source.type === ExtensionLoadSource.Developer && ( 121 <Tooltip text="This is a local extension" position="top"> 122 + {(props: any) => <ScienceIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />} 123 + </Tooltip> 124 + )} 125 + 126 + {hasDuplicateEntry && ext?.source?.url && ( 127 + <Tooltip text={`This extension is from the following repository: ${ext.source.url}`} position="top"> 128 + {(props: any) => <WindowTopOutlineIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />} 129 + </Tooltip> 130 + )} 131 + 132 + {ext.manifest?.meta?.deprecated && ( 133 + <Tooltip text="This extension is deprecated" position="top"> 134 + {(props: any) => <WarningIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />} 135 </Tooltip> 136 )} 137 </Flex> 138 139 + {tagline != null && <Text variant="text-sm/normal">{MarkupUtils.parse(tagline)}</Text>} 140 </Flex> 141 142 + <Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.END} justify={Flex.Justify.END}> 143 + <div 144 + // too lazy to learn how <Flex /> works lmao 145 + style={{ 146 + display: "flex", 147 + alignItems: "center", 148 + gap: "1rem" 149 + }} 150 + > 151 + {ext.state === ExtensionState.NotDownloaded ? ( 152 + <Tooltip 153 + text={conflicting ? CONFLICTING_TEXT : COMPAT_TEXT_MAP[ext.compat]} 154 + shouldShow={conflicting || ext.compat !== ExtensionCompat.Compatible} 155 + > 156 + {(props: any) => ( 157 + <Button 158 + {...props} 159 + color={Button.Colors.BRAND} 160 + submitting={busy} 161 + disabled={ext.compat !== ExtensionCompat.Compatible || conflicting} 162 + onClick={async () => { 163 + await MoonbaseSettingsStore.installExtension(uniqueId); 164 + const deps = await MoonbaseSettingsStore.getDependencies(uniqueId); 165 + if (deps != null) { 166 + await doMissingExtensionPopup(deps); 167 + } 168 + 169 + // Don't auto enable dangerous extensions 170 + if (!ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) { 171 + MoonbaseSettingsStore.setExtensionEnabled(uniqueId, true); 172 + } 173 + }} 174 + > 175 + Install 176 + </Button> 177 + )} 178 + </Tooltip> 179 + ) : ( 180 + <> 181 + {ext.source.type === ExtensionLoadSource.Normal && ( 182 + <PanelButton 183 + icon={TrashIcon} 184 + tooltipText="Delete" 185 + onClick={() => { 186 + MoonbaseSettingsStore.deleteExtension(uniqueId); 187 + }} 188 + /> 189 + )} 190 + 191 + {update != null && ( 192 + <PanelButton 193 + icon={DownloadIcon} 194 + tooltipText="Update" 195 + onClick={() => { 196 + MoonbaseSettingsStore.installExtension(uniqueId); 197 + }} 198 + /> 199 + )} 200 + 201 + <FormSwitch 202 + value={ext.compat === ExtensionCompat.Compatible && (enabled || implicitlyEnabled)} 203 + disabled={implicitlyEnabled || ext.compat !== ExtensionCompat.Compatible} 204 + hideBorder={true} 205 + style={{ marginBottom: "0px" }} 206 + // @ts-expect-error fix type later 207 + tooltipNote={ 208 + ext.compat !== ExtensionCompat.Compatible ? ( 209 + COMPAT_TEXT_MAP[ext.compat] 210 + ) : implicitlyEnabled ? ( 211 + <div style={{ display: "flex", flexDirection: "column" }}> 212 + <div>{`This extension is a dependency of the following enabled extension${ 213 + enabledDependants.length > 1 ? "s" : "" 214 + }:`}</div> 215 + {enabledDependants.map((dep) => ( 216 + <div>{"โ€ข " + (dep.manifest.meta?.name ?? dep.id)}</div> 217 + ))} 218 + </div> 219 + ) : undefined 220 } 221 + onChange={() => { 222 + const toggle = () => { 223 + MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled); 224 + }; 225 + 226 + if (enabled && constants.builtinExtensions.includes(ext.id)) { 227 + doGenericExtensionPopup( 228 + "Built in extension", 229 + "This extension is enabled by default. Disabling it might have consequences. Are you sure you want to disable it?", 230 + uniqueId, 231 + toggle 232 + ); 233 + } else if (!enabled && ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) { 234 + doGenericExtensionPopup( 235 + "Dangerous extension", 236 + "This extension is marked as dangerous. Enabling it might have consequences. Are you sure you want to enable it?", 237 + uniqueId, 238 + toggle 239 + ); 240 + } else { 241 + toggle(); 242 + } 243 }} 244 + /> 245 + </> 246 + )} 247 + </div> 248 + </Flex> 249 + </div> 250 + 251 + <div> 252 + {(description != null || changelog != null || settings != null || linkButtons.length > 0) && ( 253 + <Flex> 254 + <TabBar 255 + selectedItem={tab} 256 + type="top" 257 + onItemSelect={setTab} 258 + className={DiscoveryClasses.tabBar} 259 style={{ 260 + padding: "0 20px" 261 }} 262 > 263 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Info}> 264 + Info 265 + </TabBar.Item> 266 + 267 + {description != null && ( 268 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Description}> 269 + Description 270 + </TabBar.Item> 271 )} 272 273 + {changelog != null && ( 274 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Changelog}> 275 + Changelog 276 + </TabBar.Item> 277 )} 278 279 + {settings != null && ( 280 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Settings}> 281 + Settings 282 + </TabBar.Item> 283 )} 284 + </TabBar> 285 286 + <Flex 287 + align={Flex.Align.CENTER} 288 + justify={Flex.Justify.END} 289 + direction={Flex.Direction.HORIZONTAL} 290 + grow={1} 291 + className="moonbase-link-buttons" 292 > 293 + {linkButtons.length > 0 && linkButtons} 294 + </Flex> 295 + </Flex> 296 )} 297 298 <Flex 299 justify={Flex.Justify.START} 300 wrap={Flex.Wrap.WRAP} 301 style={{ 302 + padding: "16px 16px", 303 + // This looks wonky in the settings tab 304 + rowGap: tab === ExtensionPage.Info ? "16px" : undefined 305 }} 306 > 307 + {tab === ExtensionPage.Info && <ExtensionInfo ext={ext} selectTag={selectTag} />} 308 {tab === ExtensionPage.Description && ( 309 + <Text variant="text-md/normal" className={MarkupClasses.markup} style={{ width: "100%" }}> 310 {MarkupUtils.parse(description ?? "*No description*", true, { 311 allowHeading: true, 312 allowLinks: true, ··· 314 })} 315 </Text> 316 )} 317 + {tab === ExtensionPage.Changelog && ( 318 + <Text variant="text-md/normal" className={MarkupClasses.markup} style={{ width: "100%" }}> 319 + {MarkupUtils.parse(changelog ?? "*No changelog*", true, { 320 + allowHeading: true, 321 + allowLinks: true, 322 + allowList: true 323 + })} 324 + </Text> 325 + )} 326 + {tab === ExtensionPage.Settings && ( 327 + <ErrorBoundary> 328 + <Settings ext={ext} /> 329 + </ErrorBoundary> 330 + )} 331 </Flex> 332 </div> 333 </Card>
+109 -117
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
··· 1 import { tagNames } from "./info"; 2 - 3 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 4 import * as React from "@moonlight-mod/wp/react"; 5 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; ··· 11 Popout, 12 Dialog, 13 Menu, 14 - MenuGroup, 15 - MenuCheckboxItem, 16 - MenuItem 17 } from "@moonlight-mod/wp/discord/components/common/index"; 18 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 19 20 export enum Filter { 21 Core = 1 << 0, ··· 25 Disabled = 1 << 4, 26 Installed = 1 << 5, 27 Repository = 1 << 6, 28 - Incompatible = 1 << 7 29 } 30 export const defaultFilter = 127 as Filter; 31 32 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 33 - const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0] 34 - .exports; 35 - 36 - let FilterDialogClasses: any; 37 - let FilterBarClasses: any; 38 spacepack 39 - .lazyLoad( 40 - '"Missing channel in Channel.openChannelContextMenu"', 41 - /e\("(\d+)"\)/g, 42 - /webpackId:(\d+?),/ 43 - ) 44 .then(() => { 45 - FilterBarClasses = spacepack.findByCode("tagsButtonWithCount:")[0].exports; 46 - FilterDialogClasses = spacepack.findByCode( 47 - "countContainer:", 48 - "tagContainer:" 49 - )[0].exports; 50 }); 51 52 - const TagItem = spacepack.findByCode(".FORUM_TAG_A11Y_FILTER_BY_TAG")[0].exports 53 - .Z; 54 - 55 - // FIXME: type component keys 56 - const { ChevronSmallDownIcon, ChevronSmallUpIcon, ArrowsUpDownIcon } = 57 - Components; 58 - 59 - function toggleTag( 60 - selectedTags: Set<string>, 61 - setSelectedTags: (tags: Set<string>) => void, 62 - tag: string 63 - ) { 64 const newState = new Set(selectedTags); 65 if (newState.has(tag)) newState.delete(tag); 66 else newState.add(tag); ··· 76 setFilter: (filter: Filter) => void; 77 closePopout: () => void; 78 }) { 79 - const toggleFilter = (set: Filter) => 80 - setFilter(filter & set ? filter & ~set : filter | set); 81 82 return ( 83 <div className={SortMenuClasses.container}> 84 - <Menu navId="sort-filter" hideScrollbar={true} onClose={closePopout}> 85 <MenuGroup label="Type"> 86 <MenuCheckboxItem 87 id="t-core" 88 label="Core" 89 - checked={filter & Filter.Core} 90 action={() => toggleFilter(Filter.Core)} 91 /> 92 <MenuCheckboxItem 93 id="t-normal" 94 label="Normal" 95 - checked={filter & Filter.Normal} 96 action={() => toggleFilter(Filter.Normal)} 97 /> 98 <MenuCheckboxItem 99 id="t-developer" 100 label="Developer" 101 - checked={filter & Filter.Developer} 102 action={() => toggleFilter(Filter.Developer)} 103 /> 104 </MenuGroup> ··· 106 <MenuCheckboxItem 107 id="s-enabled" 108 label="Enabled" 109 - checked={filter & Filter.Enabled} 110 action={() => toggleFilter(Filter.Enabled)} 111 /> 112 <MenuCheckboxItem 113 id="s-disabled" 114 label="Disabled" 115 - checked={filter & Filter.Disabled} 116 action={() => toggleFilter(Filter.Disabled)} 117 /> 118 </MenuGroup> ··· 120 <MenuCheckboxItem 121 id="l-installed" 122 label="Installed" 123 - checked={filter & Filter.Installed} 124 action={() => toggleFilter(Filter.Installed)} 125 /> 126 <MenuCheckboxItem 127 id="l-repository" 128 label="Repository" 129 - checked={filter & Filter.Repository} 130 action={() => toggleFilter(Filter.Repository)} 131 /> 132 </MenuGroup> ··· 134 <MenuCheckboxItem 135 id="l-incompatible" 136 label="Show incompatible" 137 - checked={filter & Filter.Incompatible} 138 action={() => toggleFilter(Filter.Incompatible)} 139 /> 140 <MenuItem 141 id="reset-all" 142 className={SortMenuClasses.clearText} 143 - label={ 144 - <Text variant="text-sm/medium" color="none"> 145 - Reset to default 146 - </Text> 147 - } 148 action={() => { 149 setFilter(defaultFilter); 150 closePopout(); ··· 156 ); 157 } 158 159 - function TagButtonPopout({ 160 - selectedTags, 161 - setSelectedTags, 162 - setPopoutRef, 163 - closePopout 164 - }: any) { 165 return ( 166 - <Dialog ref={setPopoutRef} className={FilterDialogClasses.container}> 167 - <div className={FilterDialogClasses.header}> 168 - <div className={FilterDialogClasses.headerLeft}> 169 - <Heading 170 - color="interactive-normal" 171 - variant="text-xs/bold" 172 - className={FilterDialogClasses.headerText} 173 - > 174 Select tags 175 </Heading> 176 - <div className={FilterDialogClasses.countContainer}> 177 - <Text 178 - className={FilterDialogClasses.countText} 179 - color="none" 180 - variant="text-xs/medium" 181 - > 182 {selectedTags.size} 183 </Text> 184 </div> 185 </div> 186 </div> 187 - <div className={FilterDialogClasses.tagContainer}> 188 {Object.keys(tagNames).map((tag) => ( 189 <TagItem 190 key={tag} 191 - className={FilterDialogClasses.tag} 192 - tag={{ name: tagNames[tag as keyof typeof tagNames] }} 193 onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 194 selected={selectedTags.has(tag)} 195 /> 196 ))} 197 </div> 198 - <div className={FilterDialogClasses.separator} /> 199 <Button 200 look={Button.Looks.LINK} 201 size={Button.Sizes.MIN} 202 color={Button.Colors.CUSTOM} 203 - className={FilterDialogClasses.clear} 204 onClick={() => { 205 setSelectedTags(new Set()); 206 closePopout(); ··· 225 selectedTags: Set<string>; 226 setSelectedTags: (tags: Set<string>) => void; 227 }) { 228 - const windowSize = useStateFromStores([WindowStore], () => 229 - WindowStore.windowSize() 230 - ); 231 232 const tagsContainer = React.useRef<HTMLDivElement>(null); 233 const tagListInner = React.useRef<HTMLDivElement>(null); 234 const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0); 235 React.useLayoutEffect(() => { 236 if (tagsContainer.current === null || tagListInner.current === null) return; 237 - const { left: containerX, top: containerY } = 238 - tagsContainer.current.getBoundingClientRect(); 239 let offset = 0; 240 for (const child of tagListInner.current.children) { 241 - const { 242 - right: childX, 243 - top: childY, 244 - height 245 - } = child.getBoundingClientRect(); 246 if (childY - containerY > height) break; 247 const newOffset = childX - containerX; 248 if (newOffset > offset) { ··· 250 } 251 } 252 setTagsButtonOffset(offset); 253 - }, [windowSize]); 254 255 return ( 256 <div ··· 258 style={{ 259 paddingTop: "12px" 260 }} 261 - className={`${FilterBarClasses.tagsContainer} ${Margins.marginBottom8}`} 262 > 263 <Popout 264 renderPopout={({ closePopout }: any) => ( 265 - <FilterButtonPopout 266 - filter={filter} 267 - setFilter={setFilter} 268 - closePopout={closePopout} 269 - /> 270 )} 271 position="bottom" 272 align="left" ··· 276 {...props} 277 size={Button.Sizes.MIN} 278 color={Button.Colors.CUSTOM} 279 - className={FilterBarClasses.sortDropdown} 280 - innerClassName={FilterBarClasses.sortDropdownInner} 281 > 282 <ArrowsUpDownIcon size="xs" /> 283 - <Text 284 - className={FilterBarClasses.sortDropdownText} 285 - variant="text-sm/medium" 286 - color="interactive-normal" 287 - > 288 Sort & filter 289 </Text> 290 {isShown ? ( ··· 295 </Button> 296 )} 297 </Popout> 298 - <div className={FilterBarClasses.divider} /> 299 - <div className={FilterBarClasses.tagList}> 300 - <div ref={tagListInner} className={FilterBarClasses.tagListInner}> 301 {Object.keys(tagNames).map((tag) => ( 302 <TagItem 303 key={tag} 304 - className={FilterBarClasses.tag} 305 - tag={{ name: tagNames[tag as keyof typeof tagNames] }} 306 onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 307 selected={selectedTags.has(tag)} 308 /> ··· 330 left: tagsButtonOffset 331 }} 332 // TODO: Use Discord's class name utility 333 - className={`${FilterBarClasses.tagsButton} ${ 334 - selectedTags.size > 0 ? FilterBarClasses.tagsButtonWithCount : "" 335 - }`} 336 - innerClassName={FilterBarClasses.tagsButtonInner} 337 > 338 {selectedTags.size > 0 ? ( 339 - <div 340 - style={{ boxSizing: "content-box" }} 341 - className={FilterBarClasses.countContainer} 342 - > 343 - <Text 344 - className={FilterBarClasses.countText} 345 - color="none" 346 - variant="text-xs/medium" 347 - > 348 {selectedTags.size} 349 </Text> 350 </div> ··· 359 </Button> 360 )} 361 </Popout> 362 </div> 363 ); 364 }
··· 1 import { tagNames } from "./info"; 2 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 3 import * as React from "@moonlight-mod/wp/react"; 4 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; ··· 10 Popout, 11 Dialog, 12 Menu, 13 + ChevronSmallDownIcon, 14 + ChevronSmallUpIcon, 15 + ArrowsUpDownIcon, 16 + RetryIcon, 17 + Tooltip 18 } from "@moonlight-mod/wp/discord/components/common/index"; 19 + import { MenuGroup, MenuCheckboxItem, MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu"; 20 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 21 + import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css"; 22 + import TagItem from "@moonlight-mod/wp/discord/modules/forums/web/Tag"; 23 24 export enum Filter { 25 Core = 1 << 0, ··· 29 Disabled = 1 << 4, 30 Installed = 1 << 5, 31 Repository = 1 << 6, 32 + Incompatible = 1 << 7, 33 + Deprecated = 1 << 8 34 } 35 export const defaultFilter = 127 as Filter; 36 37 + let HeaderClasses: any; 38 + let ForumsClasses: any; 39 + let SortMenuClasses: any; 40 spacepack 41 + .lazyLoad('"Missing channel in Channel.openChannelContextMenu"', /e\("(\d+)"\)/g, /webpackId:(\d+?),/) 42 .then(() => { 43 + ForumsClasses = spacepack.require("discord/modules/forums/web/Forums.css"); 44 + HeaderClasses = spacepack.require("discord/modules/forums/web/Header.css"); 45 + SortMenuClasses = spacepack.require("discord/modules/forums/web/SortMenu.css"); 46 }); 47 48 + function toggleTag(selectedTags: Set<string>, setSelectedTags: (tags: Set<string>) => void, tag: string) { 49 const newState = new Set(selectedTags); 50 if (newState.has(tag)) newState.delete(tag); 51 else newState.add(tag); ··· 61 setFilter: (filter: Filter) => void; 62 closePopout: () => void; 63 }) { 64 + const toggleFilter = (set: Filter) => setFilter(filter & set ? filter & ~set : filter | set); 65 66 return ( 67 <div className={SortMenuClasses.container}> 68 + <Menu navId="sort-filter" hideScroller={true} onClose={closePopout}> 69 <MenuGroup label="Type"> 70 <MenuCheckboxItem 71 id="t-core" 72 label="Core" 73 + checked={(filter & Filter.Core) === Filter.Core} 74 action={() => toggleFilter(Filter.Core)} 75 /> 76 <MenuCheckboxItem 77 id="t-normal" 78 label="Normal" 79 + checked={(filter & Filter.Normal) === Filter.Normal} 80 action={() => toggleFilter(Filter.Normal)} 81 /> 82 <MenuCheckboxItem 83 id="t-developer" 84 label="Developer" 85 + checked={(filter & Filter.Developer) === Filter.Developer} 86 action={() => toggleFilter(Filter.Developer)} 87 /> 88 </MenuGroup> ··· 90 <MenuCheckboxItem 91 id="s-enabled" 92 label="Enabled" 93 + checked={(filter & Filter.Enabled) === Filter.Enabled} 94 action={() => toggleFilter(Filter.Enabled)} 95 /> 96 <MenuCheckboxItem 97 id="s-disabled" 98 label="Disabled" 99 + checked={(filter & Filter.Disabled) === Filter.Disabled} 100 action={() => toggleFilter(Filter.Disabled)} 101 /> 102 </MenuGroup> ··· 104 <MenuCheckboxItem 105 id="l-installed" 106 label="Installed" 107 + checked={(filter & Filter.Installed) === Filter.Installed} 108 action={() => toggleFilter(Filter.Installed)} 109 /> 110 <MenuCheckboxItem 111 id="l-repository" 112 label="Repository" 113 + checked={(filter & Filter.Repository) === Filter.Repository} 114 action={() => toggleFilter(Filter.Repository)} 115 /> 116 </MenuGroup> ··· 118 <MenuCheckboxItem 119 id="l-incompatible" 120 label="Show incompatible" 121 + checked={(filter & Filter.Incompatible) === Filter.Incompatible} 122 action={() => toggleFilter(Filter.Incompatible)} 123 /> 124 + <MenuCheckboxItem 125 + id="l-deprecated" 126 + label="Show deprecated" 127 + checked={(filter & Filter.Deprecated) === Filter.Deprecated} 128 + action={() => toggleFilter(Filter.Deprecated)} 129 + /> 130 <MenuItem 131 id="reset-all" 132 className={SortMenuClasses.clearText} 133 + label="Reset to default" 134 action={() => { 135 setFilter(defaultFilter); 136 closePopout(); ··· 142 ); 143 } 144 145 + function TagButtonPopout({ selectedTags, setSelectedTags, setPopoutRef, closePopout }: any) { 146 return ( 147 + <Dialog ref={setPopoutRef} className={HeaderClasses.container}> 148 + <div className={HeaderClasses.header}> 149 + <div className={HeaderClasses.headerLeft}> 150 + <Heading color="interactive-normal" variant="text-xs/bold" className={HeaderClasses.headerText}> 151 Select tags 152 </Heading> 153 + <div className={HeaderClasses.countContainer}> 154 + <Text className={HeaderClasses.countText} color="none" variant="text-xs/medium"> 155 {selectedTags.size} 156 </Text> 157 </div> 158 </div> 159 </div> 160 + <div className={HeaderClasses.tagContainer}> 161 {Object.keys(tagNames).map((tag) => ( 162 <TagItem 163 key={tag} 164 + className={HeaderClasses.tag} 165 + tag={{ name: tagNames[tag as keyof typeof tagNames], id: tagNames[tag as keyof typeof tagNames] }} 166 onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 167 selected={selectedTags.has(tag)} 168 /> 169 ))} 170 </div> 171 + <div className={HeaderClasses.separator} /> 172 <Button 173 look={Button.Looks.LINK} 174 size={Button.Sizes.MIN} 175 color={Button.Colors.CUSTOM} 176 + className={HeaderClasses.clear} 177 onClick={() => { 178 setSelectedTags(new Set()); 179 closePopout(); ··· 198 selectedTags: Set<string>; 199 setSelectedTags: (tags: Set<string>) => void; 200 }) { 201 + const windowSize = useStateFromStores([WindowStore], () => WindowStore.windowSize()); 202 203 const tagsContainer = React.useRef<HTMLDivElement>(null); 204 const tagListInner = React.useRef<HTMLDivElement>(null); 205 const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0); 206 + const [checkingUpdates, setCheckingUpdates] = React.useState(false); 207 + 208 React.useLayoutEffect(() => { 209 if (tagsContainer.current === null || tagListInner.current === null) return; 210 + const { left: containerX, top: containerY } = tagsContainer.current.getBoundingClientRect(); 211 let offset = 0; 212 for (const child of tagListInner.current.children) { 213 + const { right: childX, top: childY, height } = child.getBoundingClientRect(); 214 if (childY - containerY > height) break; 215 const newOffset = childX - containerX; 216 if (newOffset > offset) { ··· 218 } 219 } 220 setTagsButtonOffset(offset); 221 + }, [windowSize, tagsContainer.current, tagListInner.current, tagListInner.current?.getBoundingClientRect()?.width]); 222 223 return ( 224 <div ··· 226 style={{ 227 paddingTop: "12px" 228 }} 229 + className={`${ForumsClasses.tagsContainer} ${Margins.marginBottom8}`} 230 > 231 + <Tooltip text="Refresh updates" position="top"> 232 + {(props: any) => ( 233 + <Button 234 + {...props} 235 + size={Button.Sizes.MIN} 236 + color={Button.Colors.CUSTOM} 237 + className={`${ForumsClasses.sortDropdown} moonbase-retry-button`} 238 + innerClassName={ForumsClasses.sortDropdownInner} 239 + onClick={() => { 240 + (async () => { 241 + try { 242 + setCheckingUpdates(true); 243 + await MoonbaseSettingsStore.checkUpdates(); 244 + } finally { 245 + // artificial delay because the spin is fun 246 + await new Promise((r) => setTimeout(r, 500)); 247 + setCheckingUpdates(false); 248 + } 249 + })(); 250 + }} 251 + > 252 + <RetryIcon size={"custom"} width={16} className={checkingUpdates ? "moonbase-speen" : ""} /> 253 + </Button> 254 + )} 255 + </Tooltip> 256 <Popout 257 renderPopout={({ closePopout }: any) => ( 258 + <FilterButtonPopout filter={filter} setFilter={setFilter} closePopout={closePopout} /> 259 )} 260 position="bottom" 261 align="left" ··· 265 {...props} 266 size={Button.Sizes.MIN} 267 color={Button.Colors.CUSTOM} 268 + className={ForumsClasses.sortDropdown} 269 + innerClassName={ForumsClasses.sortDropdownInner} 270 > 271 <ArrowsUpDownIcon size="xs" /> 272 + <Text className={ForumsClasses.sortDropdownText} variant="text-sm/medium" color="interactive-normal"> 273 Sort & filter 274 </Text> 275 {isShown ? ( ··· 280 </Button> 281 )} 282 </Popout> 283 + <div className={ForumsClasses.divider} /> 284 + <div className={ForumsClasses.tagList}> 285 + <div ref={tagListInner} className={ForumsClasses.tagListInner}> 286 {Object.keys(tagNames).map((tag) => ( 287 <TagItem 288 key={tag} 289 + className={ForumsClasses.tag} 290 + tag={{ name: tagNames[tag as keyof typeof tagNames], id: tag }} 291 onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 292 selected={selectedTags.has(tag)} 293 /> ··· 315 left: tagsButtonOffset 316 }} 317 // TODO: Use Discord's class name utility 318 + className={`${ForumsClasses.tagsButton} ${selectedTags.size > 0 ? ForumsClasses.tagsButtonWithCount : ""}`} 319 + innerClassName={ForumsClasses.tagsButtonInner} 320 > 321 {selectedTags.size > 0 ? ( 322 + <div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}> 323 + <Text className={ForumsClasses.countText} color="none" variant="text-xs/medium"> 324 {selectedTags.size} 325 </Text> 326 </div> ··· 335 </Button> 336 )} 337 </Popout> 338 + <Button 339 + size={Button.Sizes.MIN} 340 + color={Button.Colors.CUSTOM} 341 + className={`${ForumsClasses.tagsButton} ${ForumsClasses.tagsButtonPlaceholder}`} 342 + innerClassName={ForumsClasses.tagsButtonInner} 343 + > 344 + {selectedTags.size > 0 ? ( 345 + <div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}> 346 + <Text className={ForumsClasses.countText} color="none" variant="text-xs/medium"> 347 + {selectedTags.size} 348 + </Text> 349 + </div> 350 + ) : null} 351 + 352 + <ChevronSmallUpIcon size={"custom"} width={20} /> 353 + </Button> 354 </div> 355 ); 356 }
+101 -54
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
··· 6 import React from "@moonlight-mod/wp/react"; 7 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 8 import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; 9 10 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 11 import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 12 13 - const SearchBar: any = Object.values( 14 - spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0].exports 15 - )[0]; 16 17 export default function ExtensionsPage() { 18 - const { extensions, savedFilter } = useStateFromStoresObject( 19 - [MoonbaseSettingsStore], 20 - () => { 21 - return { 22 - extensions: MoonbaseSettingsStore.extensions, 23 - savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw<number>( 24 - "moonbase", 25 - "filter", 26 - defaultFilter 27 - ) 28 - }; 29 - } 30 - ); 31 32 const [query, setQuery] = React.useState(""); 33 34 let filter: Filter, setFilter: (filter: Filter) => void; 35 - if ( 36 - MoonbaseSettingsStore.getExtensionConfigRaw<boolean>( 37 - "moonbase", 38 - "saveFilter", 39 - false 40 - ) 41 - ) { 42 filter = savedFilter ?? defaultFilter; 43 - setFilter = (filter) => 44 - MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter); 45 } else { 46 - const state = React.useState(defaultFilter); 47 - filter = state[0]; 48 - setFilter = state[1]; 49 } 50 const [selectedTags, setSelectedTags] = React.useState(new Set<string>()); 51 const sorted = Object.values(extensions).sort((a, b) => { 52 const aName = a.manifest.meta?.name ?? a.id; 53 const bName = b.manifest.meta?.name ?? b.id; ··· 60 ext.manifest.id?.toLowerCase().includes(query) || 61 ext.manifest.meta?.name?.toLowerCase().includes(query) || 62 ext.manifest.meta?.tagline?.toLowerCase().includes(query) || 63 ext.manifest.meta?.description?.toLowerCase().includes(query)) && 64 - [...selectedTags.values()].every( 65 - (tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag) 66 - ) && 67 // This seems very bad, sorry 68 !( 69 - (!(filter & Filter.Core) && 70 - ext.source.type === ExtensionLoadSource.Core) || 71 - (!(filter & Filter.Normal) && 72 - ext.source.type === ExtensionLoadSource.Normal) || 73 - (!(filter & Filter.Developer) && 74 - ext.source.type === ExtensionLoadSource.Developer) || 75 - (!(filter & Filter.Enabled) && 76 - MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 77 - (!(filter & Filter.Disabled) && 78 - !MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 79 - (!(filter & Filter.Installed) && 80 - ext.state !== ExtensionState.NotDownloaded) || 81 - (!(filter & Filter.Repository) && 82 - ext.state === ExtensionState.NotDownloaded) 83 ) && 84 (filter & Filter.Incompatible || 85 ext.compat === ExtensionCompat.Compatible || 86 - (ext.compat === ExtensionCompat.InvalidApiLevel && ext.hasUpdate)) 87 ); 88 89 return ( 90 <> ··· 101 spellCheck: "false" 102 }} 103 /> 104 - <FilterBar 105 - filter={filter} 106 - setFilter={setFilter} 107 - selectedTags={selectedTags} 108 - setSelectedTags={setSelectedTags} 109 - /> 110 - {filtered.map((ext) => ( 111 - <ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} /> 112 ))} 113 </> 114 );
··· 6 import React from "@moonlight-mod/wp/react"; 7 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 8 import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; 9 + import { 10 + FormDivider, 11 + CircleInformationIcon, 12 + XSmallIcon, 13 + Button 14 + } from "@moonlight-mod/wp/discord/components/common/index"; 15 + import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton"; 16 17 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 18 + import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary"; 19 import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 20 + import HelpMessage from "../HelpMessage"; 21 22 + const SearchBar = spacepack.require("discord/uikit/search/SearchBar").default; 23 + 24 + const validTags: string[] = Object.values(ExtensionTag); 25 26 export default function ExtensionsPage() { 27 + const { extensions, savedFilter } = useStateFromStoresObject([MoonbaseSettingsStore], () => { 28 + return { 29 + extensions: MoonbaseSettingsStore.extensions, 30 + savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw<number>("moonbase", "filter", defaultFilter) 31 + }; 32 + }); 33 34 const [query, setQuery] = React.useState(""); 35 + const [hitUpdateAll, setHitUpdateAll] = React.useState(false); 36 + 37 + const filterState = React.useState(defaultFilter); 38 39 let filter: Filter, setFilter: (filter: Filter) => void; 40 + if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "saveFilter", false)) { 41 filter = savedFilter ?? defaultFilter; 42 + setFilter = (filter) => MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter); 43 } else { 44 + filter = filterState[0]; 45 + setFilter = filterState[1]; 46 } 47 + 48 const [selectedTags, setSelectedTags] = React.useState(new Set<string>()); 49 + const selectTag = React.useCallback( 50 + (tag: string) => { 51 + const newState = new Set(selectedTags); 52 + if (validTags.includes(tag)) newState.add(tag); 53 + setSelectedTags(newState); 54 + }, 55 + [selectedTags] 56 + ); 57 + 58 const sorted = Object.values(extensions).sort((a, b) => { 59 const aName = a.manifest.meta?.name ?? a.id; 60 const bName = b.manifest.meta?.name ?? b.id; ··· 67 ext.manifest.id?.toLowerCase().includes(query) || 68 ext.manifest.meta?.name?.toLowerCase().includes(query) || 69 ext.manifest.meta?.tagline?.toLowerCase().includes(query) || 70 + (ext.manifest?.settings != null && 71 + Object.entries(ext.manifest.settings).some(([key, setting]) => 72 + (setting.displayName ?? key).toLowerCase().includes(query) 73 + )) || 74 + (ext.manifest?.meta?.authors != null && 75 + ext.manifest.meta.authors.some((author) => 76 + (typeof author === "string" ? author : author.name).toLowerCase().includes(query) 77 + )) || 78 ext.manifest.meta?.description?.toLowerCase().includes(query)) && 79 + [...selectedTags.values()].every((tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)) && 80 // This seems very bad, sorry 81 !( 82 + (!(filter & Filter.Core) && ext.source.type === ExtensionLoadSource.Core) || 83 + (!(filter & Filter.Normal) && ext.source.type === ExtensionLoadSource.Normal) || 84 + (!(filter & Filter.Developer) && ext.source.type === ExtensionLoadSource.Developer) || 85 + (!(filter & Filter.Enabled) && MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 86 + (!(filter & Filter.Disabled) && !MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 87 + (!(filter & Filter.Installed) && ext.state !== ExtensionState.NotDownloaded) || 88 + (!(filter & Filter.Repository) && ext.state === ExtensionState.NotDownloaded) 89 ) && 90 (filter & Filter.Incompatible || 91 ext.compat === ExtensionCompat.Compatible || 92 + (ext.compat === ExtensionCompat.InvalidApiLevel && ext.hasUpdate)) && 93 + (filter & Filter.Deprecated || 94 + ext.manifest?.meta?.deprecated !== true || 95 + ext.state !== ExtensionState.NotDownloaded) 96 ); 97 + 98 + // Prioritize extensions with updates 99 + const filteredWithUpdates = filtered.filter((ext) => ext!.hasUpdate); 100 + const filteredWithoutUpdates = filtered.filter((ext) => !ext!.hasUpdate); 101 102 return ( 103 <> ··· 114 spellCheck: "false" 115 }} 116 /> 117 + <FilterBar filter={filter} setFilter={setFilter} selectedTags={selectedTags} setSelectedTags={setSelectedTags} /> 118 + 119 + {filteredWithUpdates.length > 0 && ( 120 + <HelpMessage 121 + icon={CircleInformationIcon} 122 + text="Extension updates are available" 123 + className="moonbase-extension-update-section" 124 + > 125 + <div className="moonbase-help-message-buttons"> 126 + <Button 127 + color={Button.Colors.BRAND} 128 + size={Button.Sizes.TINY} 129 + disabled={hitUpdateAll} 130 + onClick={() => { 131 + setHitUpdateAll(true); 132 + MoonbaseSettingsStore.updateAllExtensions(); 133 + }} 134 + > 135 + Update all 136 + </Button> 137 + <PanelButton 138 + icon={XSmallIcon} 139 + onClick={() => { 140 + MoonbaseSettingsStore.dismissAllExtensionUpdates(); 141 + }} 142 + /> 143 + </div> 144 + </HelpMessage> 145 + )} 146 + 147 + {filteredWithUpdates.map((ext) => ( 148 + <ErrorBoundary> 149 + <ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} /> 150 + </ErrorBoundary> 151 + ))} 152 + {filteredWithUpdates.length > 0 && filteredWithoutUpdates.length > 0 && ( 153 + <FormDivider className="moonbase-update-divider" /> 154 + )} 155 + {filteredWithoutUpdates.map((ext) => ( 156 + <ErrorBoundary> 157 + <ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} /> 158 + </ErrorBoundary> 159 ))} 160 </> 161 );
+50 -44
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx
··· 2 import { MoonbaseExtension } from "../../../types"; 3 4 import React from "@moonlight-mod/wp/react"; 5 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 6 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 7 8 type Dependency = { 9 id: string; ··· 34 [ExtensionTag.Library]: "Library" 35 }; 36 37 - const UserInfoClasses = spacepack.findByCode( 38 - "infoScroller", 39 - "userInfoSection", 40 - "userInfoSectionHeader" 41 - )[0].exports; 42 - 43 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 44 45 - // FIXME: type component keys 46 - const { Text } = Components; 47 - 48 - function InfoSection({ 49 - title, 50 - children 51 - }: { 52 - title: string; 53 - children: React.ReactNode; 54 - }) { 55 return ( 56 <div 57 style={{ 58 marginRight: "1em" 59 }} 60 > 61 - <Text variant="eyebrow" className={UserInfoClasses.userInfoSectionHeader}> 62 {title} 63 </Text> 64 ··· 69 70 function Badge({ 71 color, 72 - children 73 }: { 74 color: string; 75 children: React.ReactNode; 76 }) { 77 return ( 78 <span 79 - style={{ 80 - borderRadius: ".1875rem", 81 - padding: "0 0.275rem", 82 - marginRight: "0.4em", 83 - backgroundColor: color, 84 - color: "#fff" 85 - }} 86 > 87 {children} 88 </span> 89 ); 90 } 91 92 - export default function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) { 93 const authors = ext.manifest?.meta?.authors; 94 const tags = ext.manifest?.meta?.tags; 95 const version = ext.manifest?.version; 96 97 const dependencies: Dependency[] = []; 98 if (ext.manifest.dependencies != null) { 99 dependencies.push( 100 ...ext.manifest.dependencies.map((dep) => ({ ··· 114 } 115 116 if (ext.manifest.incompatible != null) { 117 - dependencies.push( 118 ...ext.manifest.incompatible.map((dep) => ({ 119 id: dep, 120 type: DependencyType.Incompatible ··· 152 <InfoSection title="Tags"> 153 {tags.map((tag, i) => { 154 const name = tagNames[tag]; 155 156 return ( 157 - <Badge 158 - key={i} 159 - color={ 160 - tag === ExtensionTag.DangerZone 161 - ? "var(--red-400)" 162 - : "var(--brand-500)" 163 - } 164 - > 165 {name} 166 </Badge> 167 ); ··· 172 {dependencies.length > 0 && ( 173 <InfoSection title="Dependencies"> 174 {dependencies.map((dep) => { 175 - const colors = { 176 - [DependencyType.Dependency]: "var(--brand-500)", 177 - [DependencyType.Optional]: "var(--orange-400)", 178 - [DependencyType.Incompatible]: "var(--red-400)" 179 - }; 180 - const color = colors[dep.type]; 181 const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id); 182 183 return ( 184 - <Badge color={color} key={dep.id}> 185 {name} 186 </Badge> 187 );
··· 2 import { MoonbaseExtension } from "../../../types"; 3 4 import React from "@moonlight-mod/wp/react"; 5 + import { Text } from "@moonlight-mod/wp/discord/components/common/index"; 6 7 type Dependency = { 8 id: string; ··· 33 [ExtensionTag.Library]: "Library" 34 }; 35 36 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 37 38 + function InfoSection({ title, children }: { title: string; children: React.ReactNode }) { 39 return ( 40 <div 41 style={{ 42 marginRight: "1em" 43 }} 44 > 45 + <Text variant="eyebrow" className="moonlight-card-info-header"> 46 {title} 47 </Text> 48 ··· 53 54 function Badge({ 55 color, 56 + children, 57 + style = {}, 58 + onClick 59 }: { 60 color: string; 61 children: React.ReactNode; 62 + style?: React.CSSProperties; 63 + onClick?: () => void; 64 }) { 65 + if (onClick) style.cursor ??= "pointer"; 66 return ( 67 <span 68 + className="moonlight-card-badge" 69 + style={ 70 + { 71 + "--badge-color": color, 72 + ...style 73 + } as React.CSSProperties 74 + } 75 + onClick={onClick} 76 > 77 {children} 78 </span> 79 ); 80 } 81 82 + export default function ExtensionInfo({ 83 + ext, 84 + selectTag 85 + }: { 86 + ext: MoonbaseExtension; 87 + selectTag: (tag: string) => void; 88 + }) { 89 const authors = ext.manifest?.meta?.authors; 90 const tags = ext.manifest?.meta?.tags; 91 const version = ext.manifest?.version; 92 93 const dependencies: Dependency[] = []; 94 + const incompatible: Dependency[] = []; 95 + 96 if (ext.manifest.dependencies != null) { 97 dependencies.push( 98 ...ext.manifest.dependencies.map((dep) => ({ ··· 112 } 113 114 if (ext.manifest.incompatible != null) { 115 + incompatible.push( 116 ...ext.manifest.incompatible.map((dep) => ({ 117 id: dep, 118 type: DependencyType.Incompatible ··· 150 <InfoSection title="Tags"> 151 {tags.map((tag, i) => { 152 const name = tagNames[tag]; 153 + let color = "var(--bg-mod-strong)"; 154 + let style; 155 + if (tag === ExtensionTag.DangerZone) { 156 + color = "var(--red-460)"; 157 + style = { color: "var(--primary-230)" }; 158 + } 159 160 return ( 161 + <Badge key={i} color={color} style={style} onClick={() => selectTag(tag)}> 162 {name} 163 </Badge> 164 ); ··· 169 {dependencies.length > 0 && ( 170 <InfoSection title="Dependencies"> 171 {dependencies.map((dep) => { 172 + const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id); 173 + 174 + // TODO: figure out a decent way to distinguish suggested 175 + return ( 176 + <Badge color="var(--bg-mod-strong)" key={dep.id}> 177 + {name} 178 + </Badge> 179 + ); 180 + })} 181 + </InfoSection> 182 + )} 183 + 184 + {incompatible.length > 0 && ( 185 + <InfoSection title="Incompatible"> 186 + {incompatible.map((dep) => { 187 const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id); 188 189 return ( 190 + <Badge color="var(--bg-mod-strong)" key={dep.id}> 191 {name} 192 </Badge> 193 );
+79 -46
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx
··· 1 // TODO: clean up the styling here 2 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 3 import React from "@moonlight-mod/wp/react"; 4 import { MoonbaseExtension } from "core-extensions/src/moonbase/types"; 5 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 6 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 7 import { ExtensionLoadSource } from "@moonlight-mod/types"; 8 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 9 10 - const { 11 - openModalLazy, 12 - closeModal 13 - } = require("@moonlight-mod/wp/discord/components/common/index"); 14 - const Popup = spacepack.findByCode(".minorContainer", "secondaryAction")[0] 15 - .exports.default; 16 17 const presentableLoadSources: Record<ExtensionLoadSource, string> = { 18 [ExtensionLoadSource.Developer]: "Local extension", // should never show up ··· 31 option: string | undefined; 32 setOption: (pick: string | undefined) => void; 33 }) { 34 - const { SingleSelect } = Components; 35 - 36 return ( 37 <SingleSelect 38 key={id} ··· 42 return { 43 value: candidate.uniqueId.toString(), 44 label: 45 - candidate.source.url ?? 46 - presentableLoadSources[candidate.source.type] ?? 47 - candidate.manifest.version ?? 48 - "" 49 }; 50 })} 51 onChange={(value: string) => { 52 setOption(value); 53 }} 54 - // @ts-expect-error no thanks 55 placeholder="Missing extension" 56 /> 57 ); 58 } 59 60 - function OurPopup({ 61 deps, 62 - transitionState, 63 - id 64 }: { 65 deps: Record<string, MoonbaseExtension[]>; 66 transitionState: number | null; 67 - id: string; 68 }) { 69 - const { Text } = Components; 70 - 71 - const amountNotAvailable = Object.values(deps).filter( 72 - (candidates) => candidates.length === 0 73 - ).length; 74 75 - const [options, setOptions] = React.useState< 76 - Record<string, string | undefined> 77 - >( 78 Object.fromEntries( 79 Object.entries(deps).map(([id, candidates]) => [ 80 id, ··· 84 ); 85 86 return ( 87 - <Popup 88 body={ 89 <Flex 90 style={{ ··· 93 direction={Flex.Direction.VERTICAL} 94 > 95 <Text variant="text-md/normal"> 96 - This extension depends on other extensions which are not downloaded. 97 - Choose which extensions to download. 98 </Text> 99 100 {amountNotAvailable > 0 && ( 101 <Text variant="text-md/normal"> 102 {amountNotAvailable} extension 103 - {amountNotAvailable > 1 ? "s" : ""} could not be found, and must 104 - be installed manually. 105 </Text> 106 )} 107 ··· 142 } 143 cancelText="Cancel" 144 confirmText="Install" 145 - onCancel={() => { 146 - closeModal(id); 147 - }} 148 onConfirm={() => { 149 - closeModal(id); 150 151 for (const pick of Object.values(options)) { 152 if (pick != null) { ··· 160 ); 161 } 162 163 - export async function doPopup(deps: Record<string, MoonbaseExtension[]>) { 164 - const id: string = await openModalLazy(async () => { 165 - // eslint-disable-next-line react/display-name 166 return ({ transitionState }: { transitionState: number | null }) => { 167 - return <OurPopup transitionState={transitionState} deps={deps} id={id} />; 168 }; 169 }); 170 } 171 172 - export default async function installWithDependencyPopup(uniqueId: number) { 173 - await MoonbaseSettingsStore.installExtension(uniqueId); 174 - const deps = await MoonbaseSettingsStore.getDependencies(uniqueId); 175 - if (deps != null) { 176 - await doPopup(deps); 177 - } 178 }
··· 1 // TODO: clean up the styling here 2 import React from "@moonlight-mod/wp/react"; 3 import { MoonbaseExtension } from "core-extensions/src/moonbase/types"; 4 + import { openModalLazy, useModalsStore, closeModal } from "@moonlight-mod/wp/discord/modules/modals/Modals"; 5 + import { SingleSelect, Text } from "@moonlight-mod/wp/discord/components/common/index"; 6 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 7 import { ExtensionLoadSource } from "@moonlight-mod/types"; 8 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 9 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 10 11 + let ConfirmModal: typeof import("@moonlight-mod/wp/discord/components/modals/ConfirmModal").default; 12 + 13 + function close() { 14 + const ModalStore = useModalsStore.getState(); 15 + closeModal(ModalStore.default[0].key); 16 + } 17 + 18 + // do this to avoid a hard dependency 19 + function lazyLoad() { 20 + if (!ConfirmModal) { 21 + ConfirmModal = spacepack.require("discord/components/modals/ConfirmModal").default; 22 + } 23 + } 24 25 const presentableLoadSources: Record<ExtensionLoadSource, string> = { 26 [ExtensionLoadSource.Developer]: "Local extension", // should never show up ··· 39 option: string | undefined; 40 setOption: (pick: string | undefined) => void; 41 }) { 42 return ( 43 <SingleSelect 44 key={id} ··· 48 return { 49 value: candidate.uniqueId.toString(), 50 label: 51 + candidate.source.url ?? presentableLoadSources[candidate.source.type] ?? candidate.manifest.version ?? "" 52 }; 53 })} 54 onChange={(value: string) => { 55 setOption(value); 56 }} 57 placeholder="Missing extension" 58 /> 59 ); 60 } 61 62 + function MissingExtensionPopup({ 63 deps, 64 + transitionState 65 }: { 66 deps: Record<string, MoonbaseExtension[]>; 67 transitionState: number | null; 68 }) { 69 + lazyLoad(); 70 + const amountNotAvailable = Object.values(deps).filter((candidates) => candidates.length === 0).length; 71 72 + const [options, setOptions] = React.useState<Record<string, string | undefined>>( 73 Object.fromEntries( 74 Object.entries(deps).map(([id, candidates]) => [ 75 id, ··· 79 ); 80 81 return ( 82 + <ConfirmModal 83 body={ 84 <Flex 85 style={{ ··· 88 direction={Flex.Direction.VERTICAL} 89 > 90 <Text variant="text-md/normal"> 91 + This extension depends on other extensions which are not downloaded. Choose which extensions to download. 92 </Text> 93 94 {amountNotAvailable > 0 && ( 95 <Text variant="text-md/normal"> 96 {amountNotAvailable} extension 97 + {amountNotAvailable > 1 ? "s" : ""} could not be found, and must be installed manually. 98 </Text> 99 )} 100 ··· 135 } 136 cancelText="Cancel" 137 confirmText="Install" 138 + onCancel={close} 139 onConfirm={() => { 140 + close(); 141 142 for (const pick of Object.values(options)) { 143 if (pick != null) { ··· 151 ); 152 } 153 154 + export async function doMissingExtensionPopup(deps: Record<string, MoonbaseExtension[]>) { 155 + await openModalLazy(async () => { 156 return ({ transitionState }: { transitionState: number | null }) => { 157 + return <MissingExtensionPopup transitionState={transitionState} deps={deps} />; 158 }; 159 }); 160 } 161 162 + function GenericExtensionPopup({ 163 + title, 164 + content, 165 + transitionState, 166 + uniqueId, 167 + cb 168 + }: { 169 + title: string; 170 + content: string; 171 + transitionState: number | null; 172 + uniqueId: number; 173 + cb: () => void; 174 + }) { 175 + lazyLoad(); 176 + 177 + return ( 178 + <ConfirmModal 179 + title={title} 180 + body={ 181 + <Flex> 182 + <Text variant="text-md/normal">{content}</Text> 183 + </Flex> 184 + } 185 + confirmText="Yes" 186 + cancelText="No" 187 + onCancel={close} 188 + onConfirm={() => { 189 + close(); 190 + cb(); 191 + }} 192 + transitionState={transitionState} 193 + /> 194 + ); 195 + } 196 + 197 + export async function doGenericExtensionPopup(title: string, content: string, uniqueId: number, cb: () => void) { 198 + await openModalLazy(async () => { 199 + return ({ transitionState }: { transitionState: number | null }) => { 200 + return ( 201 + <GenericExtensionPopup 202 + title={title} 203 + content={content} 204 + transitionState={transitionState} 205 + uniqueId={uniqueId} 206 + cb={cb} 207 + /> 208 + ); 209 + }; 210 + }); 211 }
+104 -145
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
··· 11 12 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 13 import React from "@moonlight-mod/wp/react"; 14 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 15 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 16 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 17 18 type SettingsProps = { 19 ext: MoonbaseExtension; ··· 21 setting: ExtensionSettingsManifest; 22 disabled: boolean; 23 }; 24 - 25 type SettingsComponent = React.ComponentType<SettingsProps>; 26 27 - import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 28 const Margins = spacepack.require("discord/styles/shared/Margins.css"); 29 30 function useConfigEntry<T>(uniqueId: number, name: string) { 31 return useStateFromStores( 32 [MoonbaseSettingsStore], 33 () => { 34 return { 35 value: MoonbaseSettingsStore.getExtensionConfig<T>(uniqueId, name), 36 - displayName: MoonbaseSettingsStore.getExtensionConfigName( 37 - uniqueId, 38 - name 39 - ), 40 - description: MoonbaseSettingsStore.getExtensionConfigDescription( 41 - uniqueId, 42 - name 43 - ) 44 }; 45 }, 46 [uniqueId, name] ··· 48 } 49 50 function Boolean({ ext, name, setting, disabled }: SettingsProps) { 51 - const { FormSwitch } = Components; 52 - const { value, displayName, description } = useConfigEntry<boolean>( 53 - ext.uniqueId, 54 - name 55 - ); 56 57 return ( 58 <FormSwitch ··· 62 onChange={(value: boolean) => { 63 MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 64 }} 65 - note={description} 66 className={`${Margins.marginReset} ${Margins.marginTop20}`} 67 > 68 {displayName} ··· 71 } 72 73 function Number({ ext, name, setting, disabled }: SettingsProps) { 74 - const { FormItem, FormText, Slider } = Components; 75 - const { value, displayName, description } = useConfigEntry<number>( 76 - ext.uniqueId, 77 - name 78 - ); 79 80 const castedSetting = setting as NumberSettingType; 81 - const min = castedSetting.min ?? 0; 82 - const max = castedSetting.max ?? 100; 83 84 return ( 85 <FormItem className={Margins.marginTop20} title={displayName}> 86 - {description && <FormText>{description}</FormText>} 87 - <Slider 88 - initialValue={value ?? 0} 89 - disabled={disabled} 90 - minValue={castedSetting.min ?? 0} 91 - maxValue={castedSetting.max ?? 100} 92 - onValueChange={(value: number) => { 93 - const rounded = Math.max(min, Math.min(max, Math.round(value))); 94 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded); 95 - }} 96 - /> 97 </FormItem> 98 ); 99 } 100 101 function String({ ext, name, setting, disabled }: SettingsProps) { 102 - const { FormItem, FormText, TextInput } = Components; 103 - const { value, displayName, description } = useConfigEntry<string>( 104 - ext.uniqueId, 105 - name 106 - ); 107 108 return ( 109 <FormItem className={Margins.marginTop20} title={displayName}> 110 - {description && ( 111 - <FormText className={Margins.marginBottom8}>{description}</FormText> 112 - )} 113 <TextInput 114 value={value ?? ""} 115 - onChange={(value: string) => { 116 - if (disabled) return; 117 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 118 - }} 119 /> 120 </FormItem> 121 ); 122 } 123 124 function MultilineString({ ext, name, setting, disabled }: SettingsProps) { 125 - const { FormItem, FormText, TextArea } = Components; 126 - const { value, displayName, description } = useConfigEntry<string>( 127 - ext.uniqueId, 128 - name 129 - ); 130 131 return ( 132 <FormItem className={Margins.marginTop20} title={displayName}> 133 - {description && ( 134 - <FormText className={Margins.marginBottom8}>{description}</FormText> 135 - )} 136 <TextArea 137 rows={5} 138 value={value ?? ""} 139 className={"moonbase-resizeable"} 140 - onChange={(value: string) => { 141 - if (disabled) return; 142 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 143 - }} 144 /> 145 </FormItem> 146 ); 147 } 148 149 function Select({ ext, name, setting, disabled }: SettingsProps) { 150 - const { FormItem, FormText, SingleSelect } = Components; 151 - const { value, displayName, description } = useConfigEntry<string>( 152 - ext.uniqueId, 153 - name 154 - ); 155 156 const castedSetting = setting as SelectSettingType; 157 const options = castedSetting.options; 158 159 return ( 160 <FormItem className={Margins.marginTop20} title={displayName}> 161 - {description && ( 162 - <FormText className={Margins.marginBottom8}>{description}</FormText> 163 - )} 164 <SingleSelect 165 autofocus={false} 166 clearable={false} 167 value={value ?? ""} 168 - options={options.map((o: SelectOption) => 169 - typeof o === "string" ? { value: o, label: o } : o 170 - )} 171 onChange={(value: string) => { 172 if (disabled) return; 173 MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); ··· 178 } 179 180 function MultiSelect({ ext, name, setting, disabled }: SettingsProps) { 181 - const { FormItem, FormText, Select, useVariableSelect, multiSelect } = 182 - Components; 183 - const { value, displayName, description } = useConfigEntry<string | string[]>( 184 - ext.uniqueId, 185 - name 186 - ); 187 188 const castedSetting = setting as MultiSelectSettingType; 189 const options = castedSetting.options; 190 191 return ( 192 <FormItem className={Margins.marginTop20} title={displayName}> 193 - {description && ( 194 - <FormText className={Margins.marginBottom8}>{description}</FormText> 195 - )} 196 - <Select 197 autofocus={false} 198 clearable={false} 199 closeOnSelect={false} 200 - options={options.map((o: SelectOption) => 201 - typeof o === "string" ? { value: o, label: o } : o 202 - )} 203 {...useVariableSelect({ 204 onSelectInteraction: multiSelect, 205 - value: new Set(Array.isArray(value) ? value : [value]), 206 onChange: (value: string) => { 207 if (disabled) return; 208 - MoonbaseSettingsStore.setExtensionConfig( 209 - ext.id, 210 - name, 211 - Array.from(value) 212 - ); 213 } 214 })} 215 /> ··· 217 ); 218 } 219 220 - const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0] 221 - .exports; 222 - 223 - // FIXME: type component keys 224 - const { CircleXIcon } = Components; 225 - 226 - function RemoveEntryButton({ 227 - onClick, 228 - disabled 229 - }: { 230 - onClick: () => void; 231 - disabled: boolean; 232 - }) { 233 - const { Tooltip, Clickable } = Components; 234 return ( 235 - <div className={RemoveButtonClasses.removeButtonContainer}> 236 <Tooltip text="Remove entry" position="top"> 237 {(props: any) => ( 238 - <Clickable 239 - {...props} 240 - className={RemoveButtonClasses.removeButton} 241 - onClick={onClick} 242 - > 243 <CircleXIcon width={16} height={16} /> 244 </Clickable> 245 )} ··· 249 } 250 251 function List({ ext, name, setting, disabled }: SettingsProps) { 252 - const { FormItem, FormText, TextInput, Button } = Components; 253 - const { value, displayName, description } = useConfigEntry<string[]>( 254 - ext.uniqueId, 255 - name 256 - ); 257 258 const entries = value ?? []; 259 - const updateConfig = () => 260 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries); 261 262 return ( 263 <FormItem className={Margins.marginTop20} title={displayName}> 264 - {description && ( 265 - <FormText className={Margins.marginBottom4}>{description}</FormText> 266 - )} 267 <Flex direction={Flex.Direction.VERTICAL}> 268 {entries.map((val, i) => ( 269 // FIXME: stylesheets ··· 315 } 316 317 function Dictionary({ ext, name, setting, disabled }: SettingsProps) { 318 - const { FormItem, FormText, TextInput, Button } = Components; 319 - const { value, displayName, description } = useConfigEntry< 320 - Record<string, string> 321 - >(ext.uniqueId, name); 322 323 const entries = Object.entries(value ?? {}); 324 - const updateConfig = () => 325 - MoonbaseSettingsStore.setExtensionConfig( 326 - ext.id, 327 - name, 328 - Object.fromEntries(entries) 329 - ); 330 331 return ( 332 <FormItem className={Margins.marginTop20} title={displayName}> 333 - {description && ( 334 - <FormText className={Margins.marginBottom4}>{description}</FormText> 335 - )} 336 <Flex direction={Flex.Direction.VERTICAL}> 337 {entries.map(([key, val], i) => ( 338 // FIXME: stylesheets ··· 399 [MoonbaseSettingsStore], 400 () => { 401 return { 402 - component: MoonbaseSettingsStore.getExtensionConfigComponent( 403 - ext.id, 404 - name 405 - ) 406 }; 407 }, 408 [ext.uniqueId, name] 409 ); 410 411 if (Component == null) { 412 - const { Text } = Components; 413 return ( 414 - <Text variant="text/md/normal">{`Custom setting "${displayName}" is missing a component. Perhaps the extension is not installed?`}</Text> 415 ); 416 } 417 418 return ( 419 - <Component 420 - value={value} 421 - setValue={(value) => 422 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value) 423 - } 424 - /> 425 ); 426 } 427 ··· 445 export default function Settings({ ext }: { ext: MoonbaseExtension }) { 446 return ( 447 <Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}> 448 - {Object.entries(ext.manifest.settings!).map(([name, setting]) => ( 449 <Setting 450 ext={ext} 451 key={name}
··· 11 12 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 13 import React from "@moonlight-mod/wp/react"; 14 + import { 15 + FormSwitch, 16 + FormItem, 17 + FormText, 18 + TextInput, 19 + Slider, 20 + TextArea, 21 + Tooltip, 22 + Clickable, 23 + CircleXIcon, 24 + Text, 25 + SingleSelect, 26 + Button, 27 + useVariableSelect, 28 + multiSelect, 29 + Select as DiscordSelect, 30 + NumberInputStepper 31 + } from "@moonlight-mod/wp/discord/components/common/index"; 32 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 33 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 34 + import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils"; 35 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 36 + import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary"; 37 + 38 + let GuildSettingsRoleEditClasses: any; 39 + spacepack 40 + .lazyLoad( 41 + "renderArtisanalHack", 42 + /\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"GuildSettings"/, 43 + /webpackId:(\d+),name:"GuildSettings"/ 44 + ) 45 + .then( 46 + () => 47 + (GuildSettingsRoleEditClasses = spacepack.require( 48 + "discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css" 49 + )) 50 + ); 51 52 type SettingsProps = { 53 ext: MoonbaseExtension; ··· 55 setting: ExtensionSettingsManifest; 56 disabled: boolean; 57 }; 58 type SettingsComponent = React.ComponentType<SettingsProps>; 59 60 const Margins = spacepack.require("discord/styles/shared/Margins.css"); 61 62 + function markdownify(str: string) { 63 + return MarkupUtils.parse(str, true, { 64 + hideSimpleEmbedContent: true, 65 + allowLinks: true 66 + }); 67 + } 68 + 69 function useConfigEntry<T>(uniqueId: number, name: string) { 70 return useStateFromStores( 71 [MoonbaseSettingsStore], 72 () => { 73 return { 74 value: MoonbaseSettingsStore.getExtensionConfig<T>(uniqueId, name), 75 + displayName: MoonbaseSettingsStore.getExtensionConfigName(uniqueId, name), 76 + description: MoonbaseSettingsStore.getExtensionConfigDescription(uniqueId, name) 77 }; 78 }, 79 [uniqueId, name] ··· 81 } 82 83 function Boolean({ ext, name, setting, disabled }: SettingsProps) { 84 + const { value, displayName, description } = useConfigEntry<boolean>(ext.uniqueId, name); 85 86 return ( 87 <FormSwitch ··· 91 onChange={(value: boolean) => { 92 MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 93 }} 94 + note={description != null ? markdownify(description) : undefined} 95 className={`${Margins.marginReset} ${Margins.marginTop20}`} 96 > 97 {displayName} ··· 100 } 101 102 function Number({ ext, name, setting, disabled }: SettingsProps) { 103 + const { value, displayName, description } = useConfigEntry<number>(ext.uniqueId, name); 104 105 const castedSetting = setting as NumberSettingType; 106 + const min = castedSetting.min; 107 + const max = castedSetting.max; 108 + 109 + const onChange = (value: number) => { 110 + const rounded = min == null || max == null ? Math.round(value) : Math.max(min, Math.min(max, Math.round(value))); 111 + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded); 112 + }; 113 114 return ( 115 <FormItem className={Margins.marginTop20} title={displayName}> 116 + {min == null || max == null ? ( 117 + <Flex justify={Flex.Justify.BETWEEN} direction={Flex.Direction.HORIZONTAL}> 118 + {description && <FormText>{markdownify(description)}</FormText>} 119 + <NumberInputStepper value={value ?? 0} onChange={onChange} /> 120 + </Flex> 121 + ) : ( 122 + <> 123 + {description && <FormText>{markdownify(description)}</FormText>} 124 + <Slider 125 + initialValue={value ?? 0} 126 + disabled={disabled} 127 + minValue={min} 128 + maxValue={max} 129 + onValueChange={onChange} 130 + onValueRender={(value: number) => `${Math.round(value)}`} 131 + /> 132 + </> 133 + )} 134 </FormItem> 135 ); 136 } 137 138 function String({ ext, name, setting, disabled }: SettingsProps) { 139 + const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name); 140 141 return ( 142 <FormItem className={Margins.marginTop20} title={displayName}> 143 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 144 <TextInput 145 value={value ?? ""} 146 + disabled={disabled} 147 + onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} 148 /> 149 </FormItem> 150 ); 151 } 152 153 function MultilineString({ ext, name, setting, disabled }: SettingsProps) { 154 + const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name); 155 156 return ( 157 <FormItem className={Margins.marginTop20} title={displayName}> 158 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 159 <TextArea 160 rows={5} 161 value={value ?? ""} 162 + disabled={disabled} 163 className={"moonbase-resizeable"} 164 + onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} 165 /> 166 </FormItem> 167 ); 168 } 169 170 function Select({ ext, name, setting, disabled }: SettingsProps) { 171 + const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name); 172 173 const castedSetting = setting as SelectSettingType; 174 const options = castedSetting.options; 175 176 return ( 177 <FormItem className={Margins.marginTop20} title={displayName}> 178 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 179 <SingleSelect 180 autofocus={false} 181 clearable={false} 182 value={value ?? ""} 183 + options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))} 184 onChange={(value: string) => { 185 if (disabled) return; 186 MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); ··· 191 } 192 193 function MultiSelect({ ext, name, setting, disabled }: SettingsProps) { 194 + const { value, displayName, description } = useConfigEntry<string | string[]>(ext.uniqueId, name); 195 196 const castedSetting = setting as MultiSelectSettingType; 197 const options = castedSetting.options; 198 199 return ( 200 <FormItem className={Margins.marginTop20} title={displayName}> 201 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 202 + <DiscordSelect 203 autofocus={false} 204 clearable={false} 205 closeOnSelect={false} 206 + options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))} 207 {...useVariableSelect({ 208 onSelectInteraction: multiSelect, 209 + value: value == null ? new Set() : new Set(Array.isArray(value) ? value : [value]), 210 onChange: (value: string) => { 211 if (disabled) return; 212 + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Array.from(value)); 213 } 214 })} 215 /> ··· 217 ); 218 } 219 220 + function RemoveEntryButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) { 221 return ( 222 + <div className={GuildSettingsRoleEditClasses.removeButtonContainer}> 223 <Tooltip text="Remove entry" position="top"> 224 {(props: any) => ( 225 + <Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}> 226 <CircleXIcon width={16} height={16} /> 227 </Clickable> 228 )} ··· 232 } 233 234 function List({ ext, name, setting, disabled }: SettingsProps) { 235 + const { value, displayName, description } = useConfigEntry<string[]>(ext.uniqueId, name); 236 237 const entries = value ?? []; 238 + const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries); 239 240 return ( 241 <FormItem className={Margins.marginTop20} title={displayName}> 242 + {description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>} 243 <Flex direction={Flex.Direction.VERTICAL}> 244 {entries.map((val, i) => ( 245 // FIXME: stylesheets ··· 291 } 292 293 function Dictionary({ ext, name, setting, disabled }: SettingsProps) { 294 + const { value, displayName, description } = useConfigEntry<Record<string, string>>(ext.uniqueId, name); 295 296 const entries = Object.entries(value ?? {}); 297 + const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Object.fromEntries(entries)); 298 299 return ( 300 <FormItem className={Margins.marginTop20} title={displayName}> 301 + {description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>} 302 <Flex direction={Flex.Direction.VERTICAL}> 303 {entries.map(([key, val], i) => ( 304 // FIXME: stylesheets ··· 365 [MoonbaseSettingsStore], 366 () => { 367 return { 368 + component: MoonbaseSettingsStore.getExtensionConfigComponent(ext.id, name) 369 }; 370 }, 371 [ext.uniqueId, name] 372 ); 373 374 if (Component == null) { 375 return ( 376 + <Text variant="text-md/normal">{`Custom setting "${displayName}" is missing a component. Perhaps the extension is not installed?`}</Text> 377 ); 378 } 379 380 return ( 381 + <ErrorBoundary> 382 + <Component value={value} setValue={(value) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} /> 383 + </ErrorBoundary> 384 ); 385 } 386 ··· 404 export default function Settings({ ext }: { ext: MoonbaseExtension }) { 405 return ( 406 <Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}> 407 + {Object.entries(ext.settingsOverride ?? ext.manifest.settings!).map(([name, setting]) => ( 408 <Setting 409 ext={ext} 410 key={name}
+23 -29
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
··· 1 import React from "@moonlight-mod/wp/react"; 2 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 3 - import { 4 - Text, 5 - TabBar 6 - } from "@moonlight-mod/wp/discord/components/common/index"; 7 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 8 import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores"; 9 10 import ExtensionsPage from "./extensions"; 11 import ConfigPage from "./config"; 12 import Update from "./update"; 13 - 14 - const { Divider } = spacepack.findByCode(".forumOrHome]:")[0].exports.Z; 15 - const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0] 16 - .exports; 17 - const TabBarClasses = spacepack.findByCode("nowPlayingColumn:")[0].exports; 18 - const { setSection, clearSubsection } = spacepack.findByExports( 19 - "setSection", 20 - "clearSubsection" 21 - )[0].exports.Z; 22 - const Margins = spacepack.require("discord/styles/shared/Margins.css"); 23 24 export const pages: { 25 id: string; ··· 35 id: "config", 36 name: "Config", 37 element: ConfigPage 38 } 39 ]; 40 41 export function Moonbase(props: { initialTab?: number } = {}) { 42 - const subsection = useStateFromStores( 43 - [UserSettingsModalStore], 44 - () => UserSettingsModalStore.getSubsection() ?? 0 45 - ); 46 const setSubsection = React.useCallback( 47 (to: string) => { 48 - if (subsection !== to) setSection("moonbase", to); 49 }, 50 [subsection] 51 ); ··· 53 React.useEffect( 54 () => () => { 55 // Normally there's an onSettingsClose prop you can set but we don't expose it and I don't care enough to add support for it right now 56 - clearSubsection("moonbase"); 57 }, 58 [] 59 ); 60 61 return ( 62 <> 63 - <div className={`${TitleBarClasses.children} ${Margins.marginBottom20}`}> 64 - <Text 65 - className={TitleBarClasses.titleWrapper} 66 - variant="heading-lg/semibold" 67 - tag="h2" 68 - > 69 Moonbase 70 </Text> 71 <Divider /> ··· 73 selectedItem={subsection} 74 onItemSelect={setSubsection} 75 type="top-pill" 76 - className={TabBarClasses.tabBar} 77 > 78 {pages.map((page, i) => ( 79 - <TabBar.Item key={page.id} id={i} className={TabBarClasses.item}> 80 {page.name} 81 </TabBar.Item> 82 ))} 83 </TabBar> 84 </div> 85 86 <Update /> 87 88 {React.createElement(pages[subsection].element)} 89 </> 90 ); 91 }
··· 1 import React from "@moonlight-mod/wp/react"; 2 + import { Text, TabBar } from "@moonlight-mod/wp/discord/components/common/index"; 3 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 4 import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores"; 5 6 import ExtensionsPage from "./extensions"; 7 import ConfigPage from "./config"; 8 + import AboutPage from "./about"; 9 import Update from "./update"; 10 + import RestartAdviceMessage from "./RestartAdvice"; 11 + import { Divider } from "@moonlight-mod/wp/discord/components/common/BaseHeaderBar"; 12 + import HeaderBarClasses from "@moonlight-mod/wp/discord/components/common/HeaderBar.css"; 13 + import PeoplePageClasses from "@moonlight-mod/wp/discord/modules/people/web/PeoplePage.css"; 14 + import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators"; 15 + import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css"; 16 17 export const pages: { 18 id: string; ··· 28 id: "config", 29 name: "Config", 30 element: ConfigPage 31 + }, 32 + { 33 + id: "about", 34 + name: "About", 35 + element: AboutPage 36 } 37 ]; 38 39 export function Moonbase(props: { initialTab?: number } = {}) { 40 + const subsection = useStateFromStores([UserSettingsModalStore], () => UserSettingsModalStore.getSubsection() ?? 0); 41 const setSubsection = React.useCallback( 42 (to: string) => { 43 + if (subsection !== to) UserSettingsModalActionCreators.setSection("moonbase", to); 44 }, 45 [subsection] 46 ); ··· 48 React.useEffect( 49 () => () => { 50 // Normally there's an onSettingsClose prop you can set but we don't expose it and I don't care enough to add support for it right now 51 + UserSettingsModalActionCreators.clearSubsection("moonbase"); 52 }, 53 [] 54 ); 55 56 return ( 57 <> 58 + <div className={`${HeaderBarClasses.children} ${Margins.marginBottom20}`}> 59 + <Text className={HeaderBarClasses.titleWrapper} variant="heading-lg/semibold" tag="h2"> 60 Moonbase 61 </Text> 62 <Divider /> ··· 64 selectedItem={subsection} 65 onItemSelect={setSubsection} 66 type="top-pill" 67 + className={PeoplePageClasses.tabBar} 68 > 69 {pages.map((page, i) => ( 70 + <TabBar.Item key={page.id} id={i} className={PeoplePageClasses.item}> 71 {page.name} 72 </TabBar.Item> 73 ))} 74 </TabBar> 75 </div> 76 77 + <RestartAdviceMessage /> 78 <Update /> 79 80 {React.createElement(pages[subsection].element)} 81 </> 82 ); 83 } 84 + 85 + export { RestartAdviceMessage, Update };
+104 -67
packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx
··· 1 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 2 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 3 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 4 import React from "@moonlight-mod/wp/react"; 5 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 6 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 7 - 8 - enum UpdateState { 9 - Ready, 10 - Working, 11 - Installed, 12 - Failed 13 - } 14 - 15 - const { ThemeDarkIcon, Text, Button } = Components; 16 - const Margins = spacepack.require("discord/styles/shared/Margins.css"); 17 - const HelpMessageClasses = spacepack.findByExports("positive", "iconDiv")[0] 18 - .exports; 19 - 20 - const logger = moonlight.getLogger("moonbase/ui/update"); 21 22 const strings: Record<UpdateState, string> = { 23 [UpdateState.Ready]: "A new version of moonlight is available.", 24 [UpdateState.Working]: "Updating moonlight...", 25 [UpdateState.Installed]: "Updated. Restart Discord to apply changes.", 26 - [UpdateState.Failed]: 27 - "Failed to update moonlight. Please use the installer instead." 28 }; 29 30 - export default function Update() { 31 - const [state, setState] = React.useState(UpdateState.Ready); 32 - const newVersion = useStateFromStores( 33 - [MoonbaseSettingsStore], 34 - () => MoonbaseSettingsStore.newVersion 35 ); 36 37 if (newVersion == null) return null; 38 39 - // reimpl of HelpMessage but with a custom icon 40 return ( 41 - <div 42 - className={`${Margins.marginBottom20} ${HelpMessageClasses.info} ${HelpMessageClasses.container} moonbase-update-section`} 43 - > 44 - <Flex direction={Flex.Direction.HORIZONTAL}> 45 - <div 46 - className={HelpMessageClasses.iconDiv} 47 - style={{ 48 - alignItems: "center" 49 }} 50 > 51 - <ThemeDarkIcon 52 - size="sm" 53 - color="currentColor" 54 - className={HelpMessageClasses.icon} 55 - /> 56 - </div> 57 - 58 - <Text 59 - variant="text-sm/medium" 60 - color="currentColor" 61 - className={HelpMessageClasses.text} 62 - > 63 - {strings[state]} 64 - </Text> 65 - </Flex> 66 - 67 - <Button 68 - look={Button.Looks.OUTLINED} 69 - color={Button.Colors.CUSTOM} 70 - size={Button.Sizes.TINY} 71 - disabled={state !== UpdateState.Ready} 72 - onClick={() => { 73 - setState(UpdateState.Working); 74 - 75 - MoonbaseSettingsStore.updateMoonlight() 76 - .then(() => setState(UpdateState.Installed)) 77 - .catch((e) => { 78 - logger.error(e); 79 - setState(UpdateState.Failed); 80 - }); 81 - }} 82 - > 83 - Update 84 - </Button> 85 - </div> 86 ); 87 }
··· 1 import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 2 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 3 import React from "@moonlight-mod/wp/react"; 4 + import { UpdateState } from "../../types"; 5 + import HelpMessage from "./HelpMessage"; 6 + import { MoonlightBranch } from "@moonlight-mod/types"; 7 + import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils"; 8 import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 9 + import { 10 + Button, 11 + Text, 12 + ModalRoot, 13 + ModalSize, 14 + ModalContent, 15 + ModalHeader, 16 + Heading, 17 + ModalCloseButton, 18 + openModal 19 + } from "@moonlight-mod/wp/discord/components/common/index"; 20 + import MarkupClasses from "@moonlight-mod/wp/discord/modules/messages/web/Markup.css"; 21 + import ThemeDarkIcon from "@moonlight-mod/wp/moonbase_ThemeDarkIcon"; 22 23 const strings: Record<UpdateState, string> = { 24 [UpdateState.Ready]: "A new version of moonlight is available.", 25 [UpdateState.Working]: "Updating moonlight...", 26 [UpdateState.Installed]: "Updated. Restart Discord to apply changes.", 27 + [UpdateState.Failed]: "Failed to update moonlight. Please use the installer instead." 28 }; 29 30 + function MoonlightChangelog({ 31 + changelog, 32 + version, 33 + transitionState, 34 + onClose 35 + }: { 36 + changelog: string; 37 + version: string; 38 + transitionState: number | null; 39 + onClose: () => void; 40 + }) { 41 + return ( 42 + <ModalRoot transitionState={transitionState} size={ModalSize.DYNAMIC}> 43 + <ModalHeader> 44 + <Flex.Child grow={1} shrink={1}> 45 + <Heading variant="heading-lg/semibold">moonlight</Heading> 46 + <Text variant="text-xs/normal">{version}</Text> 47 + </Flex.Child> 48 + 49 + <Flex.Child grow={0}> 50 + <ModalCloseButton onClick={onClose} /> 51 + </Flex.Child> 52 + </ModalHeader> 53 + 54 + <ModalContent> 55 + <Text variant="text-md/normal" className={MarkupClasses.markup} style={{ padding: "1rem" }}> 56 + {MarkupUtils.parse(changelog, true, { 57 + allowHeading: true, 58 + allowList: true, 59 + allowLinks: true 60 + })} 61 + </Text> 62 + </ModalContent> 63 + </ModalRoot> 64 ); 65 + } 66 + 67 + export default function Update() { 68 + const [newVersion, state] = useStateFromStores([MoonbaseSettingsStore], () => [ 69 + MoonbaseSettingsStore.newVersion, 70 + MoonbaseSettingsStore.updateState 71 + ]); 72 73 if (newVersion == null) return null; 74 75 return ( 76 + <HelpMessage text={strings[state]} className="moonbase-update-section" icon={ThemeDarkIcon}> 77 + <div className="moonbase-help-message-buttons"> 78 + {moonlight.branch === MoonlightBranch.STABLE && ( 79 + <Button 80 + look={Button.Looks.OUTLINED} 81 + color={Button.Colors.CUSTOM} 82 + size={Button.Sizes.TINY} 83 + onClick={() => { 84 + fetch(`https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/tags/${newVersion}/CHANGELOG.md`) 85 + .then((r) => r.text()) 86 + .then((changelog) => 87 + openModal((modalProps) => { 88 + return <MoonlightChangelog {...modalProps} changelog={changelog} version={newVersion} />; 89 + }) 90 + ); 91 + }} 92 + > 93 + View changelog 94 + </Button> 95 + )} 96 + 97 + {state === UpdateState.Installed && ( 98 + <Button 99 + look={Button.Looks.OUTLINED} 100 + color={Button.Colors.CUSTOM} 101 + size={Button.Sizes.TINY} 102 + onClick={() => { 103 + MoonbaseSettingsStore.restartDiscord(); 104 + }} 105 + > 106 + Restart Discord 107 + </Button> 108 + )} 109 + 110 + <Button 111 + look={Button.Looks.OUTLINED} 112 + color={Button.Colors.CUSTOM} 113 + size={Button.Sizes.TINY} 114 + disabled={state !== UpdateState.Ready} 115 + onClick={() => { 116 + MoonbaseSettingsStore.updateMoonlight(); 117 }} 118 > 119 + Update 120 + </Button> 121 + </div> 122 + </HelpMessage> 123 ); 124 }
+9 -42
packages/core-extensions/src/moonbase/webpackModules/updates.tsx
··· 3 import Notices from "@moonlight-mod/wp/notices_notices"; 4 import { MoonlightBranch } from "@moonlight-mod/types"; 5 import React from "@moonlight-mod/wp/react"; 6 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 7 - 8 - // FIXME: not indexed as importable 9 - const Constants = spacepack.require("discord/Constants"); 10 - const UserSettingsSections = spacepack.findObjectFromKey( 11 - Constants, 12 - "APPEARANCE_THEME_PICKER" 13 - ); 14 - 15 - const { ThemeDarkIcon } = Components; 16 17 function plural(str: string, num: number) { 18 return `${str}${num > 1 ? "s" : ""}`; ··· 21 function listener() { 22 if ( 23 MoonbaseSettingsStore.shouldShowNotice && 24 - MoonbaseSettingsStore.getExtensionConfigRaw( 25 - "moonbase", 26 - "updateBanner", 27 - true 28 - ) 29 ) { 30 - // @ts-expect-error epic type fail 31 MoonbaseSettingsStore.removeChangeListener(listener); 32 33 const version = MoonbaseSettingsStore.newVersion; 34 - const extensionUpdateCount = Object.keys( 35 - MoonbaseSettingsStore.updates 36 - ).length; 37 const hasExtensionUpdates = extensionUpdateCount > 0; 38 39 let message; ··· 73 { 74 name: "Open Moonbase", 75 onClick: () => { 76 - const { open } = spacepack.findByExports( 77 - "setSection", 78 - "clearSubsection" 79 - )[0].exports.Z; 80 - 81 - // settings is lazy loaded thus lazily patched 82 - // FIXME: figure out a way to detect if settings has been opened 83 - // alreadyjust so the transition isnt as jarring 84 - open(UserSettingsSections.ACCOUNT); 85 - setTimeout(() => { 86 - if ( 87 - MoonbaseSettingsStore.getExtensionConfigRaw<boolean>( 88 - "moonbase", 89 - "sections", 90 - false 91 - ) 92 - ) { 93 - open("moonbase-extensions"); 94 - } else { 95 - open("moonbase", 0); 96 - } 97 - }, 0); 98 return true; 99 } 100 } ··· 103 } 104 } 105 106 - // @ts-expect-error epic type fail 107 MoonbaseSettingsStore.addChangeListener(listener);
··· 3 import Notices from "@moonlight-mod/wp/notices_notices"; 4 import { MoonlightBranch } from "@moonlight-mod/types"; 5 import React from "@moonlight-mod/wp/react"; 6 + import ThemeDarkIcon from "@moonlight-mod/wp/moonbase_ThemeDarkIcon"; 7 8 function plural(str: string, num: number) { 9 return `${str}${num > 1 ? "s" : ""}`; ··· 12 function listener() { 13 if ( 14 MoonbaseSettingsStore.shouldShowNotice && 15 + MoonbaseSettingsStore.getExtensionConfigRaw("moonbase", "updateBanner", true) 16 ) { 17 MoonbaseSettingsStore.removeChangeListener(listener); 18 19 const version = MoonbaseSettingsStore.newVersion; 20 + const extensionUpdateCount = Object.keys(MoonbaseSettingsStore.updates).length; 21 const hasExtensionUpdates = extensionUpdateCount > 0; 22 23 let message; ··· 57 { 58 name: "Open Moonbase", 59 onClick: () => { 60 + const { open } = spacepack.require("discord/actions/UserSettingsModalActionCreators").default; 61 + if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "sections", false)) { 62 + open("moonbase-extensions"); 63 + } else { 64 + open("moonbase", "0"); 65 + } 66 return true; 67 } 68 } ··· 71 } 72 } 73 74 MoonbaseSettingsStore.addChangeListener(listener);
+5
packages/core-extensions/src/moonbase/wp.d.ts
··· 5 declare module "@moonlight-mod/wp/moonbase_stores" { 6 export * from "core-extensions/src/moonbase/webpackModules/stores"; 7 }
··· 5 declare module "@moonlight-mod/wp/moonbase_stores" { 6 export * from "core-extensions/src/moonbase/webpackModules/stores"; 7 } 8 + 9 + declare module "@moonlight-mod/wp/moonbase_ThemeDarkIcon" { 10 + import ThemeDarkIcon from "core-extensions/src/moonbase/webpackModules/ThemeDarkIcon"; 11 + export = ThemeDarkIcon; 12 + }
+150 -43
packages/core-extensions/src/nativeFixes/host.ts
··· 1 import { app, nativeTheme } from "electron"; 2 3 - const enabledFeatures = app.commandLine 4 - .getSwitchValue("enable-features") 5 - .split(","); 6 7 moonlightHost.events.on("window-created", function (browserWindow) { 8 - if ( 9 - moonlightHost.getConfigOption<boolean>("nativeFixes", "devtoolsThemeFix") ?? 10 - true 11 - ) { 12 browserWindow.webContents.on("devtools-opened", () => { 13 if (!nativeTheme.shouldUseDarkColors) return; 14 nativeTheme.themeSource = "light"; ··· 19 } 20 }); 21 22 - if ( 23 - moonlightHost.getConfigOption<boolean>( 24 - "nativeFixes", 25 - "disableRendererBackgrounding" 26 - ) ?? 27 - true 28 - ) { 29 // Discord already disables UseEcoQoSForBackgroundProcess and some other 30 // related features 31 app.commandLine.appendSwitch("disable-renderer-backgrounding"); ··· 35 app.commandLine.appendSwitch("disable-background-timer-throttling"); 36 } 37 38 if (process.platform === "linux") { 39 - if ( 40 - moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxAutoscroll") ?? 41 - false 42 - ) { 43 - app.commandLine.appendSwitch( 44 - "enable-blink-features", 45 - "MiddleClickAutoscroll" 46 - ); 47 } 48 49 - if ( 50 - moonlightHost.getConfigOption<boolean>( 51 - "nativeFixes", 52 - "linuxSpeechDispatcher" 53 - ) ?? 54 - true 55 - ) { 56 app.commandLine.appendSwitch("enable-speech-dispatcher"); 57 } 58 } 59 60 // NOTE: Only tested if this appears on Windows, it should appear on all when 61 // hardware acceleration is disabled 62 const noAccel = app.commandLine.hasSwitch("disable-gpu-compositing"); 63 - if ( 64 - (moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapi") ?? true) && 65 - !noAccel 66 - ) { 67 - if (process.platform === "linux") 68 // These will eventually be renamed https://source.chromium.org/chromium/chromium/src/+/5482210941a94d70406b8da962426e4faca7fce4 69 - enabledFeatures.push( 70 - "VaapiVideoEncoder", 71 - "VaapiVideoDecoder", 72 - "VaapiVideoDecodeLinuxGL" 73 - ); 74 } 75 76 - app.commandLine.appendSwitch( 77 - "enable-features", 78 - [...new Set(enabledFeatures)].join(",") 79 - );
··· 1 import { app, nativeTheme } from "electron"; 2 + import * as path from "node:path"; 3 + import * as fs from "node:fs/promises"; 4 + import * as fsSync from "node:fs"; 5 + import { parseTarGzip } from "nanotar"; 6 7 + const logger = moonlightHost.getLogger("nativeFixes/host"); 8 + const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(","); 9 10 moonlightHost.events.on("window-created", function (browserWindow) { 11 + if (moonlightHost.getConfigOption<boolean>("nativeFixes", "devtoolsThemeFix") ?? true) { 12 browserWindow.webContents.on("devtools-opened", () => { 13 if (!nativeTheme.shouldUseDarkColors) return; 14 nativeTheme.themeSource = "light"; ··· 19 } 20 }); 21 22 + if (moonlightHost.getConfigOption<boolean>("nativeFixes", "disableRendererBackgrounding") ?? true) { 23 // Discord already disables UseEcoQoSForBackgroundProcess and some other 24 // related features 25 app.commandLine.appendSwitch("disable-renderer-backgrounding"); ··· 29 app.commandLine.appendSwitch("disable-background-timer-throttling"); 30 } 31 32 + if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vulkan") ?? false) { 33 + enabledFeatures.push("Vulkan", "DefaultANGLEVulkan", "VulkanFromANGLE"); 34 + } 35 + 36 if (process.platform === "linux") { 37 + if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxAutoscroll") ?? false) { 38 + app.commandLine.appendSwitch("enable-blink-features", "MiddleClickAutoscroll"); 39 } 40 41 + if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxSpeechDispatcher") ?? true) { 42 app.commandLine.appendSwitch("enable-speech-dispatcher"); 43 } 44 + 45 + if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxHevcSupport") ?? true) { 46 + enabledFeatures.push("PlatformHEVCDecoderSupport"); 47 + } 48 } 49 50 // NOTE: Only tested if this appears on Windows, it should appear on all when 51 // hardware acceleration is disabled 52 const noAccel = app.commandLine.hasSwitch("disable-gpu-compositing"); 53 + if ((moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapi") ?? true) && !noAccel) { 54 + if (process.platform === "linux") { 55 // These will eventually be renamed https://source.chromium.org/chromium/chromium/src/+/5482210941a94d70406b8da962426e4faca7fce4 56 + enabledFeatures.push("VaapiVideoEncoder", "VaapiVideoDecoder", "VaapiVideoDecodeLinuxGL"); 57 + 58 + if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapiIgnoreDriverChecks") ?? false) 59 + enabledFeatures.push("VaapiIgnoreDriverChecks"); 60 + } 61 } 62 63 + app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].join(",")); 64 + 65 + if (process.platform === "linux" && moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxUpdater")) { 66 + const exePath = app.getPath("exe"); 67 + const appName = path.basename(exePath); 68 + const targetDir = path.dirname(exePath); 69 + const { releaseChannel }: { releaseChannel: string } = JSON.parse( 70 + fsSync.readFileSync(path.join(targetDir, "resources", "build_info.json"), "utf8") 71 + ); 72 + 73 + const updaterModule = require(path.join(moonlightHost.asarPath, "app_bootstrap", "hostUpdater.js")); 74 + const updater = updaterModule.constructor; 75 + 76 + async function doUpdate(cb: (percent: number) => void) { 77 + logger.debug("Extracting to", targetDir); 78 + 79 + const exists = (path: string) => 80 + fs 81 + .stat(path) 82 + .then(() => true) 83 + .catch(() => false); 84 + 85 + const url = `https://discord.com/api/download/${releaseChannel}?platform=linux&format=tar.gz`; 86 + const resp = await fetch(url, { 87 + cache: "no-store" 88 + }); 89 + 90 + const reader = resp.body!.getReader(); 91 + const contentLength = parseInt(resp.headers.get("Content-Length") ?? "0"); 92 + logger.info(`Expecting ${contentLength} bytes for the update`); 93 + const bytes = new Uint8Array(contentLength); 94 + let pos = 0; 95 + let lastPercent = 0; 96 + 97 + while (true) { 98 + const { done, value } = await reader.read(); 99 + if (done) { 100 + break; 101 + } else { 102 + bytes.set(value, pos); 103 + pos += value.length; 104 + 105 + const newPercent = Math.floor((pos / contentLength) * 100); 106 + if (lastPercent !== newPercent) { 107 + lastPercent = newPercent; 108 + cb(newPercent); 109 + } 110 + } 111 + } 112 + 113 + const files = await parseTarGzip(bytes); 114 + 115 + for (const file of files) { 116 + if (!file.data) continue; 117 + // @ts-expect-error What do you mean their own types are wrong 118 + if (file.type !== "file") continue; 119 + 120 + // Discord update files are inside of a main "Discord(PTB|Canary)" folder 121 + const filePath = file.name.replace(`${appName}/`, ""); 122 + logger.info("Extracting", filePath); 123 + 124 + let targetFilePath = path.join(targetDir, filePath); 125 + if (filePath === "resources/app.asar") { 126 + // You tried 127 + targetFilePath = path.join(targetDir, "resources", "_app.asar"); 128 + } else if (filePath === appName || filePath === "chrome_crashpad_handler") { 129 + // Can't write over the executable? Just move it! 4head 130 + if (await exists(targetFilePath)) { 131 + await fs.rename(targetFilePath, targetFilePath + ".bak"); 132 + await fs.unlink(targetFilePath + ".bak"); 133 + } 134 + } 135 + const targetFileDir = path.dirname(targetFilePath); 136 + 137 + if (!(await exists(targetFileDir))) await fs.mkdir(targetFileDir, { recursive: true }); 138 + await fs.writeFile(targetFilePath, file.data); 139 + 140 + const mode = file.attrs?.mode; 141 + if (mode != null) { 142 + // Not sure why this slice is needed 143 + await fs.chmod(targetFilePath, mode.slice(-3)); 144 + } 145 + } 146 + 147 + logger.debug("Done updating"); 148 + } 149 + 150 + const realEmit = updater.prototype.emit; 151 + updater.prototype.emit = function (event: string, ...args: any[]) { 152 + // Arrow functions don't bind `this` :D 153 + const call = (event: string, ...args: any[]) => realEmit.call(this, event, ...args); 154 + 155 + if (event === "update-manually") { 156 + const latestVerStr: string = args[0]; 157 + logger.debug("update-manually called, intercepting", latestVerStr); 158 + call("update-available"); 159 + 160 + (async () => { 161 + try { 162 + await doUpdate((progress) => { 163 + call("update-progress", progress); 164 + }); 165 + // Copied from the win32 updater 166 + this.updateVersion = latestVerStr; 167 + call( 168 + "update-downloaded", 169 + {}, 170 + releaseChannel, 171 + latestVerStr, 172 + new Date(), 173 + this.updateUrl, 174 + this.quitAndInstall.bind(this) 175 + ); 176 + } catch (e) { 177 + logger.error("Error updating", e); 178 + } 179 + })(); 180 + 181 + return this; 182 + } else { 183 + return realEmit.call(this, event, ...args); 184 + } 185 + }; 186 + }
+36 -1
packages/core-extensions/src/nativeFixes/manifest.json
··· 1 { 2 "id": "nativeFixes", 3 "meta": { 4 "name": "Native Fixes", 5 "tagline": "Various configurable fixes for Discord and Electron", 6 - "authors": ["Cynosphere", "adryd"], 7 "tags": ["fixes"] 8 }, 9 "settings": { 10 "devtoolsThemeFix": { 11 "displayName": "Devtools Theme Fix", 12 "description": "Temporary workaround for devtools defaulting to light theme on Electron 32", 13 "type": "boolean", 14 "default": true 15 }, 16 "disableRendererBackgrounding": { 17 "displayName": "Disable Renderer Backgrounding", 18 "description": "This is enabled by default as a power saving measure, but it breaks screensharing and websocket connections fairly often", 19 "type": "boolean", 20 "default": true 21 }, 22 "linuxAutoscroll": { 23 "displayName": "Enable middle click autoscroll on Linux", 24 "description": "Requires manual configuration of your system to disable middle click paste, has no effect on other operating systems", 25 "type": "boolean", 26 "default": false 27 }, 28 "linuxSpeechDispatcher": { 29 "displayName": "Enable speech-dispatcher for TTS on Linux", 30 "description": "Fixes text-to-speech. Has no effect on other operating systems", 31 "type": "boolean", 32 "default": true 33 }, 34 "vaapi": { 35 "displayName": "Enable VAAPI features on Linux", 36 "description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems", 37 "type": "boolean", 38 "default": true 39 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "nativeFixes", 4 "meta": { 5 "name": "Native Fixes", 6 "tagline": "Various configurable fixes for Discord and Electron", 7 + "authors": ["Cynosphere", "adryd", "NotNite"], 8 "tags": ["fixes"] 9 }, 10 + "environment": "desktop", 11 "settings": { 12 "devtoolsThemeFix": { 13 + "advice": "restart", 14 "displayName": "Devtools Theme Fix", 15 "description": "Temporary workaround for devtools defaulting to light theme on Electron 32", 16 "type": "boolean", 17 "default": true 18 }, 19 "disableRendererBackgrounding": { 20 + "advice": "restart", 21 "displayName": "Disable Renderer Backgrounding", 22 "description": "This is enabled by default as a power saving measure, but it breaks screensharing and websocket connections fairly often", 23 "type": "boolean", 24 "default": true 25 }, 26 + "vulkan": { 27 + "advice": "restart", 28 + "displayName": "Enable Vulkan renderer", 29 + "description": "Uses the Vulkan backend for rendering", 30 + "type": "boolean", 31 + "default": false 32 + }, 33 "linuxAutoscroll": { 34 + "advice": "restart", 35 "displayName": "Enable middle click autoscroll on Linux", 36 "description": "Requires manual configuration of your system to disable middle click paste, has no effect on other operating systems", 37 "type": "boolean", 38 "default": false 39 }, 40 "linuxSpeechDispatcher": { 41 + "advice": "restart", 42 "displayName": "Enable speech-dispatcher for TTS on Linux", 43 "description": "Fixes text-to-speech. Has no effect on other operating systems", 44 "type": "boolean", 45 "default": true 46 }, 47 "vaapi": { 48 + "advice": "restart", 49 "displayName": "Enable VAAPI features on Linux", 50 "description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems", 51 + "type": "boolean", 52 + "default": true 53 + }, 54 + "vaapiIgnoreDriverChecks": { 55 + "advice": "restart", 56 + "displayName": "Ignore VAAPI driver checks on Linux", 57 + "description": "Forces hardware video acceleration on some graphics drivers at the cost of stability. Has no effect on other operating systems", 58 + "type": "boolean", 59 + "default": false 60 + }, 61 + "linuxUpdater": { 62 + "advice": "restart", 63 + "displayName": "Linux Updater", 64 + "description": "Actually implements updating Discord on Linux. Has no effect on other operating systems", 65 + "type": "boolean", 66 + "default": false 67 + }, 68 + "linuxHevcSupport": { 69 + "advice": "restart", 70 + "displayName": "HEVC support on Linux", 71 + "description": "You might also need to enable Vulkan renderer. Has no effect on other operating systems", 72 "type": "boolean", 73 "default": true 74 }
+3 -3
packages/core-extensions/src/noHideToken/index.ts
··· 2 3 export const patches: Patch[] = [ 4 { 5 - find: "hideToken:function", 6 replace: { 7 - match: /(?<=hideToken:function\(\){)/, 8 - replacement: `return()=>{};` 9 } 10 } 11 ];
··· 2 3 export const patches: Patch[] = [ 4 { 5 + find: "hideToken:()=>", 6 replace: { 7 + match: /hideToken:\(\)=>.+?,/, 8 + replacement: `hideToken:()=>{},` 9 } 10 } 11 ];
+1
packages/core-extensions/src/noHideToken/manifest.json
··· 1 { 2 "id": "noHideToken", 3 "apiLevel": 2, 4 "meta": {
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "noHideToken", 4 "apiLevel": 2, 5 "meta": {
+3 -3
packages/core-extensions/src/noTrack/index.ts
··· 2 3 export const patches: Patch[] = [ 4 { 5 - find: "analyticsTrackingStoreMaker:function", 6 replace: { 7 - match: /analyticsTrackingStoreMaker:function\(\){return .+?}/, 8 - replacement: "analyticsTrackingStoreMaker:function(){return ()=>{}}" 9 } 10 }, 11 {
··· 2 3 export const patches: Patch[] = [ 4 { 5 + find: "analyticsTrackingStoreMaker:()=>", 6 replace: { 7 + match: /analyticsTrackingStoreMaker:\(\)=>.+?,/, 8 + replacement: "analyticsTrackingStoreMaker:()=>()=>{}," 9 } 10 }, 11 {
+4 -1
packages/core-extensions/src/noTrack/manifest.json
··· 1 { 2 "id": "noTrack", 3 "apiLevel": 2, 4 "meta": { ··· 9 }, 10 "blocked": [ 11 "https://*.discord.com/api/v*/science", 12 - "https://*.discord.com/api/v*/metrics" 13 ] 14 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "noTrack", 4 "apiLevel": 2, 5 "meta": { ··· 10 }, 11 "blocked": [ 12 "https://*.discord.com/api/v*/science", 13 + "https://*.discord.com/api/v*/metrics", 14 + "https://*.discordapp.com/api/v*/science", 15 + "https://*.discordapp.com/api/v*/metrics" 16 ] 17 }
+2 -6
packages/core-extensions/src/notices/index.ts
··· 4 { 5 find: ".GUILD_RAID_NOTIFICATION:", 6 replace: { 7 - match: 8 - /(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/, 9 replacement: (orig, createElement) => 10 `case "__moonlight_notice":return${createElement}(require("notices_component").default,{});${orig}` 11 } ··· 28 29 export const webpackModules: Record<string, ExtensionWebpackModule> = { 30 notices: { 31 - dependencies: [ 32 - { id: "discord/packages/flux" }, 33 - { id: "discord/Dispatcher" } 34 - ] 35 }, 36 37 component: {
··· 4 { 5 find: ".GUILD_RAID_NOTIFICATION:", 6 replace: { 7 + match: /(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/, 8 replacement: (orig, createElement) => 9 `case "__moonlight_notice":return${createElement}(require("notices_component").default,{});${orig}` 10 } ··· 27 28 export const webpackModules: Record<string, ExtensionWebpackModule> = { 29 notices: { 30 + dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }] 31 }, 32 33 component: {
+1
packages/core-extensions/src/notices/manifest.json
··· 1 { 2 "id": "notices", 3 "apiLevel": 2, 4 "meta": {
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "notices", 4 "apiLevel": 2, 5 "meta": {
+4 -10
packages/core-extensions/src/notices/webpackModules/component.tsx
··· 1 import React from "@moonlight-mod/wp/react"; 2 import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; 3 - import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 4 import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; 5 import NoticesStore from "@moonlight-mod/wp/notices_notices"; 6 - import type { Notice } from "@moonlight-mod/types/coreExtensions/notices"; 7 8 - // FIXME: types 9 - const { Notice, NoticeCloseButton, PrimaryCTANoticeButton } = Components; 10 - 11 - function popAndDismiss(notice: Notice) { 12 NoticesStore.popNotice(); 13 if (notice?.onDismiss) { 14 notice.onDismiss(); ··· 32 {notice.element} 33 34 {(notice.showClose ?? true) && ( 35 - <NoticeCloseButton 36 - onClick={() => popAndDismiss(notice)} 37 - noticeType="__moonlight_notice" 38 - /> 39 )} 40 41 {(notice.buttons ?? []).map((button) => (
··· 1 import React from "@moonlight-mod/wp/react"; 2 import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; 3 + import { Notice, NoticeCloseButton, PrimaryCTANoticeButton } from "@moonlight-mod/wp/discord/components/common/index"; 4 import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; 5 import NoticesStore from "@moonlight-mod/wp/notices_notices"; 6 + import type { Notice as NoticeType } from "@moonlight-mod/types/coreExtensions/notices"; 7 8 + function popAndDismiss(notice: NoticeType) { 9 NoticesStore.popNotice(); 10 if (notice?.onDismiss) { 11 notice.onDismiss(); ··· 29 {notice.element} 30 31 {(notice.showClose ?? true) && ( 32 + <NoticeCloseButton onClick={() => popAndDismiss(notice)} noticeType="__moonlight_notice" /> 33 )} 34 35 {(notice.buttons ?? []).map((button) => (
+1 -4
packages/core-extensions/src/notices/webpackModules/notices.ts
··· 1 import { Store } from "@moonlight-mod/wp/discord/packages/flux"; 2 import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; 3 - import type { 4 - Notice, 5 - Notices 6 - } from "@moonlight-mod/types/coreExtensions/notices"; 7 8 // very lazy way of doing this, FIXME 9 let open = false;
··· 1 import { Store } from "@moonlight-mod/wp/discord/packages/flux"; 2 import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; 3 + import type { Notice, Notices } from "@moonlight-mod/types/coreExtensions/notices"; 4 5 // very lazy way of doing this, FIXME 6 let open = false;
+31 -41
packages/core-extensions/src/quietLoggers/index.ts
··· 1 import { Patch } from "@moonlight-mod/types"; 2 3 const notXssDefensesOnly = () => 4 - (moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ?? 5 - false) === false; 6 7 // These patches MUST run before the simple patches, these are to remove loggers 8 // that end up causing syntax errors by the normal patch ··· 29 // Patches to simply remove a logger call 30 const stubPatches = [ 31 // "sh" is not a valid locale. 32 - [ 33 - "is not a valid locale", 34 - /(.)\.error\(""\.concat\((.)," is not a valid locale\."\)\)/g 35 - ], 36 - ['="RunningGameStore"', /.\.info\("games",{.+?}\),/], 37 - [ 38 - '"[BUILD INFO] Release Channel: "', 39 - /new .{1,2}\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?"\)\),/ 40 - ], 41 - [ 42 - '.APP_NATIVE_CRASH,"Storage"', 43 - /console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/ 44 - ], 45 - [ 46 - '.APP_NATIVE_CRASH,"Storage"', 47 - 'console.log("AppCrashedFatalReport: getLastCrash not supported.");' 48 - ], 49 ['"[NATIVE INFO] ', /new .{1,2}\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/], 50 ['"Spellchecker"', /.\.info\("Switching to ".+?"\(unavailable\)"\);?/g], 51 - [ 52 - 'throw Error("Messages are still loading.");', 53 - /console\.warn\("Unsupported Locale",.\),/ 54 - ], 55 - ["}_dispatchWithDevtools(", /.\.totalTime>100&&.\.verbose\(.+?\);/], 56 - [ 57 - '"NativeDispatchUtils"', 58 - /null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/ 59 - ], 60 ['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/], 61 [ 62 '"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "', 63 /.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/ 64 ], 65 - [ 66 - 'console.warn("Window state not initialized"', 67 - /console\.warn\("Window state not initialized",.\),/ 68 - ] 69 ]; 70 71 const simplePatches = [ 72 // Moment.js deprecation warnings 73 - ["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"], 74 - 75 - // Zustand related 76 - [ 77 - /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\)/g, 78 - "/*$&*/" 79 - ], 80 - ["this.getDebugLogging()", "false"] 81 ] as { [0]: string | RegExp; [1]: string }[]; 82 83 export const patches: Patch[] = [ 84 { 85 - find: ".Messages.XSSDefenses", 86 replace: { 87 match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/, 88 replacement: "(true)" 89 } 90 }, 91 ...loggerFixes, 92 ...stubPatches.map((patch) => ({
··· 1 import { Patch } from "@moonlight-mod/types"; 2 3 const notXssDefensesOnly = () => 4 + (moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ?? false) === false; 5 + 6 + const silenceDiscordLogger = moonlight.getConfigOption<boolean>("quietLoggers", "silenceDiscordLogger") ?? false; 7 8 // These patches MUST run before the simple patches, these are to remove loggers 9 // that end up causing syntax errors by the normal patch ··· 30 // Patches to simply remove a logger call 31 const stubPatches = [ 32 // "sh" is not a valid locale. 33 + ["is not a valid locale", /void (.)\.error\(""\.concat\((.)," is not a valid locale\."\)\)/g], 34 + ['"[BUILD INFO] Release Channel: "', /new .{1,2}\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?\)\),/], 35 + ['.APP_NATIVE_CRASH,"Storage"', /console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/], 36 + ['.APP_NATIVE_CRASH,"Storage"', 'void console.log("AppCrashedFatalReport: getLastCrash not supported.")'], 37 ['"[NATIVE INFO] ', /new .{1,2}\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/], 38 ['"Spellchecker"', /.\.info\("Switching to ".+?"\(unavailable\)"\);?/g], 39 + ['throw Error("Messages are still loading.");', /console\.warn\("Unsupported Locale",.\),/], 40 + ["}_dispatchWithDevtools(", /.\.totalTime>.{1,2}&&.\.verbose\(.+?\);/], 41 + ['"NativeDispatchUtils"', /null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/], 42 ['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/], 43 [ 44 '"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "', 45 /.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/ 46 ], 47 + ['console.warn("Window state not initialized"', /console\.warn\("Window state not initialized",.\),/] 48 ]; 49 50 const simplePatches = [ 51 // Moment.js deprecation warnings 52 + ["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"] 53 ] as { [0]: string | RegExp; [1]: string }[]; 54 55 export const patches: Patch[] = [ 56 { 57 + find: ".Messages.SELF_XSS_HEADER", 58 replace: { 59 match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/, 60 replacement: "(true)" 61 } 62 + }, 63 + // Highlight.js deprecation warnings 64 + { 65 + find: "Deprecated as of", 66 + replace: { 67 + match: /console\./g, 68 + replacement: "false&&console." 69 + }, 70 + prerequisite: notXssDefensesOnly 71 + }, 72 + // Discord's logger 73 + { 74 + find: "ฮฃ:", 75 + replace: { 76 + match: "for", 77 + replacement: "return;for" 78 + }, 79 + prerequisite: () => silenceDiscordLogger && notXssDefensesOnly() 80 }, 81 ...loggerFixes, 82 ...stubPatches.map((patch) => ({
+9
packages/core-extensions/src/quietLoggers/manifest.json
··· 1 { 2 "id": "quietLoggers", 3 "apiLevel": 2, 4 "meta": { ··· 9 }, 10 "settings": { 11 "xssDefensesOnly": { 12 "displayName": "Only hide self-XSS", 13 "description": "Only disable self XSS prevention log", 14 "type": "boolean", 15 "default": false 16 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "quietLoggers", 4 "apiLevel": 2, 5 "meta": { ··· 10 }, 11 "settings": { 12 "xssDefensesOnly": { 13 + "advice": "reload", 14 "displayName": "Only hide self-XSS", 15 "description": "Only disable self XSS prevention log", 16 + "type": "boolean", 17 + "default": false 18 + }, 19 + "silenceDiscordLogger": { 20 + "advice": "reload", 21 + "displayName": "Silence Discord logger", 22 + "description": "Hides all messages from Discord's logger (the logs that start with purple text in brackets)", 23 "type": "boolean", 24 "default": false 25 }
+46 -67
packages/core-extensions/src/rocketship/host/permissions.ts
··· 14 details: Electron.PermissionCheckHandlerHandlerDetails 15 ) => boolean; 16 17 - moonlightHost.events.on( 18 - "window-created", 19 - (window: BrowserWindow, isMainWindow: boolean) => { 20 - if (!isMainWindow) return; 21 - const windowSession = window.webContents.session; 22 23 - // setPermissionRequestHandler 24 - windowSession.setPermissionRequestHandler( 25 - (webcontents, permission, callback, details) => { 26 - let cbResult = false; 27 - function fakeCallback(result: boolean) { 28 - cbResult = result; 29 - } 30 31 - if (caughtPermissionRequestHandler) { 32 - caughtPermissionRequestHandler( 33 - webcontents, 34 - permission, 35 - fakeCallback, 36 - details 37 - ); 38 - } 39 40 - if (permission === "media" || permission === "display-capture") { 41 - cbResult = true; 42 - } 43 44 - callback(cbResult); 45 - } 46 - ); 47 48 - let caughtPermissionRequestHandler: PermissionRequestHandler | undefined; 49 50 - windowSession.setPermissionRequestHandler = 51 - function catchSetPermissionRequestHandler( 52 - handler: ( 53 - webcontents: Electron.WebContents, 54 - permission: string, 55 - callback: (permissionGranted: boolean) => void 56 - ) => void 57 - ) { 58 - caughtPermissionRequestHandler = handler; 59 - }; 60 61 - // setPermissionCheckHandler 62 - windowSession.setPermissionCheckHandler( 63 - (webcontents, permission, requestingOrigin, details) => { 64 - return false; 65 - } 66 - ); 67 68 - let caughtPermissionCheckHandler: PermissionCheckHandler | undefined; 69 70 - windowSession.setPermissionCheckHandler( 71 - (webcontents, permission, requestingOrigin, details) => { 72 - let result = false; 73 74 - if (caughtPermissionCheckHandler) { 75 - result = caughtPermissionCheckHandler( 76 - webcontents, 77 - permission, 78 - requestingOrigin, 79 - details 80 - ); 81 - } 82 83 - if (permission === "media" || permission === "display-capture") { 84 - result = true; 85 - } 86 87 - return result; 88 - } 89 - ); 90 91 - windowSession.setPermissionCheckHandler = 92 - function catchSetPermissionCheckHandler(handler: PermissionCheckHandler) { 93 - caughtPermissionCheckHandler = handler; 94 - }; 95 - } 96 - );
··· 14 details: Electron.PermissionCheckHandlerHandlerDetails 15 ) => boolean; 16 17 + moonlightHost.events.on("window-created", (window: BrowserWindow, isMainWindow: boolean) => { 18 + if (!isMainWindow) return; 19 + const windowSession = window.webContents.session; 20 21 + // setPermissionRequestHandler 22 + windowSession.setPermissionRequestHandler((webcontents, permission, callback, details) => { 23 + let cbResult = false; 24 + function fakeCallback(result: boolean) { 25 + cbResult = result; 26 + } 27 28 + if (caughtPermissionRequestHandler) { 29 + caughtPermissionRequestHandler(webcontents, permission, fakeCallback, details); 30 + } 31 32 + if (permission === "media" || permission === "display-capture") { 33 + cbResult = true; 34 + } 35 36 + callback(cbResult); 37 + }); 38 39 + let caughtPermissionRequestHandler: PermissionRequestHandler | undefined; 40 41 + windowSession.setPermissionRequestHandler = function catchSetPermissionRequestHandler( 42 + handler: ( 43 + webcontents: Electron.WebContents, 44 + permission: string, 45 + callback: (permissionGranted: boolean) => void 46 + ) => void 47 + ) { 48 + caughtPermissionRequestHandler = handler; 49 + }; 50 51 + // setPermissionCheckHandler 52 + windowSession.setPermissionCheckHandler((webcontents, permission, requestingOrigin, details) => { 53 + return false; 54 + }); 55 56 + let caughtPermissionCheckHandler: PermissionCheckHandler | undefined; 57 58 + windowSession.setPermissionCheckHandler((webcontents, permission, requestingOrigin, details) => { 59 + let result = false; 60 61 + if (caughtPermissionCheckHandler) { 62 + result = caughtPermissionCheckHandler(webcontents, permission, requestingOrigin, details); 63 + } 64 65 + if (permission === "media" || permission === "display-capture") { 66 + result = true; 67 + } 68 69 + return result; 70 + }); 71 72 + windowSession.setPermissionCheckHandler = function catchSetPermissionCheckHandler(handler: PermissionCheckHandler) { 73 + caughtPermissionCheckHandler = handler; 74 + }; 75 + });
+4 -12
packages/core-extensions/src/rocketship/host/types.ts
··· 1 // https://github.com/Vencord/venmic/blob/d737ef33eaae7a73d03ec02673e008cf0243434d/lib/module.d.ts 2 type DefaultProps = "node.name" | "application.name"; 3 4 - type LiteralUnion<LiteralType, BaseType extends string> = 5 - | LiteralType 6 - | (BaseType & Record<never, never>); 7 8 - type Optional<Type, Key extends keyof Type> = Partial<Pick<Type, Key>> & 9 - Omit<Type, Key>; 10 11 - export type Node<T extends string = never> = Record< 12 - LiteralUnion<T, string>, 13 - string 14 - >; 15 16 export interface LinkData { 17 include: Node[]; ··· 29 unlink(): void; 30 31 list<T extends string = DefaultProps>(props?: T[]): Node<T>[]; 32 - link( 33 - data: Optional<LinkData, "exclude"> | Optional<LinkData, "include"> 34 - ): boolean; 35 }
··· 1 // https://github.com/Vencord/venmic/blob/d737ef33eaae7a73d03ec02673e008cf0243434d/lib/module.d.ts 2 type DefaultProps = "node.name" | "application.name"; 3 4 + type LiteralUnion<LiteralType, BaseType extends string> = LiteralType | (BaseType & Record<never, never>); 5 6 + type Optional<Type, Key extends keyof Type> = Partial<Pick<Type, Key>> & Omit<Type, Key>; 7 8 + export type Node<T extends string = never> = Record<LiteralUnion<T, string>, string>; 9 10 export interface LinkData { 11 include: Node[]; ··· 23 unlink(): void; 24 25 list<T extends string = DefaultProps>(props?: T[]): Node<T>[]; 26 + link(data: Optional<LinkData, "exclude"> | Optional<LinkData, "include">): boolean; 27 }
+23 -31
packages/core-extensions/src/rocketship/host/venmic.ts
··· 7 8 function getPatchbay() { 9 try { 10 - const venmic = require( 11 - path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node") 12 - ) as { PatchBay: new () => PatchBay }; 13 const patchbay = new venmic.PatchBay(); 14 return patchbay; 15 } catch (error) { ··· 35 36 patchbay.unlink(); 37 return patchbay.link({ 38 - exclude: [ 39 - { "application.process.id": pid }, 40 - { "media.class": "Stream/Input/Audio" } 41 - ], 42 ignore_devices: true, 43 only_speakers: true, 44 only_default_speakers: true ··· 49 } 50 } 51 52 - moonlightHost.events.on( 53 - "window-created", 54 - (window: BrowserWindow, isMainWindow: boolean) => { 55 - if (!isMainWindow) return; 56 - const windowSession = window.webContents.session; 57 58 - // @ts-expect-error these types ancient 59 - windowSession.setDisplayMediaRequestHandler( 60 - (request: any, callback: any) => { 61 - const linked = linkVenmic(); 62 - desktopCapturer 63 - .getSources({ types: ["screen", "window"] }) 64 - .then((sources) => { 65 - //logger.debug("desktopCapturer.getSources", sources); 66 - logger.debug("Linked to venmic:", linked); 67 68 - callback({ 69 - video: sources[0], 70 - audio: "loopback" 71 - }); 72 - }); 73 - }, 74 - { useSystemPicker: true } 75 - ); 76 - } 77 - );
··· 7 8 function getPatchbay() { 9 try { 10 + const venmic = require(path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node")) as { 11 + PatchBay: new () => PatchBay; 12 + }; 13 const patchbay = new venmic.PatchBay(); 14 return patchbay; 15 } catch (error) { ··· 35 36 patchbay.unlink(); 37 return patchbay.link({ 38 + exclude: [{ "application.process.id": pid }, { "media.class": "Stream/Input/Audio" }], 39 ignore_devices: true, 40 only_speakers: true, 41 only_default_speakers: true ··· 46 } 47 } 48 49 + moonlightHost.events.on("window-created", (window: BrowserWindow, isMainWindow: boolean) => { 50 + if (!isMainWindow) return; 51 + const windowSession = window.webContents.session; 52 53 + // @ts-expect-error these types ancient 54 + windowSession.setDisplayMediaRequestHandler( 55 + (request: any, callback: any) => { 56 + const linked = linkVenmic(); 57 + desktopCapturer.getSources({ types: ["screen", "window"] }).then((sources) => { 58 + //logger.debug("desktopCapturer.getSources", sources); 59 + logger.debug("Linked to venmic:", linked); 60 61 + callback({ 62 + video: sources[0], 63 + audio: "loopback" 64 + }); 65 + }); 66 + }, 67 + { useSystemPicker: true } 68 + ); 69 + });
+5 -11
packages/core-extensions/src/rocketship/index.ts
··· 9 logger.debug("Devices:", devices); 10 11 // This isn't vencord :( 12 - const id = devices.find((device) => device.label === "vencord-screen-share") 13 - ?.deviceId; 14 if (!id) return null; 15 logger.debug("Got venmic device ID:", id); 16 ··· 32 } 33 } 34 35 - navigator.mediaDevices.getDisplayMedia = async function getDisplayMediaRedirect( 36 - options 37 - ) { 38 const orig = await getDisplayMediaOrig.call(this, options); 39 40 const venmic = await getVenmicStream(); ··· 114 replace: [ 115 // Prevent loading of krisp native module by stubbing out desktop checks 116 { 117 - match: 118 - /\(\(0,.\.isWindows\)\(\)\|\|\(0,.\.isLinux\)\(\)\|\|.+?&&!__OVERLAY__/, 119 replacement: (orig, macosPlatformCheck) => `false&&!__OVERLAY__` 120 }, 121 // Enable loading of web krisp equivelant by replacing isWeb with true 122 { 123 - match: 124 - /\(0,.\.isWeb\)\(\)&&(.{1,2}\.supports\(.{1,2}\..{1,2}.NOISE_CANCELLATION)/, 125 - replacement: (orig, supportsNoiseCancellation) => 126 - `true&&${supportsNoiseCancellation}` 127 } 128 ] 129 }
··· 9 logger.debug("Devices:", devices); 10 11 // This isn't vencord :( 12 + const id = devices.find((device) => device.label === "vencord-screen-share")?.deviceId; 13 if (!id) return null; 14 logger.debug("Got venmic device ID:", id); 15 ··· 31 } 32 } 33 34 + navigator.mediaDevices.getDisplayMedia = async function getDisplayMediaRedirect(options) { 35 const orig = await getDisplayMediaOrig.call(this, options); 36 37 const venmic = await getVenmicStream(); ··· 111 replace: [ 112 // Prevent loading of krisp native module by stubbing out desktop checks 113 { 114 + match: /\(\(0,.\.isWindows\)\(\)\|\|\(0,.\.isLinux\)\(\)\|\|.+?&&!__OVERLAY__/, 115 replacement: (orig, macosPlatformCheck) => `false&&!__OVERLAY__` 116 }, 117 // Enable loading of web krisp equivelant by replacing isWeb with true 118 { 119 + match: /\(0,.\.isWeb\)\(\)&&(.{1,2}\.supports\(.{1,2}\..{1,2}.NOISE_CANCELLATION)/, 120 + replacement: (orig, supportsNoiseCancellation) => `true&&${supportsNoiseCancellation}` 121 } 122 ] 123 }
+4 -1
packages/core-extensions/src/rocketship/manifest.json
··· 1 { 2 "id": "rocketship", 3 "apiLevel": 2, 4 "meta": { 5 "name": "Rocketship", 6 "tagline": "Adds new features when using rocketship", 7 "description": "**This extension only works on Linux when using rocketship:**\nhttps://github.com/moonlight-mod/rocketship\n\nAdds new features to the Discord Linux client with rocketship, such as a better screensharing experience.", 8 - "authors": ["NotNite", "Cynosphere", "adryd"] 9 } 10 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "rocketship", 4 "apiLevel": 2, 5 + "environment": "desktop", 6 "meta": { 7 "name": "Rocketship", 8 "tagline": "Adds new features when using rocketship", 9 "description": "**This extension only works on Linux when using rocketship:**\nhttps://github.com/moonlight-mod/rocketship\n\nAdds new features to the Discord Linux client with rocketship, such as a better screensharing experience.", 10 + "authors": ["NotNite", "Cynosphere", "adryd"], 11 + "deprecated": true 12 } 13 }
+3 -5
packages/core-extensions/src/settings/index.ts
··· 6 find: '"useGenerateUserSettingsSections"', 7 replace: { 8 match: /(?<=\.push\(.+?\)}\)\)}\),)(.+?)}/, 9 - replacement: (_, sections: string) => 10 - `require("settings_settings").Settings._mutateSections(${sections})}` 11 } 12 }, 13 { 14 find: 'navId:"user-settings-cog",', 15 replace: { 16 - match: /children:\[(.)\.map\(.+?\),children:.\((.)\)/, 17 replacement: (orig, sections, section) => 18 `${orig.replace( 19 /Object\.values\(.\..+?\)/, 20 - (orig) => 21 - `[...require("settings_settings").Settings.sectionNames,...${orig}]` 22 )}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()` 23 } 24 }
··· 6 find: '"useGenerateUserSettingsSections"', 7 replace: { 8 match: /(?<=\.push\(.+?\)}\)\)}\),)(.+?)}/, 9 + replacement: (_, sections: string) => `require("settings_settings").Settings._mutateSections(${sections})}` 10 } 11 }, 12 { 13 find: 'navId:"user-settings-cog",', 14 replace: { 15 + match: /children:\[(\i)\.map\(.+?\),.*?children:\i\((\i)\)/, 16 replacement: (orig, sections, section) => 17 `${orig.replace( 18 /Object\.values\(.\..+?\)/, 19 + (orig) => `[...require("settings_settings").Settings.sectionNames,...${orig}]` 20 )}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()` 21 } 22 }
+1
packages/core-extensions/src/settings/manifest.json
··· 1 { 2 "id": "settings", 3 "apiLevel": 2, 4 "meta": {
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "settings", 4 "apiLevel": 2, 5 "meta": {
+11 -13
packages/core-extensions/src/settings/webpackModules/settings.ts
··· 1 - import { 2 - SettingsSection, 3 - Settings as SettingsType 4 - } from "@moonlight-mod/types/coreExtensions/settings"; 5 6 export const Settings: SettingsType = { 7 ourSections: [], 8 sectionNames: [], 9 sectionMenuItems: {}, 10 11 - addSection: (section, label, element, color = null, pos, notice) => { 12 const data: SettingsSection = { 13 section, 14 label, 15 color, 16 element, 17 pos: pos ?? -4, 18 - notice: notice 19 }; 20 21 Settings.ourSections.push(data); ··· 24 }, 25 addSectionMenuItems(section, ...newItems) { 26 const data = Settings.ourSections.find((x) => x.section === section); 27 - if (!data || !("element" in data)) 28 - throw new Error(`Could not find section "${section}"`); 29 (Settings.sectionMenuItems[section] ??= []).push(...newItems); 30 data._moonlight_submenu ??= () => Settings.sectionMenuItems[section]; 31 }, ··· 47 48 _mutateSections: (sections) => { 49 for (const section of Settings.ourSections) { 50 - sections.splice( 51 - section.pos < 0 ? sections.length + section.pos : section.pos, 52 - 0, 53 - section 54 - ); 55 } 56 57 return sections;
··· 1 + import { SettingsSection, Settings as SettingsType } from "@moonlight-mod/types/coreExtensions/settings"; 2 + import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators"; 3 4 export const Settings: SettingsType = { 5 ourSections: [], 6 sectionNames: [], 7 sectionMenuItems: {}, 8 9 + addSection: (section, label, element, color = null, pos, notice, onClick) => { 10 const data: SettingsSection = { 11 section, 12 label, 13 color, 14 element, 15 pos: pos ?? -4, 16 + notice: notice, 17 + onClick: onClick ?? (() => UserSettingsModalActionCreators.open(section)) 18 }; 19 20 Settings.ourSections.push(data); ··· 23 }, 24 addSectionMenuItems(section, ...newItems) { 25 const data = Settings.ourSections.find((x) => x.section === section); 26 + if (!data || !("element" in data)) throw new Error(`Could not find section "${section}"`); 27 (Settings.sectionMenuItems[section] ??= []).push(...newItems); 28 data._moonlight_submenu ??= () => Settings.sectionMenuItems[section]; 29 }, ··· 45 46 _mutateSections: (sections) => { 47 for (const section of Settings.ourSections) { 48 + // Discord's `pos` only supports numbers, so lets call the function to get the position. 49 + if (typeof section.pos === "function") { 50 + section.pos = section.pos(sections); 51 + } 52 + sections.splice(section.pos < 0 ? sections.length + section.pos : section.pos, 0, section); 53 } 54 55 return sections;
+2
packages/core-extensions/src/spacepack/manifest.json
··· 1 { 2 "id": "spacepack", 3 "apiLevel": 2, 4 "meta": { ··· 9 }, 10 "settings": { 11 "addToGlobalScope": { 12 "displayName": "Add to global scope", 13 "description": "Populates window.spacepack for easier usage in DevTools", 14 "type": "boolean",
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "spacepack", 4 "apiLevel": 2, 5 "meta": { ··· 10 }, 11 "settings": { 12 "addToGlobalScope": { 13 + "advice": "reload", 14 "displayName": "Add to global scope", 15 "description": "Populates window.spacepack for easier usage in DevTools", 16 "type": "boolean",
+84 -67
packages/core-extensions/src/spacepack/webpackModules/spacepack.ts
··· 1 - import { 2 - WebpackModule, 3 - WebpackModuleFunc, 4 - WebpackRequireType 5 - } from "@moonlight-mod/types"; 6 import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack"; 7 8 const webpackRequire = require as unknown as WebpackRequireType; 9 const cache = webpackRequire.c; ··· 40 "module", 41 "exports", 42 "require", 43 - `(${funcStr}).apply(this, arguments)\n` + 44 - `//# sourceURL=Webpack-Module-${module}` 45 ) as WebpackModuleFunc; 46 }, 47 48 findByCode: (...args: (string | RegExp)[]) => { 49 - return Object.entries(modules) 50 - .filter( 51 - ([id, mod]) => 52 - !args.some( 53 - (item) => 54 - !(item instanceof RegExp 55 - ? item.test(mod.toString()) 56 - : mod.toString().indexOf(item) !== -1) 57 - ) 58 - ) 59 .map(([id]) => { 60 //if (!(id in cache)) require(id); 61 //return cache[id]; ··· 64 try { 65 exports = require(id); 66 } catch (e) { 67 - logger.error(`Error requiring module "${id}": `, e); 68 } 69 70 return { ··· 73 }; 74 }) 75 .filter((item) => item !== null); 76 }, 77 78 findByExports: (...args: string[]) => { ··· 84 !( 85 exports !== undefined && 86 exports !== window && 87 - (exports?.[item] || 88 - exports?.default?.[item] || 89 - exports?.Z?.[item] || 90 - exports?.ZP?.[item]) 91 ) 92 ) 93 ) ··· 99 }, 100 101 findObjectFromKey: (exports: Record<string, any>, key: string) => { 102 let subKey; 103 if (key.indexOf(".") > -1) { 104 const splitKey = key.split("."); ··· 109 const obj = exports[exportKey]; 110 if (obj && obj[key] !== undefined) { 111 if (subKey) { 112 - if (obj[key][subKey]) return obj; 113 } else { 114 - return obj; 115 } 116 } 117 } 118 - return null; 119 }, 120 121 findObjectFromValue: (exports: Record<string, any>, value: any) => { 122 for (const exportKey in exports) { 123 const obj = exports[exportKey]; 124 // eslint-disable-next-line eqeqeq 125 - if (obj == value) return obj; 126 for (const subKey in obj) { 127 // eslint-disable-next-line eqeqeq 128 if (obj && obj[subKey] == value) { 129 - return obj; 130 } 131 } 132 } 133 - return null; 134 }, 135 136 - findObjectFromKeyValuePair: ( 137 - exports: Record<string, any>, 138 - key: string, 139 - value: any 140 - ) => { 141 for (const exportKey in exports) { 142 const obj = exports[exportKey]; 143 // eslint-disable-next-line eqeqeq 144 if (obj && obj[key] == value) { 145 - return obj; 146 } 147 } 148 return null; 149 }, 150 151 - findFunctionByStrings: ( 152 - exports: Record<string, any>, 153 - ...strings: (string | RegExp)[] 154 - ) => { 155 - return ( 156 Object.entries(exports).filter( 157 ([index, func]) => 158 - typeof func === "function" && 159 - !strings.some( 160 - (query) => 161 - !(query instanceof RegExp 162 - ? func.toString().match(query) 163 - : func.toString().includes(query)) 164 - ) 165 - )?.[0]?.[1] ?? null 166 - ); 167 }, 168 169 - lazyLoad: ( 170 - find: string | RegExp | (string | RegExp)[], 171 - chunk: RegExp, 172 - module: RegExp 173 - ) => { 174 - const mod = Array.isArray(find) 175 - ? spacepack.findByCode(...find) 176 - : spacepack.findByCode(find); 177 - if (mod.length < 1) return Promise.reject("Module find failed"); 178 179 const findId = mod[0].id; 180 const findCode = webpackRequire.m[findId].toString().replace(/\n/g, ""); ··· 184 chunkIds = [...findCode.matchAll(chunk)].map(([, id]) => id); 185 } else { 186 const match = findCode.match(chunk); 187 - if (match) 188 - chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id); 189 } 190 191 - if (!chunkIds || chunkIds.length === 0) 192 return Promise.reject("Chunk ID match failed"); 193 194 const moduleId = findCode.match(module)?.[1]; 195 - if (!moduleId) return Promise.reject("Module ID match failed"); 196 197 - return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() => 198 - webpackRequire(moduleId) 199 - ); 200 }, 201 202 filterReal: (modules: WebpackModule[]) => { ··· 204 } 205 }; 206 207 - if ( 208 - moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true 209 - ) { 210 window.spacepack = spacepack; 211 } 212
··· 1 + import { WebpackModule, WebpackModuleFunc, WebpackRequireType } from "@moonlight-mod/types"; 2 import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack"; 3 + import { processFind, testFind } from "@moonlight-mod/core/util/patch"; 4 5 const webpackRequire = require as unknown as WebpackRequireType; 6 const cache = webpackRequire.c; ··· 37 "module", 38 "exports", 39 "require", 40 + `(${funcStr}).apply(this, arguments)\n` + `//# sourceURL=Webpack-Module/${module.slice(0, 3)}/${module}` 41 ) as WebpackModuleFunc; 42 }, 43 44 findByCode: (...args: (string | RegExp)[]) => { 45 + const ret = Object.entries(modules) 46 + .filter(([id, mod]) => !args.some((item) => !testFind(mod.toString(), processFind(item)))) 47 .map(([id]) => { 48 //if (!(id in cache)) require(id); 49 //return cache[id]; ··· 52 try { 53 exports = require(id); 54 } catch (e) { 55 + logger.error(`findByCode: Error requiring module "${id}": `, args, e); 56 } 57 58 return { ··· 61 }; 62 }) 63 .filter((item) => item !== null); 64 + 65 + if (ret.length === 0) { 66 + logger.warn("findByCode: Got zero results for", args, new Error().stack!.substring(5)); 67 + } 68 + 69 + return ret; 70 }, 71 72 findByExports: (...args: string[]) => { ··· 78 !( 79 exports !== undefined && 80 exports !== window && 81 + (exports?.[item] || exports?.default?.[item] || exports?.Z?.[item] || exports?.ZP?.[item]) 82 ) 83 ) 84 ) ··· 90 }, 91 92 findObjectFromKey: (exports: Record<string, any>, key: string) => { 93 + let ret = null; 94 let subKey; 95 if (key.indexOf(".") > -1) { 96 const splitKey = key.split("."); ··· 101 const obj = exports[exportKey]; 102 if (obj && obj[key] !== undefined) { 103 if (subKey) { 104 + if (obj[key][subKey]) { 105 + ret = obj; 106 + break; 107 + } 108 } else { 109 + ret = obj; 110 + break; 111 } 112 } 113 } 114 + 115 + if (ret == null) { 116 + logger.warn("Failed to find object by key", key, "in", exports, new Error().stack!.substring(5)); 117 + } 118 + 119 + return ret; 120 }, 121 122 findObjectFromValue: (exports: Record<string, any>, value: any) => { 123 + let ret = null; 124 for (const exportKey in exports) { 125 const obj = exports[exportKey]; 126 // eslint-disable-next-line eqeqeq 127 + if (obj == value) { 128 + ret = obj; 129 + break; 130 + } 131 for (const subKey in obj) { 132 // eslint-disable-next-line eqeqeq 133 if (obj && obj[subKey] == value) { 134 + ret = obj; 135 + break; 136 } 137 } 138 } 139 + 140 + if (ret == null) { 141 + logger.warn("Failed to find object by value", value, "in", exports, new Error().stack!.substring(5)); 142 + } 143 + 144 + return ret; 145 }, 146 147 + findObjectFromKeyValuePair: (exports: Record<string, any>, key: string, value: any) => { 148 + let ret = null; 149 for (const exportKey in exports) { 150 const obj = exports[exportKey]; 151 // eslint-disable-next-line eqeqeq 152 if (obj && obj[key] == value) { 153 + ret = obj; 154 + break; 155 } 156 } 157 + 158 + if (ret == null) { 159 + logger.warn( 160 + "Failed to find object by key value pair", 161 + key, 162 + value, 163 + "in", 164 + exports, 165 + new Error().stack!.substring(5) 166 + ); 167 + } 168 + 169 return null; 170 }, 171 172 + findFunctionByStrings: (exports: Record<string, any>, ...strings: (string | RegExp)[]) => { 173 + const ret = 174 Object.entries(exports).filter( 175 ([index, func]) => 176 + typeof func === "function" && !strings.some((query) => !testFind(func.toString(), processFind(query))) 177 + )?.[0]?.[1] ?? null; 178 + 179 + if (ret == null) { 180 + logger.warn("Failed to find function by strings", strings, "in", exports, new Error().stack!.substring(5)); 181 + } 182 + 183 + return ret; 184 }, 185 186 + lazyLoad: (find: string | RegExp | (string | RegExp)[], chunk: RegExp, module: RegExp) => { 187 + chunk = processFind(chunk); 188 + module = processFind(module); 189 + 190 + const mod = Array.isArray(find) ? spacepack.findByCode(...find) : spacepack.findByCode(find); 191 + if (mod.length < 1) { 192 + logger.warn("lazyLoad: Module find failed", find, chunk, module, new Error().stack!.substring(5)); 193 + return Promise.reject("Module find failed"); 194 + } 195 196 const findId = mod[0].id; 197 const findCode = webpackRequire.m[findId].toString().replace(/\n/g, ""); ··· 201 chunkIds = [...findCode.matchAll(chunk)].map(([, id]) => id); 202 } else { 203 const match = findCode.match(chunk); 204 + if (match) chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id); 205 } 206 207 + if (!chunkIds || chunkIds.length === 0) { 208 + logger.warn("lazyLoad: Chunk ID match failed", find, chunk, module, new Error().stack!.substring(5)); 209 return Promise.reject("Chunk ID match failed"); 210 + } 211 212 const moduleId = findCode.match(module)?.[1]; 213 + if (!moduleId) { 214 + logger.warn("lazyLoad: Module ID match failed", find, chunk, module, new Error().stack!.substring(5)); 215 + return Promise.reject("Module ID match failed"); 216 + } 217 218 + return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() => webpackRequire(moduleId)); 219 }, 220 221 filterReal: (modules: WebpackModule[]) => { ··· 223 } 224 }; 225 226 + if (moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true) { 227 window.spacepack = spacepack; 228 } 229
+4 -1
packages/core-extensions/tsconfig.json
··· 1 { 2 - "extends": "../../tsconfig.json" 3 }
··· 1 { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["ESNext", "DOM", "DOM.Iterable"] 5 + } 6 }
+10 -3
packages/injector/package.json
··· 1 { 2 "name": "@moonlight-mod/injector", 3 "private": true, 4 "dependencies": { 5 - "@moonlight-mod/types": "workspace:*", 6 - "@moonlight-mod/core": "workspace:*" 7 - } 8 }
··· 1 { 2 "name": "@moonlight-mod/injector", 3 "private": true, 4 + "engines": { 5 + "node": ">=22", 6 + "pnpm": ">=10", 7 + "npm": "pnpm", 8 + "yarn": "pnpm" 9 + }, 10 "dependencies": { 11 + "@moonlight-mod/core": "workspace:*", 12 + "@moonlight-mod/types": "workspace:*" 13 + }, 14 + "engineStrict": true 15 }
+126 -119
packages/injector/src/index.ts
··· 6 } from "electron"; 7 import Module from "node:module"; 8 import { constants, MoonlightBranch } from "@moonlight-mod/types"; 9 - import { readConfig } from "@moonlight-mod/core/config"; 10 import { getExtensions } from "@moonlight-mod/core/extension"; 11 import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; 12 - import { 13 - loadExtensions, 14 - loadProcessedExtensions 15 - } from "@moonlight-mod/core/extension/loader"; 16 import EventEmitter from "node:events"; 17 - import { join, resolve } from "node:path"; 18 import persist from "@moonlight-mod/core/persist"; 19 import createFS from "@moonlight-mod/core/fs"; 20 21 const logger = new Logger("injector"); 22 23 let oldPreloadPath: string | undefined; 24 let corsAllow: string[] = []; 25 let blockedUrls: RegExp[] = []; 26 - let isMoonlightDesktop = false; 27 - let hasOpenAsar = false; 28 - let openAsarConfigPreload: string | undefined; 29 30 ipcMain.on(constants.ipcGetOldPreloadPath, (e) => { 31 e.returnValue = oldPreloadPath; 32 }); 33 ipcMain.on(constants.ipcGetAppData, (e) => { 34 e.returnValue = app.getPath("appData"); 35 }); 36 - ipcMain.on(constants.ipcGetIsMoonlightDesktop, (e) => { 37 - e.returnValue = isMoonlightDesktop; 38 }); 39 ipcMain.handle(constants.ipcMessageBox, (_, opts) => { 40 electron.dialog.showMessageBoxSync(opts); ··· 44 }); 45 46 const reEscapeRegExp = /[\\^$.*+?()[\]{}|]/g; 47 - const reMatchPattern = 48 - /^(?<scheme>\*|[a-z][a-z0-9+.-]*):\/\/(?<host>.+?)\/(?<path>.+)?$/; 49 50 const escapeRegExp = (s: string) => s.replace(reEscapeRegExp, "\\$&"); 51 ipcMain.handle(constants.ipcSetBlockedList, (_, list: string[]) => { ··· 76 blockedUrls = compiled; 77 }); 78 79 - function patchCsp(headers: Record<string, string[]>) { 80 - const directives = [ 81 - "style-src", 82 - "connect-src", 83 - "img-src", 84 - "font-src", 85 - "media-src", 86 - "worker-src", 87 - "prefetch-src" 88 - ]; 89 - const values = ["*", "blob:", "data:", "'unsafe-inline'", "disclip:"]; 90 91 const csp = "content-security-policy"; 92 if (headers[csp] == null) return; ··· 105 parts[directive] = values; 106 } 107 108 const stringified = Object.entries<string[]>(parts) 109 .map(([key, value]) => { 110 return `${key} ${value.join(" ")}`; ··· 113 headers[csp] = [stringified]; 114 } 115 116 - function removeOpenAsarEventIfPresent(eventHandler: (...args: any[]) => void) { 117 - const code = eventHandler.toString(); 118 - if (code.indexOf("bw.webContents.on('dom-ready'") > -1) { 119 - electron.app.off("browser-window-created", eventHandler); 120 - } 121 - } 122 - 123 class BrowserWindow extends ElectronBrowserWindow { 124 constructor(opts: BrowserWindowConstructorOptions) { 125 - oldPreloadPath = opts.webPreferences!.preload; 126 127 - const isMainWindow = 128 - opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1; 129 - 130 - if (isMainWindow) 131 opts.webPreferences!.preload = require.resolve("./node-preload.js"); 132 133 // Event for modifying window options 134 moonlightHost.events.emit("window-options", opts, isMainWindow); ··· 138 // Event for when a window is created 139 moonlightHost.events.emit("window-created", this, isMainWindow); 140 141 this.webContents.session.webRequest.onHeadersReceived((details, cb) => { 142 if (details.responseHeaders != null) { 143 // Patch CSP so things can use externally hosted assets 144 if (details.resourceType === "mainFrame") { 145 - patchCsp(details.responseHeaders); 146 } 147 148 // Allow plugins to bypass CORS for specific URLs 149 if (corsAllow.some((x) => details.url.startsWith(x))) { 150 - details.responseHeaders["access-control-allow-origin"] = ["*"]; 151 } 152 153 cb({ cancel: false, responseHeaders: details.responseHeaders }); 154 } 155 }); 156 157 - // Allow plugins to block some URLs, 158 - // this is needed because multiple webRequest handlers cannot be registered at once 159 this.webContents.session.webRequest.onBeforeRequest((details, cb) => { 160 - cb({ cancel: blockedUrls.some((u) => u.test(details.url)) }); 161 - }); 162 163 - if (hasOpenAsar) { 164 - // Remove DOM injections 165 - // Settings can still be opened via: 166 - // `DiscordNative.ipc.send("DISCORD_UPDATED_QUOTES","o")` 167 - // @ts-expect-error Electron internals 168 - const events = electron.app._events["browser-window-created"]; 169 - if (Array.isArray(events)) { 170 - for (const event of events) { 171 - removeOpenAsarEventIfPresent(event); 172 } 173 - } else if (events != null) { 174 - removeOpenAsarEventIfPresent(events); 175 - } 176 177 - // Config screen fails to context bridge properly 178 - // Less than ideal, but better than disabling it everywhere 179 - if (opts.webPreferences!.preload === openAsarConfigPreload) { 180 - opts.webPreferences!.sandbox = false; 181 } 182 - } 183 } 184 } 185 ··· 200 writable: false 201 }); 202 203 - export async function inject(asarPath: string) { 204 - isMoonlightDesktop = asarPath === "moonlightDesktop"; 205 - global.moonlightFS = createFS(); 206 207 try { 208 - const config = await readConfig(); 209 initLogger(config); 210 const extensions = await getExtensions(); 211 212 // Duplicated in node-preload... oops 213 - // eslint-disable-next-line no-inner-declarations 214 function getConfig(ext: string) { 215 const val = config.extensions[ext]; 216 if (val == null || typeof val === "boolean") return undefined; 217 return val.config; 218 } 219 - 220 global.moonlightHost = { 221 asarPath, 222 - config, 223 events: new EventEmitter(), 224 - extensions, 225 - processedExtensions: { 226 - extensions: [], 227 - dependencyGraph: new Map() 228 - }, 229 230 version: MOONLIGHT_VERSION, 231 branch: MOONLIGHT_BRANCH as MoonlightBranch, 232 233 getConfig, 234 - getConfigOption: <T>(ext: string, name: string) => { 235 - const config = getConfig(ext); 236 - if (config == null) return undefined; 237 - const option = config[name]; 238 - if (option == null) return undefined; 239 - return option as T; 240 }, 241 - getLogger: (id: string) => { 242 return new Logger(id); 243 } 244 }; 245 246 - // Check if we're running with OpenAsar 247 - try { 248 - require.resolve(join(asarPath, "updater", "updater.js")); 249 - hasOpenAsar = true; 250 - openAsarConfigPreload = resolve(asarPath, "config", "preload.js"); 251 - // eslint-disable-next-line no-empty 252 - } catch {} 253 - 254 - if (hasOpenAsar) { 255 - // Disable command line switch injection 256 - // I personally think that the command line switches should be vetted by 257 - // the user and not just "trust that these are sane defaults that work 258 - // always". I'm not hating on Ducko or anything, I'm just opinionated. 259 - // Someone can always make a command line modifier plugin, thats the point 260 - // of having host modules. 261 - try { 262 - const cmdSwitchesPath = require.resolve( 263 - join(asarPath, "cmdSwitches.js") 264 - ); 265 - require.cache[cmdSwitchesPath] = new Module( 266 - cmdSwitchesPath, 267 - require.cache[require.resolve(asarPath)] 268 - ); 269 - require.cache[cmdSwitchesPath]!.exports = () => {}; 270 - } catch (error) { 271 - logger.error("Failed to disable OpenAsar's command line flags:", error); 272 - } 273 - } 274 - 275 patchElectron(); 276 277 - global.moonlightHost.processedExtensions = await loadExtensions(extensions); 278 await loadProcessedExtensions(global.moonlightHost.processedExtensions); 279 } catch (error) { 280 logger.error("Failed to inject:", error); 281 } 282 283 - if (isMoonlightDesktop) return; 284 - 285 - if (!hasOpenAsar && !isMoonlightDesktop) { 286 persist(asarPath); 287 } 288 289 - // Need to do this instead of require() or it breaks require.main 290 - // @ts-expect-error Module internals 291 - Module._load(asarPath, Module, true); 292 } 293 294 function patchElectron() { ··· 302 configurable: false 303 }); 304 } else { 305 - Object.defineProperty( 306 - electronClone, 307 - property, 308 - Object.getOwnPropertyDescriptor(electron, property)! 309 - ); 310 } 311 } 312
··· 6 } from "electron"; 7 import Module from "node:module"; 8 import { constants, MoonlightBranch } from "@moonlight-mod/types"; 9 + import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 10 import { getExtensions } from "@moonlight-mod/core/extension"; 11 import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; 12 + import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; 13 import EventEmitter from "node:events"; 14 + import path from "node:path"; 15 import persist from "@moonlight-mod/core/persist"; 16 import createFS from "@moonlight-mod/core/fs"; 17 + import { getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config"; 18 + import { getConfigPath, getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data"; 19 20 const logger = new Logger("injector"); 21 22 let oldPreloadPath: string | undefined; 23 let corsAllow: string[] = []; 24 let blockedUrls: RegExp[] = []; 25 + let injectorConfig: InjectorConfig | undefined; 26 + 27 + const scriptUrls = ["web.", "sentry."]; 28 + const blockedScripts = new Set<string>(); 29 30 ipcMain.on(constants.ipcGetOldPreloadPath, (e) => { 31 e.returnValue = oldPreloadPath; 32 }); 33 + 34 ipcMain.on(constants.ipcGetAppData, (e) => { 35 e.returnValue = app.getPath("appData"); 36 }); 37 + ipcMain.on(constants.ipcGetInjectorConfig, (e) => { 38 + e.returnValue = injectorConfig; 39 }); 40 ipcMain.handle(constants.ipcMessageBox, (_, opts) => { 41 electron.dialog.showMessageBoxSync(opts); ··· 45 }); 46 47 const reEscapeRegExp = /[\\^$.*+?()[\]{}|]/g; 48 + const reMatchPattern = /^(?<scheme>\*|[a-z][a-z0-9+.-]*):\/\/(?<host>.+?)\/(?<path>.+)?$/; 49 50 const escapeRegExp = (s: string) => s.replace(reEscapeRegExp, "\\$&"); 51 ipcMain.handle(constants.ipcSetBlockedList, (_, list: string[]) => { ··· 76 blockedUrls = compiled; 77 }); 78 79 + function patchCsp(headers: Record<string, string[]>, extensionCspOverrides: Record<string, string[]>) { 80 + const directives = ["script-src", "style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]; 81 + const values = ["*", "blob:", "data:", "'unsafe-inline'", "'unsafe-eval'", "disclip:"]; 82 83 const csp = "content-security-policy"; 84 if (headers[csp] == null) return; ··· 97 parts[directive] = values; 98 } 99 100 + for (const [directive, urls] of Object.entries(extensionCspOverrides)) { 101 + parts[directive] ??= []; 102 + parts[directive].push(...urls); 103 + } 104 + 105 const stringified = Object.entries<string[]>(parts) 106 .map(([key, value]) => { 107 return `${key} ${value.join(" ")}`; ··· 110 headers[csp] = [stringified]; 111 } 112 113 class BrowserWindow extends ElectronBrowserWindow { 114 constructor(opts: BrowserWindowConstructorOptions) { 115 + const isMainWindow = opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1; 116 117 + if (isMainWindow) { 118 + if (!oldPreloadPath) oldPreloadPath = opts.webPreferences!.preload; 119 opts.webPreferences!.preload = require.resolve("./node-preload.js"); 120 + } 121 122 // Event for modifying window options 123 moonlightHost.events.emit("window-options", opts, isMainWindow); ··· 127 // Event for when a window is created 128 moonlightHost.events.emit("window-created", this, isMainWindow); 129 130 + const extensionCspOverrides: Record<string, string[]> = {}; 131 + 132 + { 133 + const extCsps = moonlightHost.processedExtensions.extensions.map((x) => x.manifest.csp ?? {}); 134 + for (const csp of extCsps) { 135 + for (const [directive, urls] of Object.entries(csp)) { 136 + extensionCspOverrides[directive] ??= []; 137 + extensionCspOverrides[directive].push(...urls); 138 + } 139 + } 140 + } 141 + 142 this.webContents.session.webRequest.onHeadersReceived((details, cb) => { 143 if (details.responseHeaders != null) { 144 // Patch CSP so things can use externally hosted assets 145 if (details.resourceType === "mainFrame") { 146 + patchCsp(details.responseHeaders, extensionCspOverrides); 147 } 148 149 // Allow plugins to bypass CORS for specific URLs 150 if (corsAllow.some((x) => details.url.startsWith(x))) { 151 + if (!details.responseHeaders) details.responseHeaders = {}; 152 + 153 + // Work around HTTP header case sensitivity by reusing the header name if it exists 154 + // https://github.com/moonlight-mod/moonlight/issues/201 155 + const fallback = "access-control-allow-origin"; 156 + const key = Object.keys(details.responseHeaders).find((h) => h.toLowerCase() === fallback) ?? fallback; 157 + details.responseHeaders[key] = ["*"]; 158 } 159 + 160 + moonlightHost.events.emit("headers-received", details, isMainWindow); 161 162 cb({ cancel: false, responseHeaders: details.responseHeaders }); 163 } 164 }); 165 166 this.webContents.session.webRequest.onBeforeRequest((details, cb) => { 167 + /* 168 + In order to get moonlight loading to be truly async, we prevent Discord 169 + from loading their scripts immediately. We block the requests, keep note 170 + of their URLs, and then send them off to node-preload when we get all of 171 + them. node-preload then loads node side, web side, and then recreates 172 + the script elements to cause them to re-fetch. 173 174 + The browser extension also does this, but in a background script (see 175 + packages/browser/src/background.js - we should probably get this working 176 + with esbuild someday). 177 + */ 178 + if (details.resourceType === "script" && isMainWindow) { 179 + const url = new URL(details.url); 180 + const hasUrl = scriptUrls.some((scriptUrl) => { 181 + return ( 182 + details.url.includes(scriptUrl) && 183 + !url.searchParams.has("inj") && 184 + (url.host.endsWith("discord.com") || url.host.endsWith("discordapp.com")) 185 + ); 186 + }); 187 + if (hasUrl) blockedScripts.add(details.url); 188 + 189 + if (blockedScripts.size === scriptUrls.length) { 190 + setTimeout(() => { 191 + logger.debug("Kicking off node-preload"); 192 + this.webContents.send(constants.ipcNodePreloadKickoff, Array.from(blockedScripts)); 193 + blockedScripts.clear(); 194 + }, 0); 195 } 196 197 + if (hasUrl) return cb({ cancel: true }); 198 } 199 + 200 + // Allow plugins to block some URLs, 201 + // this is needed because multiple webRequest handlers cannot be registered at once 202 + cb({ cancel: blockedUrls.some((u) => u.test(details.url)) }); 203 + }); 204 } 205 } 206 ··· 221 writable: false 222 }); 223 224 + type InjectorConfig = { disablePersist?: boolean; disableLoad?: boolean }; 225 + export async function inject(asarPath: string, _injectorConfig?: InjectorConfig) { 226 + injectorConfig = _injectorConfig; 227 + 228 + global.moonlightNodeSandboxed = { 229 + fs: createFS(), 230 + // These aren't supposed to be used from host 231 + addCors() {}, 232 + addBlocked() {} 233 + }; 234 235 try { 236 + let config = await readConfig(); 237 initLogger(config); 238 const extensions = await getExtensions(); 239 + const processedExtensions = await loadExtensions(extensions); 240 + const moonlightDir = await getMoonlightDir(); 241 + const extensionsPath = await getExtensionsPath(); 242 243 // Duplicated in node-preload... oops 244 function getConfig(ext: string) { 245 const val = config.extensions[ext]; 246 if (val == null || typeof val === "boolean") return undefined; 247 return val.config; 248 } 249 global.moonlightHost = { 250 + get config() { 251 + return config; 252 + }, 253 + extensions, 254 + processedExtensions, 255 asarPath, 256 events: new EventEmitter(), 257 258 version: MOONLIGHT_VERSION, 259 branch: MOONLIGHT_BRANCH as MoonlightBranch, 260 261 getConfig, 262 + getConfigPath, 263 + getConfigOption(ext, name) { 264 + const manifest = getManifest(extensions, ext); 265 + return getConfigOption(ext, name, config, manifest?.settings); 266 }, 267 + setConfigOption(ext, name, value) { 268 + setConfigOption(config, ext, name, value); 269 + this.writeConfig(config); 270 + }, 271 + async writeConfig(newConfig) { 272 + await writeConfig(newConfig); 273 + config = newConfig; 274 + }, 275 + 276 + getLogger(id) { 277 return new Logger(id); 278 + }, 279 + getMoonlightDir() { 280 + return moonlightDir; 281 + }, 282 + getExtensionDir: (ext: string) => { 283 + return path.join(extensionsPath, ext); 284 } 285 }; 286 287 patchElectron(); 288 289 await loadProcessedExtensions(global.moonlightHost.processedExtensions); 290 } catch (error) { 291 logger.error("Failed to inject:", error); 292 } 293 294 + if (injectorConfig?.disablePersist !== true) { 295 persist(asarPath); 296 } 297 298 + if (injectorConfig?.disableLoad !== true) { 299 + // Need to do this instead of require() or it breaks require.main 300 + // @ts-expect-error Module internals 301 + Module._load(asarPath, Module, true); 302 + } 303 } 304 305 function patchElectron() { ··· 313 configurable: false 314 }); 315 } else { 316 + Object.defineProperty(electronClone, property, Object.getOwnPropertyDescriptor(electron, property)!); 317 } 318 } 319
+8 -1
packages/node-preload/package.json
··· 1 { 2 "name": "@moonlight-mod/node-preload", 3 "private": true, 4 "dependencies": { 5 "@moonlight-mod/core": "workspace:*", 6 "@moonlight-mod/types": "workspace:*" 7 - } 8 }
··· 1 { 2 "name": "@moonlight-mod/node-preload", 3 "private": true, 4 + "engines": { 5 + "node": ">=22", 6 + "pnpm": ">=10", 7 + "npm": "pnpm", 8 + "yarn": "pnpm" 9 + }, 10 "dependencies": { 11 "@moonlight-mod/core": "workspace:*", 12 "@moonlight-mod/types": "workspace:*" 13 + }, 14 + "engineStrict": true 15 }
+121 -44
packages/node-preload/src/index.ts
··· 5 import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 6 import { constants, MoonlightBranch } from "@moonlight-mod/types"; 7 import { getExtensions } from "@moonlight-mod/core/extension"; 8 - import { 9 - getExtensionsPath, 10 - getMoonlightDir 11 - } from "@moonlight-mod/core/util/data"; 12 import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; 13 - import { 14 - loadExtensions, 15 - loadProcessedExtensions 16 - } from "@moonlight-mod/core/extension/loader"; 17 import createFS from "@moonlight-mod/core/fs"; 18 19 async function injectGlobals() { 20 - global.moonlightFS = createFS(); 21 22 - const config = await readConfig(); 23 initLogger(config); 24 const extensions = await getExtensions(); 25 const processedExtensions = await loadExtensions(extensions); 26 const moonlightDir = await getMoonlightDir(); 27 const extensionsPath = await getExtensionsPath(); 28 29 - function getConfig(ext: string) { 30 - const val = config.extensions[ext]; 31 - if (val == null || typeof val === "boolean") return undefined; 32 - return val.config; 33 - } 34 - 35 global.moonlightNode = { 36 - config, 37 extensions, 38 processedExtensions, 39 nativesCache: {}, 40 isBrowser: false, 41 42 version: MOONLIGHT_VERSION, 43 branch: MOONLIGHT_BRANCH as MoonlightBranch, 44 45 - getConfig, 46 - getConfigOption: <T>(ext: string, name: string) => { 47 - const config = getConfig(ext); 48 - if (config == null) return undefined; 49 - const option = config[name]; 50 - if (option == null) return undefined; 51 - return option as T; 52 }, 53 getNatives: (ext: string) => global.moonlightNode.nativesCache[ext], 54 getLogger: (id: string) => { 55 return new Logger(id); 56 }, 57 - 58 getMoonlightDir() { 59 return moonlightDir; 60 }, 61 getExtensionDir: (ext: string) => { 62 return path.join(extensionsPath, ext); 63 - }, 64 - writeConfig 65 }; 66 67 await loadProcessedExtensions(processedExtensions); 68 contextBridge.exposeInMainWorld("moonlightNode", moonlightNode); 69 70 - const extCors = moonlightNode.processedExtensions.extensions.flatMap( 71 - (x) => x.manifest.cors ?? [] 72 - ); 73 74 for (const repo of moonlightNode.config.repositories) { 75 const url = new URL(repo); 76 url.pathname = "/"; 77 - extCors.push(url.toString()); 78 } 79 80 - ipcRenderer.invoke(constants.ipcSetCorsList, extCors); 81 82 - const extBlocked = moonlightNode.processedExtensions.extensions.flatMap( 83 - (e) => e.manifest.blocked ?? [] 84 - ); 85 - ipcRenderer.invoke(constants.ipcSetBlockedList, extBlocked); 86 } 87 88 async function loadPreload() { 89 const webPreloadPath = path.join(__dirname, "web-preload.js"); 90 const webPreload = fs.readFileSync(webPreloadPath, "utf8"); 91 await webFrame.executeJavaScript(webPreload); 92 } 93 94 - async function init(oldPreloadPath: string) { 95 try { 96 await injectGlobals(); 97 await loadPreload(); ··· 102 message: message 103 }); 104 } 105 106 - // Let Discord start even if we fail 107 - if (oldPreloadPath) require(oldPreloadPath); 108 - } 109 110 - const oldPreloadPath: string = ipcRenderer.sendSync( 111 - constants.ipcGetOldPreloadPath 112 - ); 113 - init(oldPreloadPath);
··· 5 import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 6 import { constants, MoonlightBranch } from "@moonlight-mod/types"; 7 import { getExtensions } from "@moonlight-mod/core/extension"; 8 + import { getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data"; 9 import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; 10 + import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; 11 import createFS from "@moonlight-mod/core/fs"; 12 + import { registerCors, registerBlocked, getDynamicCors } from "@moonlight-mod/core/cors"; 13 + import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config"; 14 + import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event"; 15 + import { createEventEmitter } from "@moonlight-mod/core/util/event"; 16 + 17 + let initialized = false; 18 + let logger: Logger; 19 + 20 + function setCors() { 21 + const data = getDynamicCors(); 22 + ipcRenderer.invoke(constants.ipcSetCorsList, data.cors); 23 + ipcRenderer.invoke(constants.ipcSetBlockedList, data.blocked); 24 + } 25 26 async function injectGlobals() { 27 + global.moonlightNodeSandboxed = { 28 + fs: createFS(), 29 + addCors(url) { 30 + registerCors(url); 31 + if (initialized) setCors(); 32 + }, 33 + addBlocked(url) { 34 + registerBlocked(url); 35 + if (initialized) setCors(); 36 + } 37 + }; 38 39 + let config = await readConfig(); 40 initLogger(config); 41 + logger = new Logger("node-preload"); 42 + 43 const extensions = await getExtensions(); 44 const processedExtensions = await loadExtensions(extensions); 45 const moonlightDir = await getMoonlightDir(); 46 const extensionsPath = await getExtensionsPath(); 47 48 global.moonlightNode = { 49 + get config() { 50 + return config; 51 + }, 52 extensions, 53 processedExtensions, 54 nativesCache: {}, 55 isBrowser: false, 56 + events: createEventEmitter<NodeEventType, NodeEventPayloads>(), 57 58 version: MOONLIGHT_VERSION, 59 branch: MOONLIGHT_BRANCH as MoonlightBranch, 60 61 + getConfig(ext) { 62 + return getConfig(ext, config); 63 + }, 64 + getConfigOption(ext, name) { 65 + const manifest = getManifest(extensions, ext); 66 + return getConfigOption(ext, name, config, manifest?.settings); 67 + }, 68 + async setConfigOption(ext, name, value) { 69 + setConfigOption(config, ext, name, value); 70 + await this.writeConfig(config); 71 + }, 72 + async writeConfig(newConfig) { 73 + await writeConfig(newConfig); 74 + config = newConfig; 75 + this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig); 76 }, 77 + 78 getNatives: (ext: string) => global.moonlightNode.nativesCache[ext], 79 getLogger: (id: string) => { 80 return new Logger(id); 81 }, 82 getMoonlightDir() { 83 return moonlightDir; 84 }, 85 getExtensionDir: (ext: string) => { 86 return path.join(extensionsPath, ext); 87 + } 88 }; 89 90 await loadProcessedExtensions(processedExtensions); 91 contextBridge.exposeInMainWorld("moonlightNode", moonlightNode); 92 93 + const extCors = moonlightNode.processedExtensions.extensions.flatMap((x) => x.manifest.cors ?? []); 94 + for (const cors of extCors) { 95 + registerCors(cors); 96 + } 97 98 for (const repo of moonlightNode.config.repositories) { 99 const url = new URL(repo); 100 url.pathname = "/"; 101 + registerCors(url.toString()); 102 + } 103 + 104 + const extBlocked = moonlightNode.processedExtensions.extensions.flatMap((e) => e.manifest.blocked ?? []); 105 + for (const blocked of extBlocked) { 106 + registerBlocked(blocked); 107 } 108 109 + setCors(); 110 111 + initialized = true; 112 } 113 114 async function loadPreload() { 115 const webPreloadPath = path.join(__dirname, "web-preload.js"); 116 const webPreload = fs.readFileSync(webPreloadPath, "utf8"); 117 await webFrame.executeJavaScript(webPreload); 118 + 119 + const func = await webFrame.executeJavaScript("async () => { await window._moonlightWebLoad(); }"); 120 + await func(); 121 } 122 123 + async function init() { 124 try { 125 await injectGlobals(); 126 await loadPreload(); ··· 131 message: message 132 }); 133 } 134 + } 135 136 + const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath); 137 + const isOverlay = window.location.href.indexOf("discord_overlay") > -1; 138 139 + if (isOverlay) { 140 + // The overlay has an inline script tag to call to DiscordNative, so we'll 141 + // just load it immediately. Somehow moonlight still loads in this env, I 142 + // have no idea why - so I suspect it's just forwarding render calls or 143 + // something from the original process 144 + require(oldPreloadPath); 145 + } else { 146 + ipcRenderer.on(constants.ipcNodePreloadKickoff, (_, blockedScripts: string[]) => { 147 + (async () => { 148 + try { 149 + await init(); 150 + logger.debug("Blocked scripts:", blockedScripts); 151 + 152 + const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath); 153 + logger.debug("Old preload path:", oldPreloadPath); 154 + if (oldPreloadPath) require(oldPreloadPath); 155 + 156 + // Do this to get global.DiscordNative assigned 157 + // @ts-expect-error Lying to discord_desktop_core 158 + process.emit("loaded"); 159 + 160 + function replayScripts() { 161 + const scripts = [...document.querySelectorAll("script")].filter( 162 + (script) => script.src && blockedScripts.some((url) => url.includes(script.src)) 163 + ); 164 + 165 + blockedScripts.reverse(); 166 + for (const url of blockedScripts) { 167 + if (url.includes("/sentry.")) continue; 168 + 169 + const script = scripts.find((script) => url.includes(script.src))!; 170 + const newScript = document.createElement("script"); 171 + for (const attr of script.attributes) { 172 + if (attr.name === "src") attr.value += "?inj"; 173 + newScript.setAttribute(attr.name, attr.value); 174 + } 175 + script.remove(); 176 + document.documentElement.appendChild(newScript); 177 + } 178 + } 179 + 180 + if (document.readyState === "complete") { 181 + replayScripts(); 182 + } else { 183 + window.addEventListener("load", replayScripts); 184 + } 185 + } catch (e) { 186 + logger.error("Error restoring original scripts:", e); 187 + } 188 + })(); 189 + }); 190 + }
+4 -1
packages/node-preload/tsconfig.json
··· 1 { 2 - "extends": "../../tsconfig.json" 3 }
··· 1 { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["DOM", "ESNext", "DOM.Iterable"] 5 + } 6 }
+14 -7
packages/types/package.json
··· 1 { 2 "name": "@moonlight-mod/types", 3 - "version": "1.2.0", 4 - "main": "./src/index.ts", 5 - "types": "./src/index.ts", 6 "exports": { 7 ".": "./src/index.ts", 8 "./import": "./src/import.d.ts", 9 "./*": "./src/*.ts" 10 }, 11 "dependencies": { 12 - "@moonlight-mod/lunast": "^1.0.0", 13 - "@moonlight-mod/mappings": "^1.0.2", 14 - "@moonlight-mod/moonmap": "^1.0.2", 15 "@types/react": "^18.3.10", 16 - "csstype": "^3.1.2", 17 "standalone-electron-types": "^1.0.0" 18 } 19 }
··· 1 { 2 "name": "@moonlight-mod/types", 3 + "version": "1.3.17", 4 "exports": { 5 ".": "./src/index.ts", 6 "./import": "./src/import.d.ts", 7 "./*": "./src/*.ts" 8 }, 9 + "main": "./src/index.ts", 10 + "types": "./src/index.ts", 11 + "engineStrict": false, 12 + "engines": { 13 + "node": ">=22", 14 + "pnpm": ">=10", 15 + "npm": "pnpm", 16 + "yarn": "pnpm" 17 + }, 18 "dependencies": { 19 + "@moonlight-mod/lunast": "^1.0.1", 20 + "@moonlight-mod/mappings": "^1.1.25", 21 + "@moonlight-mod/moonmap": "^1.0.5", 22 "@types/react": "^18.3.10", 23 + "csstype": "^3.1.3", 24 "standalone-electron-types": "^1.0.0" 25 } 26 }
+49 -3
packages/types/src/config.ts
··· 6 patchAll?: boolean; 7 }; 8 9 - export type ConfigExtensions = 10 - | { [key: string]: boolean } 11 - | { [key: string]: ConfigExtension }; 12 13 export type ConfigExtension = { 14 enabled: boolean; ··· 35 }; 36 37 export type BooleanSettingType = { 38 type: ExtensionSettingType.Boolean; 39 default?: boolean; 40 }; 41 42 export type NumberSettingType = { 43 type: ExtensionSettingType.Number; 44 default?: number; 45 min?: number; ··· 47 }; 48 49 export type StringSettingType = { 50 type: ExtensionSettingType.String; 51 default?: string; 52 }; 53 54 export type MultilineTextInputSettingType = { 55 type: ExtensionSettingType.MultilineString; 56 default?: string; 57 }; 58 59 export type SelectSettingType = { 60 type: ExtensionSettingType.Select; 61 options: SelectOption[]; 62 default?: string; 63 }; 64 65 export type MultiSelectSettingType = { 66 type: ExtensionSettingType.MultiSelect; 67 options: string[]; 68 default?: string[]; 69 }; 70 71 export type ListSettingType = { 72 type: ExtensionSettingType.List; 73 default?: string[]; 74 }; 75 76 export type DictionarySettingType = { 77 type: ExtensionSettingType.Dictionary; 78 default?: Record<string, string>; 79 }; 80 81 export type CustomSettingType = { 82 type: ExtensionSettingType.Custom; 83 default?: any; 84 }; 85 86 export type ExtensionSettingsManifest = { 87 displayName?: string; 88 description?: string; 89 } & ( 90 | BooleanSettingType 91 | NumberSettingType
··· 6 patchAll?: boolean; 7 }; 8 9 + export type ConfigExtensions = { [key: string]: boolean } | { [key: string]: ConfigExtension }; 10 11 export type ConfigExtension = { 12 enabled: boolean; ··· 33 }; 34 35 export type BooleanSettingType = { 36 + /** 37 + * Displays as a simple switch. 38 + */ 39 type: ExtensionSettingType.Boolean; 40 default?: boolean; 41 }; 42 43 export type NumberSettingType = { 44 + /** 45 + * Displays as a simple slider. 46 + */ 47 type: ExtensionSettingType.Number; 48 default?: number; 49 min?: number; ··· 51 }; 52 53 export type StringSettingType = { 54 + /** 55 + * Displays as a single line string input. 56 + */ 57 type: ExtensionSettingType.String; 58 default?: string; 59 }; 60 61 export type MultilineTextInputSettingType = { 62 + /** 63 + * Displays as a multiple line string input. 64 + */ 65 type: ExtensionSettingType.MultilineString; 66 default?: string; 67 }; 68 69 export type SelectSettingType = { 70 + /** 71 + * A dropdown to pick between one of many values. 72 + */ 73 type: ExtensionSettingType.Select; 74 options: SelectOption[]; 75 default?: string; 76 }; 77 78 export type MultiSelectSettingType = { 79 + /** 80 + * A dropdown to pick multiple values. 81 + */ 82 type: ExtensionSettingType.MultiSelect; 83 options: string[]; 84 default?: string[]; 85 }; 86 87 export type ListSettingType = { 88 + /** 89 + * A list of strings that the user can add or remove from. 90 + */ 91 type: ExtensionSettingType.List; 92 default?: string[]; 93 }; 94 95 export type DictionarySettingType = { 96 + /** 97 + * A dictionary (key-value pair) that the user can add or remove from. 98 + */ 99 type: ExtensionSettingType.Dictionary; 100 default?: Record<string, string>; 101 }; 102 103 export type CustomSettingType = { 104 + /** 105 + * A custom component. 106 + * You can use the registerConfigComponent function in the Moonbase API to register a React component to render here. 107 + */ 108 type: ExtensionSettingType.Custom; 109 default?: any; 110 }; 111 112 + export enum ExtensionSettingsAdvice { 113 + None = "none", 114 + Reload = "reload", 115 + Restart = "restart" 116 + } 117 + 118 export type ExtensionSettingsManifest = { 119 + /** 120 + * A human friendly name for the setting. 121 + */ 122 displayName?: string; 123 + 124 + /** 125 + * A longer description for the setting. 126 + * Markdown is not supported. 127 + */ 128 description?: string; 129 + 130 + /** 131 + * The "advice" to give upon changing this setting. 132 + * Can be configured to reload the client, restart the client, or do nothing. 133 + */ 134 + advice?: ExtensionSettingsAdvice; 135 } & ( 136 | BooleanSettingType 137 | NumberSettingType
+6 -3
packages/types/src/constants.ts
··· 4 export const repoUrlFile = ".moonlight-repo-url"; 5 export const installedVersionFile = ".moonlight-installed-version"; 6 7 export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath"; 8 export const ipcGetAppData = "_moonlight_getAppData"; 9 - export const ipcGetIsMoonlightDesktop = "_moonlight_getIsMoonlightDesktop"; 10 export const ipcMessageBox = "_moonlight_messageBox"; 11 export const ipcSetCorsList = "_moonlight_setCorsList"; 12 export const ipcSetBlockedList = "_moonlight_setBlockedList"; 13 14 export const apiLevel = 2; 15 16 - export const mainRepo = 17 - "https://moonlight-mod.github.io/extensions-dist/repo.json";
··· 4 export const repoUrlFile = ".moonlight-repo-url"; 5 export const installedVersionFile = ".moonlight-installed-version"; 6 7 + export const ipcNodePreloadKickoff = "_moonlight_nodePreloadKickoff"; 8 export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath"; 9 + 10 export const ipcGetAppData = "_moonlight_getAppData"; 11 + export const ipcGetInjectorConfig = "_moonlight_getInjectorConfig"; 12 export const ipcMessageBox = "_moonlight_messageBox"; 13 export const ipcSetCorsList = "_moonlight_setCorsList"; 14 export const ipcSetBlockedList = "_moonlight_setBlockedList"; 15 16 export const apiLevel = 2; 17 18 + export const mainRepo = "https://moonlight-mod.github.io/extensions-dist/repo.json"; 19 + // If you're updating this, update `defaultConfig` in core as well 20 + export const builtinExtensions = ["moonbase", "disableSentry", "noTrack", "noHideToken"];
+17 -20
packages/types/src/core/event.ts
··· 1 import { WebpackModuleFunc, WebpackRequireType } from "../discord"; 2 3 - export interface MoonlightEventEmitter< 4 - EventId extends string = string, 5 - EventData = Record<EventId, any> 6 - > { 7 - dispatchEvent: <Id extends keyof EventData>( 8 - id: Id, 9 - data: EventData[Id] 10 - ) => void; 11 - addEventListener: <Id extends keyof EventData>( 12 - id: Id, 13 - cb: (data: EventData[Id]) => void 14 - ) => void; 15 - removeEventListener: <Id extends keyof EventData>( 16 - id: Id, 17 - cb: (data: EventData[Id]) => void 18 - ) => void; 19 } 20 21 - export enum EventType { 22 ChunkLoad = "chunkLoad", 23 ExtensionLoad = "extensionLoad" 24 } 25 26 - export type EventPayloads = { 27 - [EventType.ChunkLoad]: { 28 chunkId?: number[]; 29 modules: { [id: string]: WebpackModuleFunc }; 30 require?: (require: WebpackRequireType) => any; 31 }; 32 - [EventType.ExtensionLoad]: string; 33 };
··· 1 + import { Config } from "../config"; 2 import { WebpackModuleFunc, WebpackRequireType } from "../discord"; 3 4 + export interface MoonlightEventEmitter<EventId extends string = string, EventData = Record<EventId, any>> { 5 + dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => void; 6 + addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => void; 7 + removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => void; 8 } 9 10 + export enum WebEventType { 11 ChunkLoad = "chunkLoad", 12 ExtensionLoad = "extensionLoad" 13 } 14 15 + export type WebEventPayloads = { 16 + [WebEventType.ChunkLoad]: { 17 chunkId?: number[]; 18 modules: { [id: string]: WebpackModuleFunc }; 19 require?: (require: WebpackRequireType) => any; 20 }; 21 + [WebEventType.ExtensionLoad]: string; 22 + }; 23 + 24 + export enum NodeEventType { 25 + ConfigSaved = "configSaved" 26 + } 27 + 28 + export type NodeEventPayloads = { 29 + [NodeEventType.ConfigSaved]: Config; 30 };
+13
packages/types/src/coreExtensions/appPanels.ts
···
··· 1 + export type AppPanels = { 2 + /** 3 + * Registers a new panel to be displayed around the user/voice controls. 4 + * @param section A unique name for your section 5 + * @param element A React component 6 + */ 7 + addPanel: (section: string, element: React.FC<any>) => void; 8 + 9 + /** 10 + * @private 11 + */ 12 + getPanels: (el: React.FC<any>) => React.ReactNode; 13 + };
+204
packages/types/src/coreExtensions/commands.ts
···
··· 1 + export const APPLICATION_ID = "-3"; 2 + 3 + export enum CommandType { 4 + CHAT = 1, 5 + MESSAGE = 3, 6 + PRIMARY_ENTRY_POINT = 4, 7 + USER = 2 8 + } 9 + 10 + export enum InputType { 11 + BOT = 3, 12 + BUILT_IN = 0, 13 + BUILT_IN_INTEGRATION = 2, 14 + BUILT_IN_TEXT = 1, 15 + PLACEHOLDER = 4 16 + } 17 + 18 + export enum OptionType { 19 + SUB_COMMAND = 1, 20 + SUB_COMMAND_GROUP = 2, 21 + STRING = 3, 22 + INTEGER = 4, 23 + BOOLEAN = 5, 24 + USER = 6, 25 + CHANNEL = 7, 26 + ROLE = 8, 27 + MENTIONABLE = 9, 28 + NUMBER = 10, 29 + ATTACHMENT = 11 30 + } 31 + 32 + export enum ChannelType { 33 + GUILD_TEXT = 0, 34 + DM = 1, 35 + GUILD_VOICE = 2, 36 + GROUP_DM = 3, 37 + GUILD_CATEGORY = 4, 38 + GUILD_ANNOUNCEMENT = 5, 39 + GUILD_STORE = 6, 40 + ANNOUNCEMENT_THREAD = 10, 41 + PUBLIC_THREAD = 11, 42 + PRIVATE_THREAD = 12, 43 + GUILD_STAGE_VOICE = 13, 44 + GUILD_DIRECTORY = 14, 45 + GUILD_FORUM = 15, 46 + GUILD_MEDIA = 16, 47 + LOBBY = 17, 48 + DM_SDK = 18 49 + } 50 + 51 + export type RegisteredCommandOption = MoonlightCommandOption & { 52 + displayName: string; 53 + displayDescription: string; 54 + }; 55 + 56 + export type CommandOptionChoice<T> = { 57 + name: string; 58 + value: T; 59 + }; 60 + 61 + type CommandOptionBase<T> = { 62 + type: T; 63 + name: string; 64 + description: string; 65 + required?: T extends OptionType.SUB_COMMAND 66 + ? never 67 + : T extends OptionType.SUB_COMMAND_GROUP 68 + ? never 69 + : boolean | undefined; 70 + choices?: T extends OptionType.STRING 71 + ? CommandOptionChoice<string>[] 72 + : T extends OptionType.INTEGER 73 + ? CommandOptionChoice<number>[] 74 + : T extends OptionType.NUMBER 75 + ? CommandOptionChoice<number>[] 76 + : never; 77 + options?: T extends OptionType.SUB_COMMAND 78 + ? MoonlightCommandOption[] 79 + : T extends OptionType.SUB_COMMAND_GROUP 80 + ? MoonlightCommandOption[] 81 + : never; 82 + channelTypes?: T extends OptionType.CHANNEL ? ChannelType[] : never; 83 + minValue?: T extends OptionType.INTEGER ? number : T extends OptionType.NUMBER ? number : never; 84 + maxValue?: T extends OptionType.INTEGER ? number : T extends OptionType.NUMBER ? number : never; 85 + minLength?: T extends OptionType.STRING ? number : never; 86 + maxLength?: T extends OptionType.STRING ? number : never; 87 + }; 88 + 89 + // This is bad lol 90 + export type MoonlightCommandOption = 91 + | CommandOptionBase<OptionType.SUB_COMMAND> 92 + | CommandOptionBase<OptionType.SUB_COMMAND_GROUP> 93 + | CommandOptionBase<OptionType.STRING> 94 + | CommandOptionBase<OptionType.INTEGER> 95 + | CommandOptionBase<OptionType.BOOLEAN> 96 + | CommandOptionBase<OptionType.USER> 97 + | CommandOptionBase<OptionType.CHANNEL> 98 + | CommandOptionBase<OptionType.ROLE> 99 + | CommandOptionBase<OptionType.MENTIONABLE> 100 + | CommandOptionBase<OptionType.NUMBER> 101 + | CommandOptionBase<OptionType.ATTACHMENT>; 102 + 103 + // TODO: types 104 + export type CommandPredicateState = { 105 + channel: any; 106 + guild: any; 107 + }; 108 + 109 + export type RegisteredCommand = { 110 + id: string; 111 + untranslatedName: string; 112 + displayName: string; 113 + type: CommandType; 114 + inputType: InputType; 115 + applicationId: string; // set to -3! 116 + untranslatedDescription: string; 117 + displayDescription: string; 118 + options?: RegisteredCommandOption[]; 119 + predicate?: (state: CommandPredicateState) => boolean; 120 + execute: (options: CommandOption[]) => void; 121 + }; 122 + 123 + export type MoonlightCommand = { 124 + id: string; 125 + description: string; 126 + 127 + /** 128 + * You likely want CHAT 129 + */ 130 + type: CommandType; 131 + 132 + /** 133 + * You likely want BUILT_IN (or BUILT_IN_TEXT if usable with replies) 134 + */ 135 + inputType: InputType; 136 + options?: MoonlightCommandOption[]; 137 + predicate?: (state: CommandPredicateState) => boolean; 138 + execute: (options: CommandOption[]) => void; 139 + }; 140 + 141 + export type CommandOption = { 142 + name: string; 143 + } & ( // TODO: more of these 144 + | { 145 + type: Exclude<OptionType, OptionType.STRING>; 146 + value: any; 147 + } 148 + | { 149 + type: OptionType.STRING; 150 + value: string; 151 + } 152 + | { 153 + type: OptionType.NUMBER | OptionType.INTEGER; 154 + value: number; 155 + } 156 + | { 157 + type: OptionType.BOOLEAN; 158 + value: boolean; 159 + } 160 + | { 161 + type: OptionType.SUB_COMMAND | OptionType.SUB_COMMAND_GROUP; 162 + options: CommandOption[]; 163 + } 164 + ); 165 + 166 + export type AnyScopeRegex = RegExp["exec"] & { 167 + regex: RegExp; 168 + }; 169 + 170 + export type Commands = { 171 + /** 172 + * Register a command in the internal slash command system 173 + */ 174 + registerCommand: (command: MoonlightCommand) => void; 175 + 176 + /** 177 + * Register a legacy command that works via regex 178 + */ 179 + registerLegacyCommand: (id: string, command: LegacyCommand) => void; 180 + 181 + /** 182 + * Creates a regular expression that legacy commands can understand 183 + */ 184 + anyScopeRegex: (regex: RegExp) => AnyScopeRegex; 185 + 186 + /** 187 + * @private 188 + */ 189 + _getCommands: () => RegisteredCommand[]; 190 + }; 191 + 192 + export type LegacyContext = { 193 + channel: any; 194 + isEdit: boolean; 195 + }; 196 + 197 + export type LegacyReturn = { 198 + content: string; 199 + }; 200 + 201 + export type LegacyCommand = { 202 + match?: RegExp | { regex: RegExp } | AnyScopeRegex; 203 + action: (content: string, context: LegacyContext) => LegacyReturn; 204 + };
+33
packages/types/src/coreExtensions/common.ts
···
··· 1 + import type { IconProps, IconSize } from "@moonlight-mod/mappings/discord/components/common/index"; 2 + 3 + export type ErrorBoundaryProps = React.PropsWithChildren<{ 4 + noop?: boolean; 5 + fallback?: React.FC<any>; 6 + message?: string; 7 + }>; 8 + 9 + export type ErrorBoundaryState = { 10 + errored: boolean; 11 + error?: Error; 12 + componentStack?: string; 13 + }; 14 + 15 + export type ErrorBoundary = React.ComponentClass<ErrorBoundaryProps, ErrorBoundaryState>; 16 + 17 + export type ParsedIconProps = { 18 + width: number; 19 + height: number; 20 + fill: string; 21 + className: string; 22 + }; 23 + 24 + export interface Icons { 25 + /** 26 + * Parse icon props into their actual width/height. 27 + * @param props The icon props 28 + */ 29 + parseProps(props?: IconProps): ParsedIconProps; 30 + } 31 + 32 + // Re-export so extension developers don't need to depend on mappings 33 + export type { IconProps, IconSize };
+162
packages/types/src/coreExtensions/componentEditor.ts
···
··· 1 + type Patcher<T> = (elements: React.ReactNode[], props: T) => React.ReactNode[]; 2 + 3 + //#region DM List 4 + export type DMListAnchors = 5 + | "content" 6 + | "favorite-server-indicator" 7 + | "ignored-indicator" 8 + | "blocked-indicator" 9 + | "close-button" 10 + | undefined; 11 + export type DMListDecoratorAnchors = "system-tag" | undefined; 12 + 13 + export enum DMListAnchorIndicies { 14 + content = 0, 15 + "favorite-server-indicator", 16 + "ignored-indicator", 17 + "blocked-indicator", 18 + "close-button" 19 + } 20 + export enum DMListDecoratorAnchorIndicies { 21 + "system-tag" = 0 22 + } 23 + 24 + export type DMListItem = { 25 + component: React.FC<any>; 26 + anchor: DMListAnchors; 27 + before: boolean; 28 + }; 29 + export type DMListDecorator = { 30 + component: React.FC<any>; 31 + anchor: DMListDecoratorAnchors; 32 + before: boolean; 33 + }; 34 + 35 + export type DMList = { 36 + addItem: (id: string, component: React.FC<any>, anchor?: DMListAnchors, before?: boolean) => void; 37 + addDecorator: (id: string, component: React.FC<any>, anchor?: DMListDecoratorAnchors, before?: boolean) => void; 38 + //TODO: fix props type 39 + /** 40 + * @private 41 + */ 42 + _patchItems: Patcher<any>; 43 + /** 44 + * @private 45 + */ 46 + _patchDecorators: Patcher<any>; 47 + }; 48 + //#endregion 49 + 50 + //#region Member List 51 + export type MemberListDecoratorAnchors = "bot-tag" | "owner-crown" | "boost-icon" | undefined; 52 + 53 + export enum MemberListDecoratorAnchorIndicies { 54 + "bot-tag" = 0, 55 + "owner-crown", 56 + "boost-icon" 57 + } 58 + 59 + export type MemberListDecorator = { 60 + component: React.FC<any>; 61 + anchor: MemberListDecoratorAnchors; 62 + before: boolean; 63 + }; 64 + 65 + export type MemberList = { 66 + addItem: (id: string, component: React.FC<any>) => void; 67 + addDecorator: (id: string, component: React.FC<any>, anchor?: MemberListDecoratorAnchors, before?: boolean) => void; 68 + //TODO: fix props type 69 + /** 70 + * @private 71 + */ 72 + _patchItems: Patcher<any>; 73 + /** 74 + * @private 75 + */ 76 + _patchDecorators: Patcher<any>; 77 + }; 78 + //#endregion 79 + 80 + //#region Messages 81 + export type MessageUsernameAnchors = "communication-disabled" | "username" | undefined; 82 + export type MessageUsernameBadgeAnchors = 83 + | "nitro-author" 84 + | "role-icon" 85 + | "new-member" 86 + | "leaderboard-champion" 87 + | "connections" 88 + | undefined; 89 + export type MessageBadgeAnchors = "silent" | "potion" | undefined; 90 + 91 + export type MessageUsername = { 92 + component: React.FC<any>; 93 + anchor: MessageUsernameAnchors; 94 + before: boolean; 95 + }; 96 + export type MessageUsernameBadge = { 97 + component: React.FC<any>; 98 + anchor: MessageUsernameBadgeAnchors; 99 + before: boolean; 100 + }; 101 + export type MessageBadge = { 102 + component: React.FC<any>; 103 + anchor: MessageBadgeAnchors; 104 + before: boolean; 105 + }; 106 + 107 + export enum MessageUsernameIndicies { 108 + "communication-disabled" = 0, 109 + username 110 + } 111 + export enum MessageUsernameBadgeIndicies { 112 + "nitro-author" = 0, 113 + "role-icon", 114 + "new-member", 115 + "leaderboard-champion", 116 + connections 117 + } 118 + export enum MessageBadgeIndicies { 119 + silent = 0, 120 + potion 121 + } 122 + 123 + export type Messages = { 124 + /** 125 + * Adds a component to the username of a message 126 + */ 127 + addToUsername: (id: string, component: React.FC<any>, anchor?: MessageUsernameAnchors, before?: boolean) => void; 128 + /** 129 + * Adds a component to the username badge area of a message (e.g. where role icons/new member badge is) 130 + */ 131 + addUsernameBadge: ( 132 + id: string, 133 + component: React.FC<any>, 134 + anchor?: MessageUsernameBadgeAnchors, 135 + before?: boolean 136 + ) => void; 137 + /** 138 + * Adds a component to the end of a message header (e.g. silent indicator) 139 + */ 140 + addBadge: (id: string, component: React.FC<any>, anchor?: MessageBadgeAnchors, before?: boolean) => void; 141 + /** 142 + * Adds a component to message accessories (e.g. embeds) 143 + */ 144 + addAccessory: (id: string, component: React.FC<any>) => void; 145 + /** 146 + * @private 147 + */ 148 + _patchUsername: Patcher<any>; 149 + /** 150 + * @private 151 + */ 152 + _patchUsernameBadges: Patcher<any>; 153 + /** 154 + * @private 155 + */ 156 + _patchBadges: Patcher<any>; 157 + /** 158 + * @private 159 + */ 160 + _patchAccessories: Patcher<any>; 161 + }; 162 + //#endregion
+21 -121
packages/types/src/coreExtensions/contextMenu.ts
··· 1 - // TODO: Deduplicate common props 2 - 3 - export type Menu = React.FunctionComponent<{ 4 - navId: string; 5 - variant?: string; 6 - hideScrollbar?: boolean; 7 - className?: string; 8 - children: React.ReactComponentElement<MenuElement>[]; 9 - onClose?: () => void; 10 - onSelect?: () => void; 11 - }>; 12 - export type MenuProps = React.ComponentProps<Menu>; 13 - 14 - export type MenuElement = 15 - | MenuSeparator 16 - | MenuGroup 17 - | MenuItem 18 - | MenuCheckboxItem 19 - | MenuRadioItem 20 - | MenuControlItem; 21 - 22 - /* eslint-disable prettier/prettier */ 23 - export type MenuSeparator = React.FunctionComponent; 24 - export type MenuGroup = React.FunctionComponent<{ 25 - label?: string; 26 - className?: string; 27 - color?: string; 28 - children: React.ReactComponentElement<MenuElement>[]; 29 - }>; 30 - export type MenuItem = React.FunctionComponent< 31 - { 32 - id: any; 33 - dontCloseOnActionIfHoldingShiftKey?: boolean; 34 - } & ( 35 - | { 36 - label: string; 37 - subtext?: string; 38 - color?: string; 39 - hint?: string; 40 - disabled?: boolean; 41 - icon?: any; 42 - showIconFirst?: boolean; 43 - imageUrl?: string; 44 - 45 - className?: string; 46 - focusedClassName?: string; 47 - subMenuIconClassName?: string; 48 - 49 - action?: () => void; 50 - onFocus?: () => void; 51 - 52 - iconProps?: any; 53 - sparkle?: any; 54 - 55 - children?: React.ReactComponentElement<MenuElement>[]; 56 - onChildrenScroll?: any; 57 - childRowHeight?: any; 58 - listClassName?: string; 59 - subMenuClassName?: string; 60 - } 61 - | { 62 - color?: string; 63 - disabled?: boolean; 64 - keepItemStyles?: boolean; 65 - 66 - action?: () => void; 67 - 68 - render: any; 69 - navigable?: boolean; 70 - } 71 - ) 72 - >; 73 - export type MenuCheckboxItem = React.FunctionComponent<{ 74 - id: any; 75 - label: string; 76 - subtext?: string; 77 - color?: string; 78 - className?: string; 79 - focusedClassName?: string; 80 - disabled?: boolean; 81 - checked: boolean; 82 - action?: () => void; 83 - }>; 84 - export type MenuRadioItem = React.FunctionComponent<{ 85 - id: any; 86 - label: string; 87 - subtext?: string; 88 - color?: string; 89 - disabled?: boolean; 90 - action?: () => void; 91 - }>; 92 - export type MenuControlItem = React.FunctionComponent< 93 - { 94 - id: any; 95 - label: string; 96 - color?: string; 97 - disabled?: boolean; 98 - showDefaultFocus?: boolean; 99 - } & ( 100 - | { 101 - control: any; 102 - } 103 - | { 104 - control?: undefined; 105 - interactive?: boolean; 106 - children?: React.ReactComponentElement<MenuElement>[]; 107 - } 108 - ) 109 - >; 110 - /* eslint-disable prettier/prettier */ 111 112 export type ContextMenu = { 113 - addItem: ( 114 - navId: string, 115 - item: (props: any) => React.ReactComponentElement<MenuElement>, 116 - anchorId: string, 117 - before?: boolean 118 - ) => void; 119 120 MenuCheckboxItem: MenuCheckboxItem; 121 MenuControlItem: MenuControlItem; ··· 157 label: string; 158 }; 159 160 - export type EvilItemParser = ( 161 - el: 162 - | React.ReactComponentElement<MenuElement> 163 - | React.ReactComponentElement<MenuElement>[] 164 - ) => InternalItem[];
··· 1 + import { 2 + Menu, 3 + MenuCheckboxItem, 4 + MenuControlItem, 5 + MenuGroup, 6 + MenuRadioItem, 7 + MenuSeparator, 8 + MenuItem, 9 + MenuElement 10 + } from "@moonlight-mod/mappings/discord/components/common/index"; 11 12 export type ContextMenu = { 13 + /** 14 + * Registers a new context menu item for a given context menu type. 15 + * @param navId The navigation ID for the target context menu (e.g. "user-context", "message") 16 + * @param item A React component 17 + * @param anchor An existing item's ID to anchor the new item to 18 + * @param before Whether to insert the new item before the anchor item 19 + */ 20 + addItem: (navId: string, item: React.FC<any>, anchor: string | RegExp, before?: boolean) => void; 21 22 MenuCheckboxItem: MenuCheckboxItem; 23 MenuControlItem: MenuControlItem; ··· 59 label: string; 60 }; 61 62 + export type EvilItemParser = (el: MenuElement | MenuElement[]) => InternalItem[]; 63 + 64 + export type { Menu, MenuElement };
+20 -23
packages/types/src/coreExtensions/markdown.ts
··· 11 12 export type ASTNode = SingleASTNode | Array<SingleASTNode>; 13 14 - export type Parser = ( 15 - source: string, 16 - state?: State | null | undefined 17 - ) => Array<SingleASTNode>; 18 19 - export type ParseFunction = ( 20 - capture: Capture, 21 - nestedParse: Parser, 22 - state: State 23 - ) => UntypedASTNode | ASTNode; 24 25 export type Capture = 26 | (Array<string> & { ··· 38 39 export type MatchFunction = { 40 regex?: RegExp; 41 - } & (( 42 - source: string, 43 - state: State, 44 - prevCapture: string 45 - ) => Capture | null | undefined); 46 47 - export type Output<Result> = ( 48 - node: ASTNode, 49 - state?: State | null | undefined 50 - ) => Result; 51 52 - export type SingleNodeOutput<Result> = ( 53 - node: SingleASTNode, 54 - nestedOutput: Output<Result>, 55 - state: State 56 - ) => Result; 57 58 // }}} 59 ··· 100 slateDecorators: Record<string, string>; 101 ruleBlacklists: Record<Ruleset, Record<string, boolean>>; 102 103 addRule: ( 104 name: string, 105 markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule, 106 slate: (rules: Record<string, SlateRule>) => SlateRule, 107 decorator?: string | undefined 108 ) => void; 109 blacklistFromRuleset: (ruleset: Ruleset, name: string) => void; 110 };
··· 11 12 export type ASTNode = SingleASTNode | Array<SingleASTNode>; 13 14 + export type Parser = (source: string, state?: State | null | undefined) => Array<SingleASTNode>; 15 16 + export type ParseFunction = (capture: Capture, nestedParse: Parser, state: State) => UntypedASTNode | ASTNode; 17 18 export type Capture = 19 | (Array<string> & { ··· 31 32 export type MatchFunction = { 33 regex?: RegExp; 34 + } & ((source: string, state: State, prevCapture: string) => Capture | null | undefined); 35 36 + export type Output<Result> = (node: ASTNode, state?: State | null | undefined) => Result; 37 38 + export type SingleNodeOutput<Result> = (node: SingleASTNode, nestedOutput: Output<Result>, state: State) => Result; 39 40 // }}} 41 ··· 82 slateDecorators: Record<string, string>; 83 ruleBlacklists: Record<Ruleset, Record<string, boolean>>; 84 85 + /** 86 + * Registers a new Markdown rule with simple-markdown. 87 + * @param name The name of the rule 88 + * @param markdown A function that returns simple-markdown rules 89 + * @param slate A function that returns Slate rules 90 + * @param decorator A decorator name for Slate 91 + * @see https://www.npmjs.com/package/simple-markdown#adding-a-simple-extension 92 + * @see https://docs.slatejs.org/ 93 + */ 94 addRule: ( 95 name: string, 96 markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule, 97 slate: (rules: Record<string, SlateRule>) => SlateRule, 98 decorator?: string | undefined 99 ) => void; 100 + 101 + /** 102 + * Blacklist a rule from a ruleset. 103 + * @param ruleset The ruleset name 104 + * @param name The rule name 105 + */ 106 blacklistFromRuleset: (ruleset: Ruleset, name: string) => void; 107 };
+12 -7
packages/types/src/coreExtensions/moonbase.ts
··· 1 - export type CustomComponent = React.FC<{ 2 value: any; 3 setValue: (value: any) => void; 4 - }>; 5 6 export type Moonbase = { 7 - registerConfigComponent: ( 8 - ext: string, 9 - option: string, 10 - component: CustomComponent 11 - ) => void; 12 };
··· 1 + export type CustomComponentProps = { 2 value: any; 3 setValue: (value: any) => void; 4 + }; 5 + 6 + export type CustomComponent = React.FC<CustomComponentProps>; 7 8 export type Moonbase = { 9 + /** 10 + * Registers a custom component for an extension setting. 11 + * The extension setting must be of type "custom". 12 + * @param ext The extension ID 13 + * @param option The setting ID 14 + * @param component A React component 15 + */ 16 + registerConfigComponent: (ext: string, option: string, component: CustomComponent) => void; 17 };
+16 -1
packages/types/src/coreExtensions/notices.ts
··· 1 - import type { Store } from "@moonlight-mod/mappings/discord/packages/flux"; 2 3 export type NoticeButton = { 4 name: string; ··· 14 }; 15 16 export type Notices = Store<any> & { 17 addNotice: (notice: Notice) => void; 18 popNotice: () => void; 19 getCurrentNotice: () => Notice | null; 20 shouldShowNotice: () => boolean; 21 };
··· 1 + import type { Store } from "@moonlight-mod/mappings/discord/packages/flux/Store"; 2 3 export type NoticeButton = { 4 name: string; ··· 14 }; 15 16 export type Notices = Store<any> & { 17 + /** 18 + * Adds a custom notice to the top of the screen. 19 + */ 20 addNotice: (notice: Notice) => void; 21 + 22 + /** 23 + * Removes the current notice from the top of the screen. 24 + */ 25 popNotice: () => void; 26 + 27 + /** 28 + * @private 29 + */ 30 getCurrentNotice: () => Notice | null; 31 + 32 + /** 33 + * @private 34 + */ 35 shouldShowNotice: () => boolean; 36 };
+39 -8
packages/types/src/coreExtensions/settings.ts
··· 1 import React, { ReactElement } from "react"; 2 - import type { Store } from "@moonlight-mod/mappings/discord/packages/flux"; 3 4 export type NoticeProps = { 5 stores: Store<any>[]; ··· 7 }; 8 9 export type SettingsSection = 10 - | { section: "DIVIDER"; pos: number } 11 - | { section: "HEADER"; label: string; pos: number } 12 | { 13 section: string; 14 label: string; 15 color: string | null; 16 element: React.FunctionComponent; 17 - pos: number; 18 notice?: NoticeProps; 19 _moonlight_submenu?: () => ReactElement | ReactElement[]; 20 }; 21 ··· 24 sectionNames: string[]; 25 sectionMenuItems: Record<string, ReactElement[]>; 26 27 addSection: ( 28 section: string, 29 label: string, 30 element: React.FunctionComponent, 31 color?: string | null, 32 - pos?: number, 33 - notice?: NoticeProps 34 ) => void; 35 addSectionMenuItems: (section: string, ...items: ReactElement[]) => void; 36 37 - addDivider: (pos: number | null) => void; 38 - addHeader: (label: string, pos: number | null) => void; 39 _mutateSections: (sections: SettingsSection[]) => SettingsSection[]; 40 };
··· 1 import React, { ReactElement } from "react"; 2 + import type { Store } from "@moonlight-mod/mappings/discord/packages/flux/Store"; 3 4 export type NoticeProps = { 5 stores: Store<any>[]; ··· 7 }; 8 9 export type SettingsSection = 10 + | { section: "DIVIDER"; pos: number | ((sections: SettingsSection[]) => number) } 11 + | { section: "HEADER"; label: string; pos: number | ((sections: SettingsSection[]) => number) } 12 | { 13 section: string; 14 label: string; 15 color: string | null; 16 element: React.FunctionComponent; 17 + pos: number | ((sections: SettingsSection[]) => number); 18 notice?: NoticeProps; 19 + onClick?: () => void; 20 _moonlight_submenu?: () => ReactElement | ReactElement[]; 21 }; 22 ··· 25 sectionNames: string[]; 26 sectionMenuItems: Record<string, ReactElement[]>; 27 28 + /** 29 + * Registers a new section in the settings menu. 30 + * @param section The section ID 31 + * @param label The label for the section 32 + * @param element The React component to render 33 + * @param color A color to use for the section 34 + * @param pos The position in the settings menu to place the section 35 + * @param notice A notice to display when in the section 36 + * @param onClick A custom action to execute when clicked from the context menu 37 + */ 38 addSection: ( 39 section: string, 40 label: string, 41 element: React.FunctionComponent, 42 color?: string | null, 43 + pos?: number | ((sections: SettingsSection[]) => number), 44 + notice?: NoticeProps, 45 + onClick?: () => void 46 ) => void; 47 + 48 + /** 49 + * Adds new items to a section in the settings menu. 50 + * @param section The section ID 51 + * @param items The React components to render 52 + */ 53 addSectionMenuItems: (section: string, ...items: ReactElement[]) => void; 54 55 + /** 56 + * Places a divider in the settings menu. 57 + * @param pos The position in the settings menu to place the divider 58 + */ 59 + addDivider: (pos: number | ((sections: SettingsSection[]) => number) | null) => void; 60 + 61 + /** 62 + * Places a header in the settings menu. 63 + * @param pos The position in the settings menu to place the header 64 + */ 65 + addHeader: (label: string, pos: number | ((sections: SettingsSection[]) => number) | null) => void; 66 + 67 + /** 68 + * @private 69 + */ 70 _mutateSections: (sections: SettingsSection[]) => SettingsSection[]; 71 };
+83 -18
packages/types/src/coreExtensions/spacepack.ts
··· 1 - import { 2 - WebpackModule, 3 - WebpackModuleFunc, 4 - WebpackRequireType 5 - } from "../discord"; 6 7 export type Spacepack = { 8 inspect: (module: number | string) => WebpackModuleFunc | null; 9 - findByCode: (...args: (string | RegExp)[]) => any[]; 10 - findByExports: (...args: string[]) => any[]; 11 require: WebpackRequireType; 12 modules: Record<string, WebpackModuleFunc>; 13 cache: Record<string, any>; 14 findObjectFromKey: (exports: Record<string, any>, key: string) => any | null; 15 findObjectFromValue: (exports: Record<string, any>, value: any) => any | null; 16 - findObjectFromKeyValuePair: ( 17 - exports: Record<string, any>, 18 - key: string, 19 - value: any 20 - ) => any | null; 21 findFunctionByStrings: ( 22 exports: Record<string, any>, 23 ...strings: (string | RegExp)[] 24 - // eslint-disable-next-line @typescript-eslint/ban-types 25 ) => Function | null; 26 - lazyLoad: ( 27 - find: string | RegExp | (string | RegExp)[], 28 - chunk: RegExp, 29 - module: RegExp 30 - ) => Promise<any>; 31 filterReal: (modules: WebpackModule[]) => WebpackModule[]; 32 };
··· 1 + import { WebpackModule, WebpackModuleFunc, WebpackRequireType } from "../discord"; 2 3 export type Spacepack = { 4 + /** 5 + * Given a Webpack module ID, returns the function for the Webpack module. 6 + * Can be double clicked to inspect in DevTools. 7 + * @param module The module ID 8 + * @returns The Webpack module, if found 9 + */ 10 inspect: (module: number | string) => WebpackModuleFunc | null; 11 + 12 + /** 13 + * Find Webpack modules based on matches in code. 14 + * @param args A list of finds to match against 15 + * @returns The Webpack modules, if found 16 + */ 17 + findByCode: (...args: (string | RegExp)[]) => WebpackModule[]; 18 + 19 + /** 20 + * Find Webpack modules based on their exports. 21 + * @deprecated This has race conditions. Consider using findByCode instead. 22 + * @param args A list of finds to match exports against 23 + * @returns The Webpack modules, if found 24 + */ 25 + findByExports: (...args: string[]) => WebpackModule[]; 26 + 27 + /** 28 + * The Webpack require function. 29 + */ 30 require: WebpackRequireType; 31 + 32 + /** 33 + * The Webpack module list. 34 + * Re-export of require.m. 35 + */ 36 modules: Record<string, WebpackModuleFunc>; 37 + 38 + /** 39 + * The Webpack module cache. 40 + * Re-export of require.c. 41 + */ 42 cache: Record<string, any>; 43 + 44 + /** 45 + * Finds an object from a module's exports using the given key. 46 + * @param exports Exports from a Webpack module 47 + * @param key The key to find with 48 + * @returns The object, if found 49 + */ 50 findObjectFromKey: (exports: Record<string, any>, key: string) => any | null; 51 + 52 + /** 53 + * Finds an object from a module's exports using the given value. 54 + * @param exports Exports from a Webpack module 55 + * @param value The value to find with 56 + * @returns The object, if found 57 + */ 58 findObjectFromValue: (exports: Record<string, any>, value: any) => any | null; 59 + 60 + /** 61 + * Finds an object from a module's exports using the given key-value pair. 62 + * @param exports Exports from a Webpack module 63 + * @param key The key to find with 64 + * @param value The value to find with 65 + * @returns The object, if found 66 + */ 67 + findObjectFromKeyValuePair: (exports: Record<string, any>, key: string, value: any) => any | null; 68 + 69 + /** 70 + * Finds a function from a module's exports using the given source find. 71 + * This behaves like findByCode but localized to the exported function. 72 + * @param exports A module's exports 73 + * @param strings A list of finds to use 74 + * @returns The function, if found 75 + */ 76 findFunctionByStrings: ( 77 exports: Record<string, any>, 78 ...strings: (string | RegExp)[] 79 + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 80 ) => Function | null; 81 + 82 + /** 83 + * Lazy load a Webpack module. 84 + * @param find A list of finds to discover a target module with 85 + * @param chunk A RegExp to match chunks to load 86 + * @param module A RegExp to match the target Webpack module 87 + * @returns The target Webpack module 88 + */ 89 + lazyLoad: (find: string | RegExp | (string | RegExp)[], chunk: RegExp, module: RegExp) => Promise<any>; 90 + 91 + /** 92 + * Filter a list of Webpack modules to "real" ones from the Discord client. 93 + * @param modules A list of Webpack modules 94 + * @returns A filtered list of Webpack modules 95 + */ 96 filterReal: (modules: WebpackModule[]) => WebpackModule[]; 97 };
+4
packages/types/src/coreExtensions.ts
··· 4 export * as ContextMenu from "./coreExtensions/contextMenu"; 5 export * as Notices from "./coreExtensions/notices"; 6 export * as Moonbase from "./coreExtensions/moonbase";
··· 4 export * as ContextMenu from "./coreExtensions/contextMenu"; 5 export * as Notices from "./coreExtensions/notices"; 6 export * as Moonbase from "./coreExtensions/moonbase"; 7 + export * as AppPanels from "./coreExtensions/appPanels"; 8 + export * as Commands from "./coreExtensions/commands"; 9 + export * as ComponentEditor from "./coreExtensions/componentEditor"; 10 + export * as Common from "./coreExtensions/common";
+17 -2
packages/types/src/discord/require.ts
··· 1 import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu"; 2 import { Markdown } from "../coreExtensions/markdown"; 3 import { Settings } from "../coreExtensions/settings"; 4 import { Spacepack } from "../coreExtensions/spacepack"; 5 - import { Notices } from "../coreExtensions/notices"; 6 - import { Moonbase } from "../coreExtensions/moonbase"; 7 8 declare function WebpackRequire(id: string): any; 9 10 declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser; 11 declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;
··· 1 + import { AppPanels } from "../coreExtensions/appPanels"; 2 + import { Commands } from "../coreExtensions/commands"; 3 + import { ErrorBoundary, Icons } from "../coreExtensions/common"; 4 + import { DMList, MemberList, Messages } from "../coreExtensions/componentEditor"; 5 import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu"; 6 import { Markdown } from "../coreExtensions/markdown"; 7 + import { Moonbase } from "../coreExtensions/moonbase"; 8 + import { Notices } from "../coreExtensions/notices"; 9 import { Settings } from "../coreExtensions/settings"; 10 import { Spacepack } from "../coreExtensions/spacepack"; 11 12 declare function WebpackRequire(id: string): any; 13 + 14 + declare function WebpackRequire(id: "appPanels_appPanels"): AppPanels; 15 + 16 + declare function WebpackRequire(id: "commands_commands"): Commands; 17 + 18 + declare function WebpackRequire(id: "common_ErrorBoundary"): ErrorBoundary; 19 + declare function WebpackRequire(id: "common_icons"): Icons; 20 + 21 + declare function WebpackRequire(id: "componentEditor_dmList"): DMList; 22 + declare function WebpackRequire(id: "componentEditor_memberList"): MemberList; 23 + declare function WebpackRequire(id: "componentEditor_messages"): Messages; 24 25 declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser; 26 declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;
+3 -11
packages/types/src/discord/webpack.ts
··· 10 11 export type WebpackModule = { 12 id: string | number; 13 - loaded: boolean; 14 exports: any; 15 }; 16 17 - export type WebpackModuleFunc = (( 18 - module: any, 19 - exports: any, 20 - require: WebpackRequireType 21 - ) => void) & { 22 __moonlight?: boolean; 23 }; 24 25 - export type WebpackJsonpEntry = [ 26 - number[], 27 - { [id: string]: WebpackModuleFunc }, 28 - (require: WebpackRequireType) => any 29 - ]; 30 31 export type WebpackJsonp = WebpackJsonpEntry[] & { 32 push: {
··· 10 11 export type WebpackModule = { 12 id: string | number; 13 + loaded?: boolean; 14 exports: any; 15 }; 16 17 + export type WebpackModuleFunc = ((module: any, exports: any, require: WebpackRequireType) => void) & { 18 __moonlight?: boolean; 19 }; 20 21 + export type WebpackJsonpEntry = [number[], { [id: string]: WebpackModuleFunc }, (require: WebpackRequireType) => any]; 22 23 export type WebpackJsonp = WebpackJsonpEntry[] & { 24 push: {
+105 -3
packages/types/src/extension.ts
··· 28 }; 29 30 export type ExtensionManifest = { 31 id: string; 32 version?: string; 33 apiLevel?: number; 34 environment?: ExtensionEnvironment; 35 36 meta?: { 37 name?: string; 38 tagline?: string; 39 description?: string; 40 authors?: ExtensionAuthor[]; 41 - deprecated?: boolean; 42 tags?: ExtensionTag[]; 43 source?: string; 44 }; 45 46 dependencies?: string[]; 47 suggested?: string[]; 48 incompatible?: string[]; 49 50 settings?: Record<string, ExtensionSettingsManifest>; 51 52 cors?: string[]; 53 blocked?: string[]; 54 }; 55 56 export enum ExtensionEnvironment { 57 Both = "both", 58 Desktop = "desktop", 59 Web = "web" 60 } 61 ··· 75 webpackModules?: Record<string, string>; 76 nodePath?: string; 77 hostPath?: string; 78 }; 79 }; 80 ··· 106 export type Patch = { 107 find: PatchMatch; 108 replace: PatchReplace | PatchReplace[]; 109 prerequisite?: () => boolean; 110 }; 111 ··· 133 id: number; 134 }; 135 136 - export type IdentifiedWebpackModule = ExtensionWebpackModule & 137 - ExplicitExtensionDependency;
··· 28 }; 29 30 export type ExtensionManifest = { 31 + $schema?: string; 32 + 33 + /** 34 + * A unique identifier for your extension. 35 + */ 36 id: string; 37 + 38 + /** 39 + * A version string for your extension - doesn't need to follow a specific format. Required for publishing. 40 + */ 41 version?: string; 42 + 43 + /** 44 + * The API level this extension targets. If it does not match the current version, the extension will not be loaded. 45 + */ 46 apiLevel?: number; 47 + 48 + /** 49 + * Which environment this extension is capable of running in. 50 + */ 51 environment?: ExtensionEnvironment; 52 53 + /** 54 + * Metadata about your extension for use in Moonbase. 55 + */ 56 meta?: { 57 + /** 58 + * A human friendly name for your extension as a proper noun. 59 + */ 60 name?: string; 61 + 62 + /** 63 + * A short tagline that appears below the name. 64 + */ 65 tagline?: string; 66 + 67 + /** 68 + * A longer description that can use Markdown. 69 + */ 70 description?: string; 71 + 72 + /** 73 + * List of authors that worked on this extension - accepts string or object with ID. 74 + */ 75 authors?: ExtensionAuthor[]; 76 + 77 + /** 78 + * A list of tags that are relevant to the extension. 79 + */ 80 tags?: ExtensionTag[]; 81 + 82 + /** 83 + * The URL to the source repository. 84 + */ 85 source?: string; 86 + 87 + /** 88 + * A donation link (or other method of support). If you don't want financial contributions, consider putting your favorite charity here! 89 + */ 90 + donate?: string; 91 + 92 + /** 93 + * A changelog to show in Moonbase. 94 + * Moonbase will show the changelog for the latest version, even if it is not installed. 95 + */ 96 + changelog?: string; 97 + 98 + /** 99 + * Whether the extension is deprecated and no longer receiving updates. 100 + */ 101 + deprecated?: boolean; 102 }; 103 104 + /** 105 + * A list of extension IDs that are required for the extension to load. 106 + */ 107 dependencies?: string[]; 108 + 109 + /** 110 + * A list of extension IDs that the user may want to install. 111 + */ 112 suggested?: string[]; 113 + 114 + /** 115 + * A list of extension IDs that the extension is incompatible with. 116 + * If two incompatible extensions are enabled, one of them will not load. 117 + */ 118 incompatible?: string[]; 119 120 + /** 121 + * A list of settings for your extension, where the key is the settings ID. 122 + */ 123 settings?: Record<string, ExtensionSettingsManifest>; 124 125 + /** 126 + * A list of URLs to bypass CORS for. 127 + * This is implemented by checking if the start of the URL matches. 128 + * @example https://moonlight-mod.github.io/ 129 + */ 130 cors?: string[]; 131 + 132 + /** 133 + * A list of URLs to block all requests to. 134 + * This is implemented by checking if the start of the URL matches. 135 + * @example https://moonlight-mod.github.io/ 136 + */ 137 blocked?: string[]; 138 + 139 + /** 140 + * A mapping from CSP directives to URLs to allow. 141 + * @example { "script-src": ["https://example.com"] } 142 + */ 143 + csp?: Record<string, string[]>; 144 }; 145 146 export enum ExtensionEnvironment { 147 + /** 148 + * The extension will run on both platforms, the host/native modules MAY be loaded 149 + */ 150 Both = "both", 151 + 152 + /** 153 + * Extension will run on desktop only, the host/native modules are guaranteed to load 154 + */ 155 Desktop = "desktop", 156 + 157 + /** 158 + * Currently equivalent to Both 159 + */ 160 Web = "web" 161 } 162 ··· 176 webpackModules?: Record<string, string>; 177 nodePath?: string; 178 hostPath?: string; 179 + style?: string; 180 }; 181 }; 182 ··· 208 export type Patch = { 209 find: PatchMatch; 210 replace: PatchReplace | PatchReplace[]; 211 + hardFail?: boolean; // if any patches fail, all fail 212 prerequisite?: () => boolean; 213 }; 214 ··· 236 id: number; 237 }; 238 239 + export type IdentifiedWebpackModule = ExtensionWebpackModule & ExplicitExtensionDependency;
+1
packages/types/src/fs.ts
··· 11 12 exists: (path: string) => Promise<boolean>; 13 isFile: (path: string) => Promise<boolean>; 14 15 join: (...parts: string[]) => string; 16 dirname: (path: string) => string;
··· 11 12 exists: (path: string) => Promise<boolean>; 13 isFile: (path: string) => Promise<boolean>; 14 + isDir: (path: string) => Promise<boolean>; 15 16 join: (...parts: string[]) => string; 17 dirname: (path: string) => string;
+37 -21
packages/types/src/globals.ts
··· 1 import type { Logger } from "./logger"; 2 import type { Config, ConfigExtension } from "./config"; 3 - import type { 4 - DetectedExtension, 5 - IdentifiedPatch, 6 - IdentifiedWebpackModule, 7 - ProcessedExtensions 8 - } from "./extension"; 9 import type EventEmitter from "events"; 10 import type LunAST from "@moonlight-mod/lunast"; 11 import type Moonmap from "@moonlight-mod/moonmap"; 12 import type { 13 - EventPayloads, 14 - EventType, 15 - MoonlightEventEmitter 16 } from "./core/event"; 17 18 export type MoonlightHost = { 19 - asarPath: string; 20 config: Config; 21 - events: EventEmitter; 22 extensions: DetectedExtension[]; 23 processedExtensions: ProcessedExtensions; 24 25 version: string; 26 branch: MoonlightBranch; 27 28 getConfig: (ext: string) => ConfigExtension["config"]; 29 getConfigOption: <T>(ext: string, name: string) => T | undefined; 30 getLogger: (id: string) => Logger; 31 }; 32 33 export type MoonlightNode = { ··· 36 processedExtensions: ProcessedExtensions; 37 nativesCache: Record<string, any>; 38 isBrowser: boolean; 39 40 version: string; 41 branch: MoonlightBranch; 42 43 getConfig: (ext: string) => ConfigExtension["config"]; 44 getConfigOption: <T>(ext: string, name: string) => T | undefined; 45 getNatives: (ext: string) => any | undefined; 46 getLogger: (id: string) => Logger; 47 - 48 getMoonlightDir: () => string; 49 getExtensionDir: (ext: string) => string; 50 - writeConfig: (config: Config) => Promise<void>; 51 }; 52 53 export type MoonlightWeb = { 54 unpatched: Set<IdentifiedPatch>; 55 pendingModules: Set<IdentifiedWebpackModule>; 56 enabledExtensions: Set<string>; 57 - apiLevel: number; 58 - events: MoonlightEventEmitter<EventType, EventPayloads>; 59 patchingInternals: { 60 - onModuleLoad: ( 61 - moduleId: string | string[], 62 - callback: (moduleId: string) => void 63 - ) => void; 64 registerPatch: (patch: IdentifiedPatch) => void; 65 registerWebpackModule: (module: IdentifiedWebpackModule) => void; 66 }; 67 68 version: string; 69 branch: MoonlightBranch; 70 71 - getConfig: (ext: string) => ConfigExtension["config"]; 72 - getConfigOption: <T>(ext: string, name: string) => T | undefined; 73 getNatives: (ext: string) => any | undefined; 74 getLogger: (id: string) => Logger; 75 lunast: LunAST; 76 moonmap: Moonmap; 77 };
··· 1 import type { Logger } from "./logger"; 2 import type { Config, ConfigExtension } from "./config"; 3 + import type { DetectedExtension, IdentifiedPatch, IdentifiedWebpackModule, ProcessedExtensions } from "./extension"; 4 import type EventEmitter from "events"; 5 import type LunAST from "@moonlight-mod/lunast"; 6 import type Moonmap from "@moonlight-mod/moonmap"; 7 import type { 8 + WebEventPayloads, 9 + WebEventType, 10 + MoonlightEventEmitter, 11 + NodeEventType, 12 + NodeEventPayloads 13 } from "./core/event"; 14 + import type { MoonlightFS } from "./fs"; 15 16 export type MoonlightHost = { 17 config: Config; 18 extensions: DetectedExtension[]; 19 processedExtensions: ProcessedExtensions; 20 + asarPath: string; 21 + events: EventEmitter; 22 23 version: string; 24 branch: MoonlightBranch; 25 26 getConfig: (ext: string) => ConfigExtension["config"]; 27 + getConfigPath: () => Promise<string>; 28 getConfigOption: <T>(ext: string, name: string) => T | undefined; 29 + setConfigOption: <T>(ext: string, name: string, value: T) => void; 30 + writeConfig: (config: Config) => Promise<void>; 31 + 32 getLogger: (id: string) => Logger; 33 + getMoonlightDir: () => string; 34 + getExtensionDir: (ext: string) => string; 35 }; 36 37 export type MoonlightNode = { ··· 40 processedExtensions: ProcessedExtensions; 41 nativesCache: Record<string, any>; 42 isBrowser: boolean; 43 + events: MoonlightEventEmitter<NodeEventType, NodeEventPayloads>; 44 45 version: string; 46 branch: MoonlightBranch; 47 48 getConfig: (ext: string) => ConfigExtension["config"]; 49 getConfigOption: <T>(ext: string, name: string) => T | undefined; 50 + setConfigOption: <T>(ext: string, name: string, value: T) => Promise<void>; 51 + writeConfig: (config: Config) => Promise<void>; 52 + 53 getNatives: (ext: string) => any | undefined; 54 getLogger: (id: string) => Logger; 55 getMoonlightDir: () => string; 56 getExtensionDir: (ext: string) => string; 57 + }; 58 + 59 + export type MoonlightNodeSandboxed = { 60 + fs: MoonlightFS; 61 + addCors: (url: string) => void; 62 + addBlocked: (url: string) => void; 63 }; 64 65 export type MoonlightWeb = { 66 + patched: Map<string, Set<string>>; 67 unpatched: Set<IdentifiedPatch>; 68 pendingModules: Set<IdentifiedWebpackModule>; 69 enabledExtensions: Set<string>; 70 + events: MoonlightEventEmitter<WebEventType, WebEventPayloads>; 71 patchingInternals: { 72 + onModuleLoad: (moduleId: string | string[], callback: (moduleId: string) => void) => void; 73 registerPatch: (patch: IdentifiedPatch) => void; 74 registerWebpackModule: (module: IdentifiedWebpackModule) => void; 75 }; 76 + localStorage: Storage; 77 78 version: string; 79 branch: MoonlightBranch; 80 + apiLevel: number; 81 82 + // Re-exports for ease of use 83 + getConfig: MoonlightNode["getConfig"]; 84 + getConfigOption: MoonlightNode["getConfigOption"]; 85 + setConfigOption: MoonlightNode["setConfigOption"]; 86 + writeConfig: MoonlightNode["writeConfig"]; 87 + 88 getNatives: (ext: string) => any | undefined; 89 getLogger: (id: string) => Logger; 90 + 91 lunast: LunAST; 92 moonmap: Moonmap; 93 };
+38
packages/types/src/import.d.ts
··· 1 declare module "@moonlight-mod/wp/common_stores"; 2 3 declare module "@moonlight-mod/wp/contextMenu_evilMenu" { 4 import { CoreExtensions } from "@moonlight-mod/types";
··· 1 + declare module "@moonlight-mod/wp/appPanels_appPanels" { 2 + import { CoreExtensions } from "@moonlight-mod/types"; 3 + const AppPanels: CoreExtensions.AppPanels.AppPanels; 4 + export = AppPanels; 5 + } 6 + 7 + declare module "@moonlight-mod/wp/commands_commands" { 8 + import { CoreExtensions } from "@moonlight-mod/types"; 9 + export const commands: CoreExtensions.Commands.Commands; 10 + export default commands; 11 + } 12 + 13 + declare module "@moonlight-mod/wp/common_ErrorBoundary" { 14 + import { CoreExtensions } from "@moonlight-mod/types"; 15 + const ErrorBoundary: CoreExtensions.Common.ErrorBoundary; 16 + export = ErrorBoundary; 17 + } 18 + declare module "@moonlight-mod/wp/common_icons" { 19 + import { CoreExtensions } from "@moonlight-mod/types"; 20 + export const icons: CoreExtensions.Common.Icons; 21 + export default icons; 22 + } 23 declare module "@moonlight-mod/wp/common_stores"; 24 + 25 + declare module "@moonlight-mod/wp/componentEditor_dmList" { 26 + import { CoreExtensions } from "@moonlight-mod/types"; 27 + export const dmList: CoreExtensions.ComponentEditor.DMList; 28 + export default dmList; 29 + } 30 + declare module "@moonlight-mod/wp/componentEditor_memberList" { 31 + import { CoreExtensions } from "@moonlight-mod/types"; 32 + export const memberList: CoreExtensions.ComponentEditor.MemberList; 33 + export default memberList; 34 + } 35 + declare module "@moonlight-mod/wp/componentEditor_messages" { 36 + import { CoreExtensions } from "@moonlight-mod/types"; 37 + export const message: CoreExtensions.ComponentEditor.Messages; 38 + export default message; 39 + } 40 41 declare module "@moonlight-mod/wp/contextMenu_evilMenu" { 42 import { CoreExtensions } from "@moonlight-mod/types";
+5 -10
packages/types/src/index.ts
··· 4 /// <reference types="./mappings" /> 5 /* eslint-disable no-var */ 6 7 - import { MoonlightFS } from "./fs"; 8 - import { 9 - MoonlightEnv, 10 - MoonlightHost, 11 - MoonlightNode, 12 - MoonlightWeb 13 - } from "./globals"; 14 15 export * from "./discord"; 16 export * from "./config"; ··· 36 37 var moonlightHost: MoonlightHost; 38 var moonlightNode: MoonlightNode; 39 var moonlight: MoonlightWeb; 40 - var moonlightFS: MoonlightFS; 41 42 - var _moonlightBrowserInit: () => Promise<void>; 43 - var _moonlightBrowserLoad: () => Promise<void>; 44 }
··· 4 /// <reference types="./mappings" /> 5 /* eslint-disable no-var */ 6 7 + import { MoonlightEnv, MoonlightHost, MoonlightNode, MoonlightNodeSandboxed, MoonlightWeb } from "./globals"; 8 9 export * from "./discord"; 10 export * from "./config"; ··· 30 31 var moonlightHost: MoonlightHost; 32 var moonlightNode: MoonlightNode; 33 + var moonlightNodeSandboxed: MoonlightNodeSandboxed; 34 var moonlight: MoonlightWeb; 35 + var _moonlight_coreExtensionsStr: string; 36 37 + var _moonlightBrowserInit: undefined | (() => Promise<void>); 38 + var _moonlightWebLoad: undefined | (() => Promise<void>); 39 }
+860 -14
packages/types/src/mappings.d.ts
··· 1 // auto-generated 2 declare module "@moonlight-mod/wp/discord/Dispatcher" { 3 import { MappedModules } from "@moonlight-mod/mappings"; 4 - const _: MappedModules["discord/Dispatcher"]; 5 - export = _; 6 } 7 8 declare module "@moonlight-mod/wp/discord/components/common/index" { 9 import { MappedModules } from "@moonlight-mod/mappings"; 10 - const _: MappedModules["discord/components/common/index"]; 11 - export = _; 12 } 13 14 - declare module "@moonlight-mod/wp/discord/modules/guild_settings/IntegrationCard.css" { 15 import { MappedModules } from "@moonlight-mod/mappings"; 16 - const _: MappedModules["discord/modules/guild_settings/IntegrationCard.css"]; 17 - export = _; 18 } 19 20 declare module "@moonlight-mod/wp/discord/modules/markup/MarkupUtils" { 21 import { MappedModules } from "@moonlight-mod/mappings"; 22 - const _: MappedModules["discord/modules/markup/MarkupUtils"]; 23 - export = _; 24 } 25 26 declare module "@moonlight-mod/wp/discord/packages/flux" { 27 import { MappedModules } from "@moonlight-mod/mappings"; 28 - const _: MappedModules["discord/packages/flux"]; 29 - export = _; 30 } 31 32 declare module "@moonlight-mod/wp/discord/uikit/Flex" { 33 import { MappedModules } from "@moonlight-mod/mappings"; 34 - const _: MappedModules["discord/uikit/Flex"]; 35 - export = _; 36 } 37 38 declare module "@moonlight-mod/wp/react" { 39 import { MappedModules } from "@moonlight-mod/mappings"; 40 - const _: MappedModules["react"]; 41 export = _; 42 }
··· 1 // auto-generated 2 + declare module "@moonlight-mod/wp/chroma-js" {} 3 + 4 + declare module "@moonlight-mod/wp/classnames" { 5 + import { MappedModules } from "@moonlight-mod/mappings"; 6 + const _default: MappedModules["classnames"]["default"]; 7 + export default _default; 8 + } 9 + 10 + declare module "@moonlight-mod/wp/dependency-graph" { 11 + import { MappedModules } from "@moonlight-mod/mappings"; 12 + export const DepGraph: MappedModules["dependency-graph"]["DepGraph"]; 13 + } 14 + 15 + declare module "@moonlight-mod/wp/discord/Constants" { 16 + import { MappedModules } from "@moonlight-mod/mappings"; 17 + export const ActivityFlags: MappedModules["discord/Constants"]["ActivityFlags"]; 18 + export const ActivityTypes: MappedModules["discord/Constants"]["ActivityTypes"]; 19 + export const AnalyticsLocations: MappedModules["discord/Constants"]["AnalyticsLocations"]; 20 + export const ChannelLayouts: MappedModules["discord/Constants"]["ChannelLayouts"]; 21 + export const ChannelModes: MappedModules["discord/Constants"]["ChannelModes"]; 22 + export const ChannelTypes: MappedModules["discord/Constants"]["ChannelTypes"]; 23 + export const ChannelStreamTypes: MappedModules["discord/Constants"]["ChannelStreamTypes"]; 24 + export const ComponentActions: MappedModules["discord/Constants"]["ComponentActions"]; 25 + export const DEFAULT_ROLE_COLOR: MappedModules["discord/Constants"]["DEFAULT_ROLE_COLOR"]; 26 + export const Endpoints: MappedModules["discord/Constants"]["Endpoints"]; 27 + export const MessageFlags: MappedModules["discord/Constants"]["MessageFlags"]; 28 + export const MessageTypes: MappedModules["discord/Constants"]["MessageTypes"]; 29 + export const Permissions: MappedModules["discord/Constants"]["Permissions"]; 30 + export const PlatformTypes: MappedModules["discord/Constants"]["PlatformTypes"]; 31 + export const RelationshipTypes: MappedModules["discord/Constants"]["RelationshipTypes"]; 32 + export const Routes: MappedModules["discord/Constants"]["Routes"]; 33 + export const StatusTypes: MappedModules["discord/Constants"]["StatusTypes"]; 34 + export const Themes: MappedModules["discord/Constants"]["Themes"]; 35 + export const UserSettingsSections: MappedModules["discord/Constants"]["UserSettingsSections"]; 36 + export const UserFlags: MappedModules["discord/Constants"]["UserFlags"]; 37 + } 38 + 39 declare module "@moonlight-mod/wp/discord/Dispatcher" { 40 import { MappedModules } from "@moonlight-mod/mappings"; 41 + const _default: MappedModules["discord/Dispatcher"]["default"]; 42 + export default _default; 43 + } 44 + 45 + declare module "@moonlight-mod/wp/discord/actions/ContextMenuActionCreators" { 46 + import { MappedModules } from "@moonlight-mod/mappings"; 47 + export const closeContextMenu: MappedModules["discord/actions/ContextMenuActionCreators"]["closeContextMenu"]; 48 + export const openContextMenu: MappedModules["discord/actions/ContextMenuActionCreators"]["openContextMenu"]; 49 + export const openContextMenuLazy: MappedModules["discord/actions/ContextMenuActionCreators"]["openContextMenuLazy"]; 50 + } 51 + 52 + declare module "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators" { 53 + import { MappedModules } from "@moonlight-mod/mappings"; 54 + const _default: MappedModules["discord/actions/UserSettingsModalActionCreators"]["default"]; 55 + export default _default; 56 + } 57 + 58 + declare module "@moonlight-mod/wp/discord/common/AppStartPerformance" { 59 + import { MappedModules } from "@moonlight-mod/mappings"; 60 + const _default: MappedModules["discord/common/AppStartPerformance"]["default"]; 61 + export default _default; 62 + } 63 + 64 + declare module "@moonlight-mod/wp/discord/components/common/Alerts" { 65 + import { MappedModules } from "@moonlight-mod/mappings"; 66 + const _default: MappedModules["discord/components/common/Alerts"]["default"]; 67 + export default _default; 68 + } 69 + 70 + declare module "@moonlight-mod/wp/discord/components/common/BaseHeaderBar" { 71 + import { MappedModules } from "@moonlight-mod/mappings"; 72 + export const Icon: MappedModules["discord/components/common/BaseHeaderBar"]["Icon"]; 73 + export const Divider: MappedModules["discord/components/common/BaseHeaderBar"]["Divider"]; 74 + const _default: MappedModules["discord/components/common/BaseHeaderBar"]["default"]; 75 + export default _default; 76 + } 77 + 78 + declare module "@moonlight-mod/wp/discord/components/common/Card" { 79 + import { MappedModules } from "@moonlight-mod/mappings"; 80 + const _default: MappedModules["discord/components/common/Card"]["default"]; 81 + export default _default; 82 + export const Types: MappedModules["discord/components/common/Card"]["Types"]; 83 + } 84 + 85 + declare module "@moonlight-mod/wp/discord/components/common/FileUpload" { 86 + import { MappedModules } from "@moonlight-mod/mappings"; 87 + const _default: MappedModules["discord/components/common/FileUpload"]["default"]; 88 + export default _default; 89 + } 90 + 91 + declare module "@moonlight-mod/wp/discord/components/common/FormSwitch.css" { 92 + import { MappedModules } from "@moonlight-mod/mappings"; 93 + export const container: MappedModules["discord/components/common/FormSwitch.css"]["container"]; 94 + export const labelRow: MappedModules["discord/components/common/FormSwitch.css"]["labelRow"]; 95 + export const control: MappedModules["discord/components/common/FormSwitch.css"]["control"]; 96 + export const disabled: MappedModules["discord/components/common/FormSwitch.css"]["disabled"]; 97 + export const title: MappedModules["discord/components/common/FormSwitch.css"]["title"]; 98 + export const note: MappedModules["discord/components/common/FormSwitch.css"]["note"]; 99 + export const disabledText: MappedModules["discord/components/common/FormSwitch.css"]["disabledText"]; 100 + export const dividerDefault: MappedModules["discord/components/common/FormSwitch.css"]["dividerDefault"]; 101 + } 102 + 103 + declare module "@moonlight-mod/wp/discord/components/common/HeaderBar.css" { 104 + import { MappedModules } from "@moonlight-mod/mappings"; 105 + export const caret: MappedModules["discord/components/common/HeaderBar.css"]["caret"]; 106 + export const children: MappedModules["discord/components/common/HeaderBar.css"]["children"]; 107 + export const clickable: MappedModules["discord/components/common/HeaderBar.css"]["clickable"]; 108 + export const container: MappedModules["discord/components/common/HeaderBar.css"]["container"]; 109 + export const divider: MappedModules["discord/components/common/HeaderBar.css"]["divider"]; 110 + export const dot: MappedModules["discord/components/common/HeaderBar.css"]["dot"]; 111 + export const hamburger: MappedModules["discord/components/common/HeaderBar.css"]["hamburger"]; 112 + export const icon: MappedModules["discord/components/common/HeaderBar.css"]["icon"]; 113 + export const iconBadge: MappedModules["discord/components/common/HeaderBar.css"]["iconBadge"]; 114 + export const iconBadgeBottom: MappedModules["discord/components/common/HeaderBar.css"]["iconBadgeBottom"]; 115 + export const iconBadgeTop: MappedModules["discord/components/common/HeaderBar.css"]["iconBadgeTop"]; 116 + export const iconWrapper: MappedModules["discord/components/common/HeaderBar.css"]["iconWrapper"]; 117 + export const scrollable: MappedModules["discord/components/common/HeaderBar.css"]["scrollable"]; 118 + export const selected: MappedModules["discord/components/common/HeaderBar.css"]["selected"]; 119 + export const themed: MappedModules["discord/components/common/HeaderBar.css"]["themed"]; 120 + export const themedMobile: MappedModules["discord/components/common/HeaderBar.css"]["themedMobile"]; 121 + export const title: MappedModules["discord/components/common/HeaderBar.css"]["title"]; 122 + export const titleWrapper: MappedModules["discord/components/common/HeaderBar.css"]["titleWrapper"]; 123 + export const toolbar: MappedModules["discord/components/common/HeaderBar.css"]["toolbar"]; 124 + export const transparent: MappedModules["discord/components/common/HeaderBar.css"]["transparent"]; 125 + export const upperContainer: MappedModules["discord/components/common/HeaderBar.css"]["upperContainer"]; 126 + } 127 + 128 + declare module "@moonlight-mod/wp/discord/components/common/HelpMessage.css" { 129 + import { MappedModules } from "@moonlight-mod/mappings"; 130 + export const container: MappedModules["discord/components/common/HelpMessage.css"]["container"]; 131 + export const icon: MappedModules["discord/components/common/HelpMessage.css"]["icon"]; 132 + export const iconDiv: MappedModules["discord/components/common/HelpMessage.css"]["iconDiv"]; 133 + export const text: MappedModules["discord/components/common/HelpMessage.css"]["text"]; 134 + export const positive: MappedModules["discord/components/common/HelpMessage.css"]["positive"]; 135 + export const warning: MappedModules["discord/components/common/HelpMessage.css"]["warning"]; 136 + export const info: MappedModules["discord/components/common/HelpMessage.css"]["info"]; 137 + export const error: MappedModules["discord/components/common/HelpMessage.css"]["error"]; 138 + } 139 + 140 + declare module "@moonlight-mod/wp/discord/components/common/Image" {} 141 + 142 + declare module "@moonlight-mod/wp/discord/components/common/PanelButton" { 143 + import { MappedModules } from "@moonlight-mod/mappings"; 144 + const _default: MappedModules["discord/components/common/PanelButton"]["default"]; 145 + export default _default; 146 + } 147 + 148 + declare module "@moonlight-mod/wp/discord/components/common/Scroller.css" { 149 + import { MappedModules } from "@moonlight-mod/mappings"; 150 + export const auto: MappedModules["discord/components/common/Scroller.css"]["auto"]; 151 + export const content: MappedModules["discord/components/common/Scroller.css"]["content"]; 152 + export const customTheme: MappedModules["discord/components/common/Scroller.css"]["customTheme"]; 153 + export const disableScrollAnchor: MappedModules["discord/components/common/Scroller.css"]["disableScrollAnchor"]; 154 + export const fade: MappedModules["discord/components/common/Scroller.css"]["fade"]; 155 + export const managedReactiveScroller: MappedModules["discord/components/common/Scroller.css"]["managedReactiveScroller"]; 156 + export const none: MappedModules["discord/components/common/Scroller.css"]["none"]; 157 + export const pointerCover: MappedModules["discord/components/common/Scroller.css"]["pointerCover"]; 158 + export const scrolling: MappedModules["discord/components/common/Scroller.css"]["scrolling"]; 159 + export const thin: MappedModules["discord/components/common/Scroller.css"]["thin"]; 160 } 161 162 declare module "@moonlight-mod/wp/discord/components/common/index" { 163 import { MappedModules } from "@moonlight-mod/mappings"; 164 + export const Clickable: MappedModules["discord/components/common/index"]["Clickable"]; 165 + export const TextInput: MappedModules["discord/components/common/index"]["TextInput"]; 166 + export const TextArea: MappedModules["discord/components/common/index"]["TextArea"]; 167 + export const FormDivider: MappedModules["discord/components/common/index"]["FormDivider"]; 168 + export const FormSection: MappedModules["discord/components/common/index"]["FormSection"]; 169 + export const FormText: MappedModules["discord/components/common/index"]["FormText"]; 170 + export const FormTitle: MappedModules["discord/components/common/index"]["FormTitle"]; 171 + export const FormSwitch: MappedModules["discord/components/common/index"]["FormSwitch"]; 172 + export const FormItem: MappedModules["discord/components/common/index"]["FormItem"]; 173 + export const Slider: MappedModules["discord/components/common/index"]["Slider"]; 174 + export const Switch: MappedModules["discord/components/common/index"]["Switch"]; 175 + export const Button: MappedModules["discord/components/common/index"]["Button"]; 176 + export const Tooltip: MappedModules["discord/components/common/index"]["Tooltip"]; 177 + export const Avatar: MappedModules["discord/components/common/index"]["Avatar"]; 178 + export const AvatarSizes: MappedModules["discord/components/common/index"]["AvatarSizes"]; 179 + export const AvatarSizeSpecs: MappedModules["discord/components/common/index"]["AvatarSizeSpecs"]; 180 + export const Scroller: MappedModules["discord/components/common/index"]["Scroller"]; 181 + export const Text: MappedModules["discord/components/common/index"]["Text"]; 182 + export const Heading: MappedModules["discord/components/common/index"]["Heading"]; 183 + export const Card: MappedModules["discord/components/common/index"]["Card"]; 184 + export const Popout: MappedModules["discord/components/common/index"]["Popout"]; 185 + export const Dialog: MappedModules["discord/components/common/index"]["Dialog"]; 186 + export const Menu: MappedModules["discord/components/common/index"]["Menu"]; 187 + export const TabBar: MappedModules["discord/components/common/index"]["TabBar"]; 188 + export const SingleSelect: MappedModules["discord/components/common/index"]["SingleSelect"]; 189 + export const Select: MappedModules["discord/components/common/index"]["Select"]; 190 + export const NoticeColors: MappedModules["discord/components/common/index"]["NoticeColors"]; 191 + export const Notice: MappedModules["discord/components/common/index"]["Notice"]; 192 + export const NoticeCloseButton: MappedModules["discord/components/common/index"]["NoticeCloseButton"]; 193 + export const PrimaryCTANoticeButton: MappedModules["discord/components/common/index"]["PrimaryCTANoticeButton"]; 194 + export const Breadcrumbs: MappedModules["discord/components/common/index"]["Breadcrumbs"]; 195 + export const Image: MappedModules["discord/components/common/index"]["Image"]; 196 + export const tokens: MappedModules["discord/components/common/index"]["tokens"]; 197 + export const useVariableSelect: MappedModules["discord/components/common/index"]["useVariableSelect"]; 198 + export const useMultiSelect: MappedModules["discord/components/common/index"]["useMultiSelect"]; 199 + export const multiSelect: MappedModules["discord/components/common/index"]["multiSelect"]; 200 + export const openModal: MappedModules["discord/components/common/index"]["openModal"]; 201 + export const openModalLazy: MappedModules["discord/components/common/index"]["openModalLazy"]; 202 + export const closeModal: MappedModules["discord/components/common/index"]["closeModal"]; 203 + export const AngleBracketsIcon: MappedModules["discord/components/common/index"]["AngleBracketsIcon"]; 204 + export const ArrowAngleLeftUpIcon: MappedModules["discord/components/common/index"]["ArrowAngleLeftUpIcon"]; 205 + export const ArrowAngleRightUpIcon: MappedModules["discord/components/common/index"]["ArrowAngleRightUpIcon"]; 206 + export const ArrowsUpDownIcon: MappedModules["discord/components/common/index"]["ArrowsUpDownIcon"]; 207 + export const BookCheckIcon: MappedModules["discord/components/common/index"]["BookCheckIcon"]; 208 + export const ChannelListIcon: MappedModules["discord/components/common/index"]["ChannelListIcon"]; 209 + export const ChevronSmallDownIcon: MappedModules["discord/components/common/index"]["ChevronSmallDownIcon"]; 210 + export const ChevronSmallUpIcon: MappedModules["discord/components/common/index"]["ChevronSmallUpIcon"]; 211 + export const CircleInformationIcon: MappedModules["discord/components/common/index"]["CircleInformationIcon"]; 212 + export const CircleWarningIcon: MappedModules["discord/components/common/index"]["CircleWarningIcon"]; 213 + export const CircleXIcon: MappedModules["discord/components/common/index"]["CircleXIcon"]; 214 + export const ClydeIcon: MappedModules["discord/components/common/index"]["ClydeIcon"]; 215 + export const CopyIcon: MappedModules["discord/components/common/index"]["CopyIcon"]; 216 + export const DownloadIcon: MappedModules["discord/components/common/index"]["DownloadIcon"]; 217 + export const FullscreenEnterIcon: MappedModules["discord/components/common/index"]["FullscreenEnterIcon"]; 218 + export const GameControllerIcon: MappedModules["discord/components/common/index"]["GameControllerIcon"]; 219 + export const GlobeEarthIcon: MappedModules["discord/components/common/index"]["GlobeEarthIcon"]; 220 + export const HeartIcon: MappedModules["discord/components/common/index"]["HeartIcon"]; 221 + export const LinkIcon: MappedModules["discord/components/common/index"]["LinkIcon"]; 222 + export const MaximizeIcon: MappedModules["discord/components/common/index"]["MaximizeIcon"]; 223 + export const MinusIcon: MappedModules["discord/components/common/index"]["MinusIcon"]; 224 + export const MobilePhoneIcon: MappedModules["discord/components/common/index"]["MobilePhoneIcon"]; 225 + export const PauseIcon: MappedModules["discord/components/common/index"]["PauseIcon"]; 226 + export const PlayIcon: MappedModules["discord/components/common/index"]["PlayIcon"]; 227 + export const PlusLargeIcon: MappedModules["discord/components/common/index"]["PlusLargeIcon"]; 228 + export const RetryIcon: MappedModules["discord/components/common/index"]["RetryIcon"]; 229 + export const ScienceIcon: MappedModules["discord/components/common/index"]["ScienceIcon"]; 230 + export const ScreenIcon: MappedModules["discord/components/common/index"]["ScreenIcon"]; 231 + export const StarIcon: MappedModules["discord/components/common/index"]["StarIcon"]; 232 + export const TrashIcon: MappedModules["discord/components/common/index"]["TrashIcon"]; 233 + export const WarningIcon: MappedModules["discord/components/common/index"]["WarningIcon"]; 234 + export const WindowLaunchIcon: MappedModules["discord/components/common/index"]["WindowLaunchIcon"]; 235 + export const WindowTopOutlineIcon: MappedModules["discord/components/common/index"]["WindowTopOutlineIcon"]; 236 + export const XLargeIcon: MappedModules["discord/components/common/index"]["XLargeIcon"]; 237 + export const XSmallIcon: MappedModules["discord/components/common/index"]["XSmallIcon"]; 238 + export const ConfirmModal: MappedModules["discord/components/common/index"]["ConfirmModal"]; 239 + export const H: MappedModules["discord/components/common/index"]["H"]; 240 + export const HelpMessage: MappedModules["discord/components/common/index"]["HelpMessage"]; 241 + export const ModalCloseButton: MappedModules["discord/components/common/index"]["ModalCloseButton"]; 242 + export const ModalContent: MappedModules["discord/components/common/index"]["ModalContent"]; 243 + export const ModalFooter: MappedModules["discord/components/common/index"]["ModalFooter"]; 244 + export const ModalHeader: MappedModules["discord/components/common/index"]["ModalHeader"]; 245 + export const ModalRoot: MappedModules["discord/components/common/index"]["ModalRoot"]; 246 + export const NumberInputStepper: MappedModules["discord/components/common/index"]["NumberInputStepper"]; 247 + export const SearchableSelect: MappedModules["discord/components/common/index"]["SearchableSelect"]; 248 + export const createToast: MappedModules["discord/components/common/index"]["createToast"]; 249 + export const popToast: MappedModules["discord/components/common/index"]["popToast"]; 250 + export const showToast: MappedModules["discord/components/common/index"]["showToast"]; 251 + export const useThemeContext: MappedModules["discord/components/common/index"]["useThemeContext"]; 252 + export const AccessibilityAnnouncer: MappedModules["discord/components/common/index"]["AccessibilityAnnouncer"]; 253 + export const BackdropStyles: MappedModules["discord/components/common/index"]["BackdropStyles"]; 254 + export const BadgeShapes: MappedModules["discord/components/common/index"]["BadgeShapes"]; 255 + export const CardTypes: MappedModules["discord/components/common/index"]["CardTypes"]; 256 + export const CircleIconButtonColors: MappedModules["discord/components/common/index"]["CircleIconButtonColors"]; 257 + export const CircleIconButtonSizes: MappedModules["discord/components/common/index"]["CircleIconButtonSizes"]; 258 + export const FormErrorBlockColors: MappedModules["discord/components/common/index"]["FormErrorBlockColors"]; 259 + export const FormNoticeImagePositions: MappedModules["discord/components/common/index"]["FormNoticeImagePositions"]; 260 + export const FormTitleTags: MappedModules["discord/components/common/index"]["FormTitleTags"]; 261 + export const HelpMessageTypes: MappedModules["discord/components/common/index"]["HelpMessageTypes"]; 262 + export const ModalSize: MappedModules["discord/components/common/index"]["ModalSize"]; 263 + export const ModalTransitionState: MappedModules["discord/components/common/index"]["ModalTransitionState"]; 264 + export const PRETTY_KEYS: MappedModules["discord/components/common/index"]["PRETTY_KEYS"]; 265 + export const SelectLooks: MappedModules["discord/components/common/index"]["SelectLooks"]; 266 + export const SpinnerTypes: MappedModules["discord/components/common/index"]["SpinnerTypes"]; 267 + export const StatusTypes: MappedModules["discord/components/common/index"]["StatusTypes"]; 268 + export const ToastPosition: MappedModules["discord/components/common/index"]["ToastPosition"]; 269 + export const ToastType: MappedModules["discord/components/common/index"]["ToastType"]; 270 + export const TransitionStates: MappedModules["discord/components/common/index"]["TransitionStates"]; 271 + export const DEFAULT_MODAL_CONTEXT: MappedModules["discord/components/common/index"]["DEFAULT_MODAL_CONTEXT"]; 272 + export const LOW_SATURATION_THRESHOLD: MappedModules["discord/components/common/index"]["LOW_SATURATION_THRESHOLD"]; 273 + export const LayerClassName: MappedModules["discord/components/common/index"]["LayerClassName"]; 274 + export const POPOUT_MODAL_CONTEXT: MappedModules["discord/components/common/index"]["POPOUT_MODAL_CONTEXT"]; 275 + } 276 + 277 + declare module "@moonlight-mod/wp/discord/components/modals/ConfirmModal" { 278 + import { MappedModules } from "@moonlight-mod/mappings"; 279 + const _default: MappedModules["discord/components/modals/ConfirmModal"]["default"]; 280 + export default _default; 281 } 282 283 + declare module "@moonlight-mod/wp/discord/lib/BaseRecord" { 284 import { MappedModules } from "@moonlight-mod/mappings"; 285 + const _default: MappedModules["discord/lib/BaseRecord"]["default"]; 286 + export default _default; 287 + } 288 + 289 + declare module "@moonlight-mod/wp/discord/lib/web/Storage" { 290 + import { MappedModules } from "@moonlight-mod/mappings"; 291 + export const ObjectStorage: MappedModules["discord/lib/web/Storage"]["ObjectStorage"]; 292 + export const impl: MappedModules["discord/lib/web/Storage"]["impl"]; 293 + } 294 + 295 + declare module "@moonlight-mod/wp/discord/modules/build_overrides/web/BuildOverride.css" { 296 + import { MappedModules } from "@moonlight-mod/mappings"; 297 + export const wrapper: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["wrapper"]; 298 + export const titleRegion: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["titleRegion"]; 299 + export const title: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["title"]; 300 + export const infoIcon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["infoIcon"]; 301 + export const copyLink: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copyLink"]; 302 + export const copied: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copied"]; 303 + export const copyLinkIcon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copyLinkIcon"]; 304 + export const content: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["content"]; 305 + export const infoLink: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["infoLink"]; 306 + export const buildInfo: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buildInfo"]; 307 + export const button: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["button"]; 308 + export const buttonSize: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buttonSize"]; 309 + export const subHead: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["subHead"]; 310 + export const icon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["icon"]; 311 + export const buildDetails: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buildDetails"]; 312 + export const barLoader: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["barLoader"]; 313 + export const barTitle: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["barTitle"]; 314 + export const buttonLoader: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buttonLoader"]; 315 + export const disabledButtonOverride: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["disabledButtonOverride"]; 316 + } 317 + 318 + declare module "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css" { 319 + import { MappedModules } from "@moonlight-mod/mappings"; 320 + export const header: MappedModules["discord/modules/discovery/web/Discovery.css"]["header"]; 321 + export const headerImage: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImage"]; 322 + export const headerImageSimple: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImageSimple"]; 323 + export const headerImageBG: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImageBG"]; 324 + export const searchTitle: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchTitle"]; 325 + export const searchSubtitle: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchSubtitle"]; 326 + export const headerContentWrapper: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContentWrapper"]; 327 + export const headerContent: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContent"]; 328 + export const headerContentSmall: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContentSmall"]; 329 + export const searchBox: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchBox"]; 330 + export const searchBoxInput: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchBoxInput"]; 331 + export const closeIcon: MappedModules["discord/modules/discovery/web/Discovery.css"]["closeIcon"]; 332 + export const searchIcon: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchIcon"]; 333 + export const tabBar: MappedModules["discord/modules/discovery/web/Discovery.css"]["tabBar"]; 334 + export const tabBarItem: MappedModules["discord/modules/discovery/web/Discovery.css"]["tabBarItem"]; 335 + export const sectionHeader: MappedModules["discord/modules/discovery/web/Discovery.css"]["sectionHeader"]; 336 + } 337 + 338 + declare module "@moonlight-mod/wp/discord/modules/forums/web/Forums.css" { 339 + import { MappedModules } from "@moonlight-mod/mappings"; 340 + export const container: MappedModules["discord/modules/forums/web/Forums.css"]["container"]; 341 + export const uploadArea: MappedModules["discord/modules/forums/web/Forums.css"]["uploadArea"]; 342 + export const label: MappedModules["discord/modules/forums/web/Forums.css"]["label"]; 343 + export const content: MappedModules["discord/modules/forums/web/Forums.css"]["content"]; 344 + export const noListContainer: MappedModules["discord/modules/forums/web/Forums.css"]["noListContainer"]; 345 + export const list: MappedModules["discord/modules/forums/web/Forums.css"]["list"]; 346 + export const grid: MappedModules["discord/modules/forums/web/Forums.css"]["grid"]; 347 + export const headerRow: MappedModules["discord/modules/forums/web/Forums.css"]["headerRow"]; 348 + export const card: MappedModules["discord/modules/forums/web/Forums.css"]["card"]; 349 + export const columnsSpan: MappedModules["discord/modules/forums/web/Forums.css"]["columnsSpan"]; 350 + export const emptyStateRow: MappedModules["discord/modules/forums/web/Forums.css"]["emptyStateRow"]; 351 + export const newMemberBanner: MappedModules["discord/modules/forums/web/Forums.css"]["newMemberBanner"]; 352 + export const gridViewBanner: MappedModules["discord/modules/forums/web/Forums.css"]["gridViewBanner"]; 353 + export const placeholder: MappedModules["discord/modules/forums/web/Forums.css"]["placeholder"]; 354 + export const mainCard: MappedModules["discord/modules/forums/web/Forums.css"]["mainCard"]; 355 + export const emptyMainCard: MappedModules["discord/modules/forums/web/Forums.css"]["emptyMainCard"]; 356 + export const outOfDate: MappedModules["discord/modules/forums/web/Forums.css"]["outOfDate"]; 357 + export const header: MappedModules["discord/modules/forums/web/Forums.css"]["header"]; 358 + export const matchingPostsRow: MappedModules["discord/modules/forums/web/Forums.css"]["matchingPostsRow"]; 359 + export const headerWithMatchingPosts: MappedModules["discord/modules/forums/web/Forums.css"]["headerWithMatchingPosts"]; 360 + export const noForm: MappedModules["discord/modules/forums/web/Forums.css"]["noForm"]; 361 + export const sortContainer: MappedModules["discord/modules/forums/web/Forums.css"]["sortContainer"]; 362 + export const sort: MappedModules["discord/modules/forums/web/Forums.css"]["sort"]; 363 + export const sortPopout: MappedModules["discord/modules/forums/web/Forums.css"]["sortPopout"]; 364 + export const archivedDividerRow: MappedModules["discord/modules/forums/web/Forums.css"]["archivedDividerRow"]; 365 + export const archivedDivider: MappedModules["discord/modules/forums/web/Forums.css"]["archivedDivider"]; 366 + export const newPostsButton: MappedModules["discord/modules/forums/web/Forums.css"]["newPostsButton"]; 367 + export const loadingCard: MappedModules["discord/modules/forums/web/Forums.css"]["loadingCard"]; 368 + export const enterIcon: MappedModules["discord/modules/forums/web/Forums.css"]["enterIcon"]; 369 + export const warnIcon: MappedModules["discord/modules/forums/web/Forums.css"]["warnIcon"]; 370 + export const searchIcon: MappedModules["discord/modules/forums/web/Forums.css"]["searchIcon"]; 371 + export const missingReadHistoryPermission: MappedModules["discord/modules/forums/web/Forums.css"]["missingReadHistoryPermission"]; 372 + export const divider: MappedModules["discord/modules/forums/web/Forums.css"]["divider"]; 373 + export const tagsContainer: MappedModules["discord/modules/forums/web/Forums.css"]["tagsContainer"]; 374 + export const filterIcon: MappedModules["discord/modules/forums/web/Forums.css"]["filterIcon"]; 375 + export const tagList: MappedModules["discord/modules/forums/web/Forums.css"]["tagList"]; 376 + export const tagListInner: MappedModules["discord/modules/forums/web/Forums.css"]["tagListInner"]; 377 + export const tag: MappedModules["discord/modules/forums/web/Forums.css"]["tag"]; 378 + export const tagsButton: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButton"]; 379 + export const tagsButtonInner: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonInner"]; 380 + export const tagsButtonPlaceholder: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonPlaceholder"]; 381 + export const tagsButtonWithCount: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonWithCount"]; 382 + export const sortDropdown: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdown"]; 383 + export const sortDropdownInner: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdownInner"]; 384 + export const sortDropdownText: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdownText"]; 385 + export const clear: MappedModules["discord/modules/forums/web/Forums.css"]["clear"]; 386 + export const matchingPosts: MappedModules["discord/modules/forums/web/Forums.css"]["matchingPosts"]; 387 + export const startPostHelp: MappedModules["discord/modules/forums/web/Forums.css"]["startPostHelp"]; 388 + export const tagsSpacer: MappedModules["discord/modules/forums/web/Forums.css"]["tagsSpacer"]; 389 + export const keyboardShortcut: MappedModules["discord/modules/forums/web/Forums.css"]["keyboardShortcut"]; 390 + export const key: MappedModules["discord/modules/forums/web/Forums.css"]["key"]; 391 + export const countContainer: MappedModules["discord/modules/forums/web/Forums.css"]["countContainer"]; 392 + export const countText: MappedModules["discord/modules/forums/web/Forums.css"]["countText"]; 393 + export const optInNotice: MappedModules["discord/modules/forums/web/Forums.css"]["optInNotice"]; 394 + } 395 + 396 + declare module "@moonlight-mod/wp/discord/modules/forums/web/Header.css" { 397 + import { MappedModules } from "@moonlight-mod/mappings"; 398 + export const container: MappedModules["discord/modules/forums/web/Header.css"]["container"]; 399 + export const header: MappedModules["discord/modules/forums/web/Header.css"]["header"]; 400 + export const headerLeft: MappedModules["discord/modules/forums/web/Header.css"]["headerLeft"]; 401 + export const headerText: MappedModules["discord/modules/forums/web/Header.css"]["headerText"]; 402 + export const countContainer: MappedModules["discord/modules/forums/web/Header.css"]["countContainer"]; 403 + export const countText: MappedModules["discord/modules/forums/web/Header.css"]["countText"]; 404 + export const tagContainer: MappedModules["discord/modules/forums/web/Header.css"]["tagContainer"]; 405 + export const tag: MappedModules["discord/modules/forums/web/Header.css"]["tag"]; 406 + export const clear: MappedModules["discord/modules/forums/web/Header.css"]["clear"]; 407 + export const row: MappedModules["discord/modules/forums/web/Header.css"]["row"]; 408 + export const separator: MappedModules["discord/modules/forums/web/Header.css"]["separator"]; 409 + } 410 + 411 + declare module "@moonlight-mod/wp/discord/modules/forums/web/SortMenu.css" { 412 + import { MappedModules } from "@moonlight-mod/mappings"; 413 + export const container: MappedModules["discord/modules/forums/web/SortMenu.css"]["container"]; 414 + export const clearText: MappedModules["discord/modules/forums/web/SortMenu.css"]["clearText"]; 415 + } 416 + 417 + declare module "@moonlight-mod/wp/discord/modules/forums/web/Tag" { 418 + import { MappedModules } from "@moonlight-mod/mappings"; 419 + const _default: MappedModules["discord/modules/forums/web/Tag"]["default"]; 420 + export default _default; 421 + export const TagBar: MappedModules["discord/modules/forums/web/Tag"]["TagBar"]; 422 + } 423 + 424 + declare module "@moonlight-mod/wp/discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css" { 425 + import { MappedModules } from "@moonlight-mod/mappings"; 426 + export const addButton: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["addButton"]; 427 + export const container: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["container"]; 428 + export const emptyRowContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["emptyRowContainer"]; 429 + export const emptyRowText: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["emptyRowText"]; 430 + export const headerContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["headerContainer"]; 431 + export const list: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["list"]; 432 + export const memberDetails: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["memberDetails"]; 433 + export const memberRow: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["memberRow"]; 434 + export const removeButton: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButton"]; 435 + export const removeButtonContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButtonContainer"]; 436 + export const removeButtonDisabled: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButtonDisabled"]; 437 + export const removeTip: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeTip"]; 438 + export const searchContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["searchContainer"]; 439 + export const searchWarning: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["searchWarning"]; 440 + } 441 + 442 + declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCard.css" { 443 + import { MappedModules } from "@moonlight-mod/mappings"; 444 + export const card: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["card"]; 445 + export const inModal: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["inModal"]; 446 + export const cardHeader: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["cardHeader"]; 447 + export const title: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["title"]; 448 + } 449 + 450 + declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCardItem.css" { 451 + import { MappedModules } from "@moonlight-mod/mappings"; 452 + export const icon: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["icon"]; 453 + export const identifier: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["identifier"]; 454 + export const item: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["item"]; 455 + export const statusContainer: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusContainer"]; 456 + export const statusLine: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusLine"]; 457 + export const statusIcon: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusIcon"]; 458 + } 459 + 460 + declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/SearchSection.css" { 461 + import { MappedModules } from "@moonlight-mod/mappings"; 462 + export const container: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["container"]; 463 + export const headerContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["headerContainer"]; 464 + export const searchContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["searchContainer"]; 465 + export const searchWarning: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["searchWarning"]; 466 + export const addButton: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["addButton"]; 467 + export const memberRow: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["memberRow"]; 468 + export const emptyRowContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["emptyRowContainer"]; 469 + export const emptyRowText: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["emptyRowText"]; 470 + export const memberDetails: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["memberDetails"]; 471 + export const list: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["list"]; 472 + export const removeButtonContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButtonContainer"]; 473 + export const removeButton: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButton"]; 474 + export const removeButtonDisabled: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButtonDisabled"]; 475 + export const removeTip: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeTip"]; 476 + } 477 + 478 + declare module "@moonlight-mod/wp/discord/modules/guild_sidebar/web/CategoryChannel.css" { 479 + import { MappedModules } from "@moonlight-mod/mappings"; 480 + export const containerDefault: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDefault"]; 481 + export const containerDragBefore: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDragBefore"]; 482 + export const containerDragAfter: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDragAfter"]; 483 + export const addButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["addButton"]; 484 + export const forceVisible: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["forceVisible"]; 485 + export const iconVisibility: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["iconVisibility"]; 486 + export const addButtonIcon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["addButtonIcon"]; 487 + export const wrapper: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["wrapper"]; 488 + export const wrapperStatic: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["wrapperStatic"]; 489 + export const clickable: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["clickable"]; 490 + export const children: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["children"]; 491 + export const mainContent: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["mainContent"]; 492 + export const icon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["icon"]; 493 + export const collapsed: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["collapsed"]; 494 + export const muted: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["muted"]; 495 + export const name: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["name"]; 496 + export const dismissWrapper: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismissWrapper"]; 497 + export const dismissButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismissButton"]; 498 + export const dismiss: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismiss"]; 499 + export const voiceChannelsButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["voiceChannelsButton"]; 500 + export const voiceChannelsToggleIcon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["voiceChannelsToggleIcon"]; 501 + export const refreshVoiceChannelsButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["refreshVoiceChannelsButton"]; 502 + export const refreshVoiceChannelsButtonInner: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["refreshVoiceChannelsButtonInner"]; 503 } 504 505 declare module "@moonlight-mod/wp/discord/modules/markup/MarkupUtils" { 506 import { MappedModules } from "@moonlight-mod/mappings"; 507 + const _default: MappedModules["discord/modules/markup/MarkupUtils"]["default"]; 508 + export default _default; 509 + } 510 + 511 + declare module "@moonlight-mod/wp/discord/modules/menus/web/Menu" { 512 + import { MappedModules } from "@moonlight-mod/mappings"; 513 + export const MenuSpinner: MappedModules["discord/modules/menus/web/Menu"]["MenuSpinner"]; 514 + export const Menu: MappedModules["discord/modules/menus/web/Menu"]["Menu"]; 515 + } 516 + 517 + declare module "@moonlight-mod/wp/discord/modules/messages/web/Markup.css" { 518 + import { MappedModules } from "@moonlight-mod/mappings"; 519 + export const markup: MappedModules["discord/modules/messages/web/Markup.css"]["markup"]; 520 + export const inlineFormat: MappedModules["discord/modules/messages/web/Markup.css"]["inlineFormat"]; 521 + export const codeContainer: MappedModules["discord/modules/messages/web/Markup.css"]["codeContainer"]; 522 + export const codeActions: MappedModules["discord/modules/messages/web/Markup.css"]["codeActions"]; 523 + export const blockquoteContainer: MappedModules["discord/modules/messages/web/Markup.css"]["blockquoteContainer"]; 524 + export const blockquoteDivider: MappedModules["discord/modules/messages/web/Markup.css"]["blockquoteDivider"]; 525 + export const slateBlockquoteContainer: MappedModules["discord/modules/messages/web/Markup.css"]["slateBlockquoteContainer"]; 526 + export const roleMention: MappedModules["discord/modules/messages/web/Markup.css"]["roleMention"]; 527 + export const rolePopout: MappedModules["discord/modules/messages/web/Markup.css"]["rolePopout"]; 528 + export const roleHeader: MappedModules["discord/modules/messages/web/Markup.css"]["roleHeader"]; 529 + export const roleScroller: MappedModules["discord/modules/messages/web/Markup.css"]["roleScroller"]; 530 + export const timestamp: MappedModules["discord/modules/messages/web/Markup.css"]["timestamp"]; 531 + export const timestampTooltip: MappedModules["discord/modules/messages/web/Markup.css"]["timestampTooltip"]; 532 + } 533 + 534 + declare module "@moonlight-mod/wp/discord/modules/messages/web/Message.css" { 535 + import { MappedModules } from "@moonlight-mod/mappings"; 536 + export const wrapper: MappedModules["discord/modules/messages/web/Message.css"]["wrapper"]; 537 + export const compact: MappedModules["discord/modules/messages/web/Message.css"]["compact"]; 538 + export const cozy: MappedModules["discord/modules/messages/web/Message.css"]["cozy"]; 539 + export const contentOnly: MappedModules["discord/modules/messages/web/Message.css"]["contentOnly"]; 540 + export const repliedMessage: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessage"]; 541 + export const threadMessageAccessory: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessory"]; 542 + export const executedCommand: MappedModules["discord/modules/messages/web/Message.css"]["executedCommand"]; 543 + export const latin12CompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["latin12CompactTimeStamp"]; 544 + export const latin24CompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["latin24CompactTimeStamp"]; 545 + export const asianCompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["asianCompactTimeStamp"]; 546 + export const contextCommandMessage: MappedModules["discord/modules/messages/web/Message.css"]["contextCommandMessage"]; 547 + export const messageSpine: MappedModules["discord/modules/messages/web/Message.css"]["messageSpine"]; 548 + export const repliedMessageClickableSpine: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageClickableSpine"]; 549 + export const repliedMessageContentHovered: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageContentHovered"]; 550 + export const threadMessageAccessoryAvatar: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryAvatar"]; 551 + export const replyAvatar: MappedModules["discord/modules/messages/web/Message.css"]["replyAvatar"]; 552 + export const replyBadge: MappedModules["discord/modules/messages/web/Message.css"]["replyBadge"]; 553 + export const executedCommandAvatar: MappedModules["discord/modules/messages/web/Message.css"]["executedCommandAvatar"]; 554 + export const replyChatIconContainer: MappedModules["discord/modules/messages/web/Message.css"]["replyChatIconContainer"]; 555 + export const replyIcon: MappedModules["discord/modules/messages/web/Message.css"]["replyIcon"]; 556 + export const clanTagChiplet: MappedModules["discord/modules/messages/web/Message.css"]["clanTagChiplet"]; 557 + export const userJoinSystemMessageIcon: MappedModules["discord/modules/messages/web/Message.css"]["userJoinSystemMessageIcon"]; 558 + export const ticketIcon: MappedModules["discord/modules/messages/web/Message.css"]["ticketIcon"]; 559 + export const commandIcon: MappedModules["discord/modules/messages/web/Message.css"]["commandIcon"]; 560 + export const username: MappedModules["discord/modules/messages/web/Message.css"]["username"]; 561 + export const roleDot: MappedModules["discord/modules/messages/web/Message.css"]["roleDot"]; 562 + export const commandName: MappedModules["discord/modules/messages/web/Message.css"]["commandName"]; 563 + export const appsIcon: MappedModules["discord/modules/messages/web/Message.css"]["appsIcon"]; 564 + export const appLauncherOnboardingCommandName: MappedModules["discord/modules/messages/web/Message.css"]["appLauncherOnboardingCommandName"]; 565 + export const targetUsername: MappedModules["discord/modules/messages/web/Message.css"]["targetUsername"]; 566 + export const executedCommandSeparator: MappedModules["discord/modules/messages/web/Message.css"]["executedCommandSeparator"]; 567 + export const repliedTextPreview: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextPreview"]; 568 + export const threadMessageAccessoryPreview: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryPreview"]; 569 + export const repliedTextContent: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContent"]; 570 + export const clickable: MappedModules["discord/modules/messages/web/Message.css"]["clickable"]; 571 + export const repliedMessageClickableSpineHovered: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageClickableSpineHovered"]; 572 + export const threadMessageAccessoryContent: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContent"]; 573 + export const repliedTextPlaceholder: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextPlaceholder"]; 574 + export const threadMessageAccessoryPlaceholder: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryPlaceholder"]; 575 + export const repliedTextContentTrailingIcon: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContentTrailingIcon"]; 576 + export const threadMessageAccessoryContentTrailingIcon: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContentTrailingIcon"]; 577 + export const repliedTextContentLeadingIcon: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContentLeadingIcon"]; 578 + export const threadMessageAccessoryContentLeadingIcon: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContentLeadingIcon"]; 579 + export const contents: MappedModules["discord/modules/messages/web/Message.css"]["contents"]; 580 + export const zalgo: MappedModules["discord/modules/messages/web/Message.css"]["zalgo"]; 581 + export const messageContent: MappedModules["discord/modules/messages/web/Message.css"]["messageContent"]; 582 + export const header: MappedModules["discord/modules/messages/web/Message.css"]["header"]; 583 + export const buttonContainer: MappedModules["discord/modules/messages/web/Message.css"]["buttonContainer"]; 584 + export const avatar: MappedModules["discord/modules/messages/web/Message.css"]["avatar"]; 585 + export const avatarDecoration: MappedModules["discord/modules/messages/web/Message.css"]["avatarDecoration"]; 586 + export const roleIcon: MappedModules["discord/modules/messages/web/Message.css"]["roleIcon"]; 587 + export const timestamp: MappedModules["discord/modules/messages/web/Message.css"]["timestamp"]; 588 + export const timestampInline: MappedModules["discord/modules/messages/web/Message.css"]["timestampInline"]; 589 + export const alt: MappedModules["discord/modules/messages/web/Message.css"]["alt"]; 590 + export const timestampTooltip: MappedModules["discord/modules/messages/web/Message.css"]["timestampTooltip"]; 591 + export const timestampVisibleOnHover: MappedModules["discord/modules/messages/web/Message.css"]["timestampVisibleOnHover"]; 592 + export const nitroAuthorBadgeTootip: MappedModules["discord/modules/messages/web/Message.css"]["nitroAuthorBadgeTootip"]; 593 + export const headerText: MappedModules["discord/modules/messages/web/Message.css"]["headerText"]; 594 + export const hasRoleIcon: MappedModules["discord/modules/messages/web/Message.css"]["hasRoleIcon"]; 595 + export const hasBadges: MappedModules["discord/modules/messages/web/Message.css"]["hasBadges"]; 596 + export const botTagCompact: MappedModules["discord/modules/messages/web/Message.css"]["botTagCompact"]; 597 + export const botTagCozy: MappedModules["discord/modules/messages/web/Message.css"]["botTagCozy"]; 598 + export const nitroBadgeSvg: MappedModules["discord/modules/messages/web/Message.css"]["nitroBadgeSvg"]; 599 + export const nitroAuthorBadgeContainer: MappedModules["discord/modules/messages/web/Message.css"]["nitroAuthorBadgeContainer"]; 600 + export const separator: MappedModules["discord/modules/messages/web/Message.css"]["separator"]; 601 + export const hasThread: MappedModules["discord/modules/messages/web/Message.css"]["hasThread"]; 602 + export const isSystemMessage: MappedModules["discord/modules/messages/web/Message.css"]["isSystemMessage"]; 603 + export const hasReply: MappedModules["discord/modules/messages/web/Message.css"]["hasReply"]; 604 + export const markupRtl: MappedModules["discord/modules/messages/web/Message.css"]["markupRtl"]; 605 + export const isSending: MappedModules["discord/modules/messages/web/Message.css"]["isSending"]; 606 + export const isFailed: MappedModules["discord/modules/messages/web/Message.css"]["isFailed"]; 607 + export const isUnsupported: MappedModules["discord/modules/messages/web/Message.css"]["isUnsupported"]; 608 + export const edited: MappedModules["discord/modules/messages/web/Message.css"]["edited"]; 609 + export const communicationDisabled: MappedModules["discord/modules/messages/web/Message.css"]["communicationDisabled"]; 610 + export const compactCommunicationDisabled: MappedModules["discord/modules/messages/web/Message.css"]["compactCommunicationDisabled"]; 611 + export const communicationDisabledOpacity: MappedModules["discord/modules/messages/web/Message.css"]["communicationDisabledOpacity"]; 612 + export const badgesContainer: MappedModules["discord/modules/messages/web/Message.css"]["badgesContainer"]; 613 + } 614 + 615 + declare module "@moonlight-mod/wp/discord/modules/modals/Modals" { 616 + import { MappedModules } from "@moonlight-mod/mappings"; 617 + export const closeAllModals: MappedModules["discord/modules/modals/Modals"]["closeAllModals"]; 618 + export const closeAllModalsForContext: MappedModules["discord/modules/modals/Modals"]["closeAllModalsForContext"]; 619 + export const closeModal: MappedModules["discord/modules/modals/Modals"]["closeModal"]; 620 + export const getInteractingModalContext: MappedModules["discord/modules/modals/Modals"]["getInteractingModalContext"]; 621 + export const hasAnyModalOpen: MappedModules["discord/modules/modals/Modals"]["hasAnyModalOpen"]; 622 + export const hasAnyModalOpenSelector: MappedModules["discord/modules/modals/Modals"]["hasAnyModalOpenSelector"]; 623 + export const hasModalOpen: MappedModules["discord/modules/modals/Modals"]["hasModalOpen"]; 624 + export const hasModalOpenSelector: MappedModules["discord/modules/modals/Modals"]["hasModalOpenSelector"]; 625 + export const openModal: MappedModules["discord/modules/modals/Modals"]["openModal"]; 626 + export const openModalLazy: MappedModules["discord/modules/modals/Modals"]["openModalLazy"]; 627 + export const updateModal: MappedModules["discord/modules/modals/Modals"]["updateModal"]; 628 + export const useHasAnyModalOpen: MappedModules["discord/modules/modals/Modals"]["useHasAnyModalOpen"]; 629 + export const useIsModalAtTop: MappedModules["discord/modules/modals/Modals"]["useIsModalAtTop"]; 630 + export const useModalsStore: MappedModules["discord/modules/modals/Modals"]["useModalsStore"]; 631 + } 632 + 633 + declare module "@moonlight-mod/wp/discord/modules/oauth2/index" { 634 + import { MappedModules } from "@moonlight-mod/mappings"; 635 + export const OAuth2AuthorizeModal: MappedModules["discord/modules/oauth2/index"]["OAuth2AuthorizeModal"]; 636 + export const OAuth2AuthorizePage: MappedModules["discord/modules/oauth2/index"]["OAuth2AuthorizePage"]; 637 + export const getOAuth2AuthorizeProps: MappedModules["discord/modules/oauth2/index"]["getOAuth2AuthorizeProps"]; 638 + export const openOAuth2Modal: MappedModules["discord/modules/oauth2/index"]["openOAuth2Modal"]; 639 + export const openOAuth2ModalWithCreateGuildModal: MappedModules["discord/modules/oauth2/index"]["openOAuth2ModalWithCreateGuildModal"]; 640 + export const useOAuth2AuthorizeForm: MappedModules["discord/modules/oauth2/index"]["useOAuth2AuthorizeForm"]; 641 + } 642 + 643 + declare module "@moonlight-mod/wp/discord/modules/people/web/PeoplePage.css" { 644 + import { MappedModules } from "@moonlight-mod/mappings"; 645 + export const addFriend: MappedModules["discord/modules/people/web/PeoplePage.css"]["addFriend"]; 646 + export const badge: MappedModules["discord/modules/people/web/PeoplePage.css"]["badge"]; 647 + export const container: MappedModules["discord/modules/people/web/PeoplePage.css"]["container"]; 648 + export const inviteToolbar: MappedModules["discord/modules/people/web/PeoplePage.css"]["inviteToolbar"]; 649 + export const item: MappedModules["discord/modules/people/web/PeoplePage.css"]["item"]; 650 + export const nowPlayingColumn: MappedModules["discord/modules/people/web/PeoplePage.css"]["nowPlayingColumn"]; 651 + export const peopleColumn: MappedModules["discord/modules/people/web/PeoplePage.css"]["peopleColumn"]; 652 + export const tabBar: MappedModules["discord/modules/people/web/PeoplePage.css"]["tabBar"]; 653 + export const tabBody: MappedModules["discord/modules/people/web/PeoplePage.css"]["tabBody"]; 654 + } 655 + 656 + declare module "@moonlight-mod/wp/discord/modules/user_profile/web/BiteSizeActivity.css" { 657 + import { MappedModules } from "@moonlight-mod/mappings"; 658 + export const header: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["header"]; 659 + export const headerTag: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["headerTag"]; 660 + export const body: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["body"]; 661 + export const footer: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["footer"]; 662 + export const backdrop: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["backdrop"]; 663 + export const toast: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["toast"]; 664 + export const activity: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["activity"]; 665 + export const upsell: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["upsell"]; 666 } 667 668 declare module "@moonlight-mod/wp/discord/packages/flux" { 669 import { MappedModules } from "@moonlight-mod/mappings"; 670 + export const BatchedStoreListener: MappedModules["discord/packages/flux"]["BatchedStoreListener"]; 671 + export const Dispatcher: MappedModules["discord/packages/flux"]["Dispatcher"]; 672 + export const Store: MappedModules["discord/packages/flux"]["Store"]; 673 + const _default: MappedModules["discord/packages/flux"]["default"]; 674 + export default _default; 675 + export const statesWillNeverBeEqual: MappedModules["discord/packages/flux"]["statesWillNeverBeEqual"]; 676 + export const useStateFromStores: MappedModules["discord/packages/flux"]["useStateFromStores"]; 677 + export const useStateFromStoresArray: MappedModules["discord/packages/flux"]["useStateFromStoresArray"]; 678 + export const useStateFromStoresObject: MappedModules["discord/packages/flux"]["useStateFromStoresObject"]; 679 + } 680 + 681 + declare module "@moonlight-mod/wp/discord/packages/flux/BatchedStoreListener" { 682 + import { MappedModules } from "@moonlight-mod/mappings"; 683 + const _default: MappedModules["discord/packages/flux/BatchedStoreListener"]["default"]; 684 + export default _default; 685 + } 686 + 687 + declare module "@moonlight-mod/wp/discord/packages/flux/ChangeListeners" { 688 + import { MappedModules } from "@moonlight-mod/mappings"; 689 + const _default: MappedModules["discord/packages/flux/ChangeListeners"]["default"]; 690 + export default _default; 691 + } 692 + 693 + declare module "@moonlight-mod/wp/discord/packages/flux/Dispatcher" { 694 + import { MappedModules } from "@moonlight-mod/mappings"; 695 + export const Dispatcher: MappedModules["discord/packages/flux/Dispatcher"]["Dispatcher"]; 696 + } 697 + 698 + declare module "@moonlight-mod/wp/discord/packages/flux/Emitter" { 699 + import { MappedModules } from "@moonlight-mod/mappings"; 700 + const _default: MappedModules["discord/packages/flux/Emitter"]["default"]; 701 + export default _default; 702 + } 703 + 704 + declare module "@moonlight-mod/wp/discord/packages/flux/LoggingUtils" { 705 + import { MappedModules } from "@moonlight-mod/mappings"; 706 + const _default: MappedModules["discord/packages/flux/LoggingUtils"]["default"]; 707 + export default _default; 708 + } 709 + 710 + declare module "@moonlight-mod/wp/discord/packages/flux/PersistedStore" { 711 + import { MappedModules } from "@moonlight-mod/mappings"; 712 + export const PersistedStore: MappedModules["discord/packages/flux/PersistedStore"]["PersistedStore"]; 713 + } 714 + 715 + declare module "@moonlight-mod/wp/discord/packages/flux/Store" { 716 + import { MappedModules } from "@moonlight-mod/mappings"; 717 + export const Store: MappedModules["discord/packages/flux/Store"]["Store"]; 718 + } 719 + 720 + declare module "@moonlight-mod/wp/discord/packages/flux/connectStores" { 721 + import { MappedModules } from "@moonlight-mod/mappings"; 722 + const _default: MappedModules["discord/packages/flux/connectStores"]["default"]; 723 + export default _default; 724 + } 725 + 726 + declare module "@moonlight-mod/wp/discord/records/UserRecord" { 727 + import { MappedModules } from "@moonlight-mod/mappings"; 728 + const _default: MappedModules["discord/records/UserRecord"]["default"]; 729 + export default _default; 730 + } 731 + 732 + declare module "@moonlight-mod/wp/discord/styles/shared/Margins.css" { 733 + import { MappedModules } from "@moonlight-mod/mappings"; 734 + export const marginReset: MappedModules["discord/styles/shared/Margins.css"]["marginReset"]; 735 + export const marginTop4: MappedModules["discord/styles/shared/Margins.css"]["marginTop4"]; 736 + export const marginBottom4: MappedModules["discord/styles/shared/Margins.css"]["marginBottom4"]; 737 + export const marginTop8: MappedModules["discord/styles/shared/Margins.css"]["marginTop8"]; 738 + export const marginBottom8: MappedModules["discord/styles/shared/Margins.css"]["marginBottom8"]; 739 + export const marginTop20: MappedModules["discord/styles/shared/Margins.css"]["marginTop20"]; 740 + export const marginBottom20: MappedModules["discord/styles/shared/Margins.css"]["marginBottom20"]; 741 + export const marginTop40: MappedModules["discord/styles/shared/Margins.css"]["marginTop40"]; 742 + export const marginBottom40: MappedModules["discord/styles/shared/Margins.css"]["marginBottom40"]; 743 + export const marginTop60: MappedModules["discord/styles/shared/Margins.css"]["marginTop60"]; 744 + export const marginBottom60: MappedModules["discord/styles/shared/Margins.css"]["marginBottom60"]; 745 + export const marginCenterHorz: MappedModules["discord/styles/shared/Margins.css"]["marginCenterHorz"]; 746 + export const marginLeft8: MappedModules["discord/styles/shared/Margins.css"]["marginLeft8"]; 747 } 748 749 declare module "@moonlight-mod/wp/discord/uikit/Flex" { 750 import { MappedModules } from "@moonlight-mod/mappings"; 751 + const _default: MappedModules["discord/uikit/Flex"]["default"]; 752 + export default _default; 753 + } 754 + 755 + declare module "@moonlight-mod/wp/discord/utils/ClipboardUtils" { 756 + import { MappedModules } from "@moonlight-mod/mappings"; 757 + export const SUPPORTS_COPY: MappedModules["discord/utils/ClipboardUtils"]["SUPPORTS_COPY"]; 758 + export const copy: MappedModules["discord/utils/ClipboardUtils"]["copy"]; 759 + } 760 + 761 + declare module "@moonlight-mod/wp/discord/utils/ComponentDispatchUtils" { 762 + import { MappedModules } from "@moonlight-mod/mappings"; 763 + export const ComponentDispatcher: MappedModules["discord/utils/ComponentDispatchUtils"]["ComponentDispatcher"]; 764 + export const ComponentDispatch: MappedModules["discord/utils/ComponentDispatchUtils"]["ComponentDispatch"]; 765 + } 766 + 767 + declare module "@moonlight-mod/wp/discord/utils/HTTPUtils" { 768 + import { MappedModules } from "@moonlight-mod/mappings"; 769 + export const HTTP: MappedModules["discord/utils/HTTPUtils"]["HTTP"]; 770 + } 771 + 772 + declare module "@moonlight-mod/wp/discord/utils/MaskedLinkUtils" { 773 + import { MappedModules } from "@moonlight-mod/mappings"; 774 + export const isLinkTrusted: MappedModules["discord/utils/MaskedLinkUtils"]["isLinkTrusted"]; 775 + export const handleClick: MappedModules["discord/utils/MaskedLinkUtils"]["handleClick"]; 776 + } 777 + 778 + declare module "@moonlight-mod/wp/discord/utils/NativeUtils" { 779 + import { MappedModules } from "@moonlight-mod/mappings"; 780 + const _default: MappedModules["discord/utils/NativeUtils"]["default"]; 781 + export default _default; 782 + } 783 + 784 + declare module "@moonlight-mod/wp/highlight.js" { 785 + import { MappedModules } from "@moonlight-mod/mappings"; 786 + export const highlight: MappedModules["highlight.js"]["highlight"]; 787 + export const highlightAuto: MappedModules["highlight.js"]["highlightAuto"]; 788 + export const fixMarkup: MappedModules["highlight.js"]["fixMarkup"]; 789 + export const highlightBlock: MappedModules["highlight.js"]["highlightBlock"]; 790 + export const configure: MappedModules["highlight.js"]["configure"]; 791 + export const initHighlighting: MappedModules["highlight.js"]["initHighlighting"]; 792 + export const initHighlightingOnLoad: MappedModules["highlight.js"]["initHighlightingOnLoad"]; 793 + export const registerLanguage: MappedModules["highlight.js"]["registerLanguage"]; 794 + export const listLanguages: MappedModules["highlight.js"]["listLanguages"]; 795 + export const getLanguage: MappedModules["highlight.js"]["getLanguage"]; 796 + export const inherit: MappedModules["highlight.js"]["inherit"]; 797 + export const COMMENT: MappedModules["highlight.js"]["COMMENT"]; 798 + export const IDENT_RE: MappedModules["highlight.js"]["IDENT_RE"]; 799 + export const UNDERSCORE_IDENT_RE: MappedModules["highlight.js"]["UNDERSCORE_IDENT_RE"]; 800 + export const NUMBER_RE: MappedModules["highlight.js"]["NUMBER_RE"]; 801 + export const C_NUMBER_RE: MappedModules["highlight.js"]["C_NUMBER_RE"]; 802 + export const BINARY_NUMBER_RE: MappedModules["highlight.js"]["BINARY_NUMBER_RE"]; 803 + export const RE_STARTERS_RE: MappedModules["highlight.js"]["RE_STARTERS_RE"]; 804 + export const BACKSLASH_ESCAPE: MappedModules["highlight.js"]["BACKSLASH_ESCAPE"]; 805 + export const APOS_STRING_MODE: MappedModules["highlight.js"]["APOS_STRING_MODE"]; 806 + export const QUOTE_STRING_MODE: MappedModules["highlight.js"]["QUOTE_STRING_MODE"]; 807 + export const PHRASAL_WORDS_MODE: MappedModules["highlight.js"]["PHRASAL_WORDS_MODE"]; 808 + export const C_LINE_COMMENT_MODE: MappedModules["highlight.js"]["C_LINE_COMMENT_MODE"]; 809 + export const C_BLOCK_COMMENT_MODE: MappedModules["highlight.js"]["C_BLOCK_COMMENT_MODE"]; 810 + export const HASH_COMMENT_MODE: MappedModules["highlight.js"]["HASH_COMMENT_MODE"]; 811 + export const NUMBER_MODE: MappedModules["highlight.js"]["NUMBER_MODE"]; 812 + export const C_NUMBER_MODE: MappedModules["highlight.js"]["C_NUMBER_MODE"]; 813 + export const BINARY_NUMBER_MODE: MappedModules["highlight.js"]["BINARY_NUMBER_MODE"]; 814 + export const CSS_NUMBER_MODE: MappedModules["highlight.js"]["CSS_NUMBER_MODE"]; 815 + export const REGEX_MODE: MappedModules["highlight.js"]["REGEX_MODE"]; 816 + export const TITLE_MODE: MappedModules["highlight.js"]["TITLE_MODE"]; 817 + export const UNDERSCORE_TITLE_MODE: MappedModules["highlight.js"]["UNDERSCORE_TITLE_MODE"]; 818 + const _default: MappedModules["highlight.js"]["default"]; 819 + export default _default; 820 + export const HighlightJS: MappedModules["highlight.js"]["HighlightJS"]; 821 + } 822 + 823 + declare module "@moonlight-mod/wp/highlight.js/lib/core" { 824 + import { MappedModules } from "@moonlight-mod/mappings"; 825 + export const highlight: MappedModules["highlight.js/lib/core"]["highlight"]; 826 + export const highlightAuto: MappedModules["highlight.js/lib/core"]["highlightAuto"]; 827 + export const fixMarkup: MappedModules["highlight.js/lib/core"]["fixMarkup"]; 828 + export const highlightBlock: MappedModules["highlight.js/lib/core"]["highlightBlock"]; 829 + export const configure: MappedModules["highlight.js/lib/core"]["configure"]; 830 + export const initHighlighting: MappedModules["highlight.js/lib/core"]["initHighlighting"]; 831 + export const initHighlightingOnLoad: MappedModules["highlight.js/lib/core"]["initHighlightingOnLoad"]; 832 + export const registerLanguage: MappedModules["highlight.js/lib/core"]["registerLanguage"]; 833 + export const listLanguages: MappedModules["highlight.js/lib/core"]["listLanguages"]; 834 + export const getLanguage: MappedModules["highlight.js/lib/core"]["getLanguage"]; 835 + export const inherit: MappedModules["highlight.js/lib/core"]["inherit"]; 836 + export const COMMENT: MappedModules["highlight.js/lib/core"]["COMMENT"]; 837 + export const IDENT_RE: MappedModules["highlight.js/lib/core"]["IDENT_RE"]; 838 + export const UNDERSCORE_IDENT_RE: MappedModules["highlight.js/lib/core"]["UNDERSCORE_IDENT_RE"]; 839 + export const NUMBER_RE: MappedModules["highlight.js/lib/core"]["NUMBER_RE"]; 840 + export const C_NUMBER_RE: MappedModules["highlight.js/lib/core"]["C_NUMBER_RE"]; 841 + export const BINARY_NUMBER_RE: MappedModules["highlight.js/lib/core"]["BINARY_NUMBER_RE"]; 842 + export const RE_STARTERS_RE: MappedModules["highlight.js/lib/core"]["RE_STARTERS_RE"]; 843 + export const BACKSLASH_ESCAPE: MappedModules["highlight.js/lib/core"]["BACKSLASH_ESCAPE"]; 844 + export const APOS_STRING_MODE: MappedModules["highlight.js/lib/core"]["APOS_STRING_MODE"]; 845 + export const QUOTE_STRING_MODE: MappedModules["highlight.js/lib/core"]["QUOTE_STRING_MODE"]; 846 + export const PHRASAL_WORDS_MODE: MappedModules["highlight.js/lib/core"]["PHRASAL_WORDS_MODE"]; 847 + export const C_LINE_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["C_LINE_COMMENT_MODE"]; 848 + export const C_BLOCK_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["C_BLOCK_COMMENT_MODE"]; 849 + export const HASH_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["HASH_COMMENT_MODE"]; 850 + export const NUMBER_MODE: MappedModules["highlight.js/lib/core"]["NUMBER_MODE"]; 851 + export const C_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["C_NUMBER_MODE"]; 852 + export const BINARY_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["BINARY_NUMBER_MODE"]; 853 + export const CSS_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["CSS_NUMBER_MODE"]; 854 + export const REGEX_MODE: MappedModules["highlight.js/lib/core"]["REGEX_MODE"]; 855 + export const TITLE_MODE: MappedModules["highlight.js/lib/core"]["TITLE_MODE"]; 856 + export const UNDERSCORE_TITLE_MODE: MappedModules["highlight.js/lib/core"]["UNDERSCORE_TITLE_MODE"]; 857 + } 858 + 859 + declare module "@moonlight-mod/wp/lodash" {} 860 + 861 + declare module "@moonlight-mod/wp/murmurhash" { 862 + import { MappedModules } from "@moonlight-mod/mappings"; 863 + export const v2: MappedModules["murmurhash"]["v2"]; 864 + export const v3: MappedModules["murmurhash"]["v3"]; 865 + } 866 + 867 + declare module "@moonlight-mod/wp/platform.js" { 868 + import { MappedModules } from "@moonlight-mod/mappings"; 869 + export const description: MappedModules["platform.js"]["description"]; 870 + export const layout: MappedModules["platform.js"]["layout"]; 871 + export const manufacturer: MappedModules["platform.js"]["manufacturer"]; 872 + export const name: MappedModules["platform.js"]["name"]; 873 + export const prerelease: MappedModules["platform.js"]["prerelease"]; 874 + export const product: MappedModules["platform.js"]["product"]; 875 + export const ua: MappedModules["platform.js"]["ua"]; 876 + export const version: MappedModules["platform.js"]["version"]; 877 + export const os: MappedModules["platform.js"]["os"]; 878 + export const parse: MappedModules["platform.js"]["parse"]; 879 + export const toString: MappedModules["platform.js"]["toString"]; 880 } 881 882 declare module "@moonlight-mod/wp/react" { 883 import { MappedModules } from "@moonlight-mod/mappings"; 884 + const _: Omit<MappedModules["react"], "__mappings_exportEquals">; 885 export = _; 886 } 887 + 888 + declare module "@moonlight-mod/wp/uuid/v4" {}
+7 -7
packages/types/tsconfig.json
··· 1 { 2 "compilerOptions": { 3 - "target": "es2016", 4 - "module": "es6", 5 - "esModuleInterop": true, 6 - "forceConsistentCasingInFileNames": true, 7 - "strict": true, 8 - "moduleResolution": "bundler", 9 "jsx": "react", 10 - "declaration": true 11 }, 12 "include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"] 13 }
··· 1 { 2 "compilerOptions": { 3 + "target": "ES2016", 4 "jsx": "react", 5 + "module": "ES6", 6 + "moduleResolution": "bundler", 7 + "strict": true, 8 + "declaration": true, 9 + "esModuleInterop": true, 10 + "forceConsistentCasingInFileNames": true 11 }, 12 "include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"] 13 }
+10 -3
packages/web-preload/package.json
··· 2 "name": "@moonlight-mod/web-preload", 3 "private": true, 4 "main": "src/index.ts", 5 "dependencies": { 6 "@moonlight-mod/core": "workspace:*", 7 - "@moonlight-mod/lunast": "^1.0.0", 8 - "@moonlight-mod/mappings": "^1.0.2", 9 - "@moonlight-mod/moonmap": "^1.0.2", 10 "@moonlight-mod/types": "workspace:*" 11 } 12 }
··· 2 "name": "@moonlight-mod/web-preload", 3 "private": true, 4 "main": "src/index.ts", 5 + "engineStrict": true, 6 + "engines": { 7 + "node": ">=22", 8 + "pnpm": ">=10", 9 + "npm": "pnpm", 10 + "yarn": "pnpm" 11 + }, 12 "dependencies": { 13 "@moonlight-mod/core": "workspace:*", 14 + "@moonlight-mod/lunast": "catalog:prod", 15 + "@moonlight-mod/mappings": "catalog:prod", 16 + "@moonlight-mod/moonmap": "catalog:prod", 17 "@moonlight-mod/types": "workspace:*" 18 } 19 }
+16 -19
packages/web-preload/src/index.ts
··· 1 import { loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; 2 - import { 3 - installWebpackPatcher, 4 - onModuleLoad, 5 - registerPatch, 6 - registerWebpackModule 7 - } from "@moonlight-mod/core/patch"; 8 import { constants, MoonlightBranch } from "@moonlight-mod/types"; 9 import { installStyles } from "@moonlight-mod/core/styles"; 10 import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; ··· 12 import Moonmap from "@moonlight-mod/moonmap"; 13 import loadMappings from "@moonlight-mod/mappings"; 14 import { createEventEmitter } from "@moonlight-mod/core/util/event"; 15 - import { EventPayloads, EventType } from "@moonlight-mod/types/core/event"; 16 17 async function load() { 18 initLogger(moonlightNode.config); 19 const logger = new Logger("web-preload"); 20 21 window.moonlight = { 22 - apiLevel: constants.apiLevel, 23 unpatched: new Set(), 24 pendingModules: new Set(), 25 enabledExtensions: new Set(), 26 - events: createEventEmitter<EventType, EventPayloads>(), 27 patchingInternals: { 28 onModuleLoad, 29 registerPatch, 30 registerWebpackModule 31 }, 32 33 version: MOONLIGHT_VERSION, 34 branch: MOONLIGHT_BRANCH as MoonlightBranch, 35 36 getConfig: moonlightNode.getConfig.bind(moonlightNode), 37 getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode), 38 getNatives: moonlightNode.getNatives.bind(moonlightNode), 39 getLogger(id) { 40 return new Logger(id); 41 }, 42 lunast: new LunAST(), 43 moonmap: new Moonmap() 44 }; ··· 51 logger.error("Error setting up web-preload", e); 52 } 53 54 - if (MOONLIGHT_ENV === "web-preload") { 55 - window.addEventListener("DOMContentLoaded", () => { 56 - installStyles(); 57 - }); 58 } else { 59 - installStyles(); 60 } 61 } 62 63 - if (MOONLIGHT_ENV === "web-preload") { 64 - load(); 65 - } else { 66 - window._moonlightBrowserLoad = load; 67 - }
··· 1 import { loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; 2 + import { installWebpackPatcher, onModuleLoad, registerPatch, registerWebpackModule } from "@moonlight-mod/core/patch"; 3 import { constants, MoonlightBranch } from "@moonlight-mod/types"; 4 import { installStyles } from "@moonlight-mod/core/styles"; 5 import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; ··· 7 import Moonmap from "@moonlight-mod/moonmap"; 8 import loadMappings from "@moonlight-mod/mappings"; 9 import { createEventEmitter } from "@moonlight-mod/core/util/event"; 10 + import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event"; 11 12 async function load() { 13 + delete window._moonlightWebLoad; 14 initLogger(moonlightNode.config); 15 const logger = new Logger("web-preload"); 16 17 window.moonlight = { 18 + patched: new Map(), 19 unpatched: new Set(), 20 pendingModules: new Set(), 21 enabledExtensions: new Set(), 22 + 23 + events: createEventEmitter<WebEventType, WebEventPayloads>(), 24 patchingInternals: { 25 onModuleLoad, 26 registerPatch, 27 registerWebpackModule 28 }, 29 + localStorage: window.localStorage, 30 31 version: MOONLIGHT_VERSION, 32 branch: MOONLIGHT_BRANCH as MoonlightBranch, 33 + apiLevel: constants.apiLevel, 34 35 getConfig: moonlightNode.getConfig.bind(moonlightNode), 36 getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode), 37 + setConfigOption: moonlightNode.setConfigOption.bind(moonlightNode), 38 + writeConfig: moonlightNode.writeConfig.bind(moonlightNode), 39 + 40 getNatives: moonlightNode.getNatives.bind(moonlightNode), 41 getLogger(id) { 42 return new Logger(id); 43 }, 44 + 45 lunast: new LunAST(), 46 moonmap: new Moonmap() 47 }; ··· 54 logger.error("Error setting up web-preload", e); 55 } 56 57 + if (document.readyState === "complete") { 58 + installStyles(); 59 } else { 60 + window.addEventListener("load", installStyles); 61 } 62 } 63 64 + window._moonlightWebLoad = load;
+4 -1
packages/web-preload/tsconfig.json
··· 1 { 2 - "extends": "../../tsconfig.json" 3 }
··· 1 { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["ESNext", "DOM"] 5 + } 6 }
+1498 -1196
pnpm-lock.yaml
··· 4 autoInstallPeers: true 5 excludeLinksFromLockfile: false 6 7 importers: 8 9 .: 10 devDependencies: 11 - '@typescript-eslint/eslint-plugin': 12 - specifier: ^6.13.2 13 - version: 6.13.2(@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2))(eslint@8.55.0)(typescript@5.3.2) 14 - '@typescript-eslint/parser': 15 - specifier: ^6.13.2 16 - version: 6.13.2(eslint@8.55.0)(typescript@5.3.2) 17 esbuild: 18 - specifier: ^0.19.3 19 version: 0.19.3 20 esbuild-copy-static-files: 21 - specifier: ^0.1.0 22 version: 0.1.0 23 eslint: 24 - specifier: ^8.55.0 25 - version: 8.55.0 26 - eslint-config-prettier: 27 - specifier: ^9.1.0 28 - version: 9.1.0(eslint@8.55.0) 29 - eslint-plugin-prettier: 30 - specifier: ^5.0.1 31 - version: 5.0.1(eslint-config-prettier@9.1.0(eslint@8.55.0))(eslint@8.55.0)(prettier@3.1.0) 32 - eslint-plugin-react: 33 - specifier: ^7.33.2 34 - version: 7.33.2(eslint@8.55.0) 35 husky: 36 - specifier: ^8.0.3 37 version: 8.0.3 38 prettier: 39 - specifier: ^3.1.0 40 version: 3.1.0 41 typescript: 42 - specifier: ^5.3.2 43 - version: 5.3.2 44 45 packages/browser: 46 dependencies: ··· 54 specifier: workspace:* 55 version: link:../web-preload 56 '@zenfs/core': 57 - specifier: ^1.0.2 58 - version: 1.0.2 59 '@zenfs/dom': 60 - specifier: ^0.2.16 61 - version: 0.2.16(@zenfs/core@1.0.2) 62 63 packages/core: 64 dependencies: ··· 74 '@moonlight-mod/types': 75 specifier: workspace:* 76 version: link:../types 77 nanotar: 78 - specifier: ^0.1.1 79 version: 0.1.1 80 81 packages/injector: ··· 99 packages/types: 100 dependencies: 101 '@moonlight-mod/lunast': 102 - specifier: ^1.0.0 103 - version: 1.0.0 104 '@moonlight-mod/mappings': 105 - specifier: ^1.0.2 106 - version: 1.0.2(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2) 107 '@moonlight-mod/moonmap': 108 - specifier: ^1.0.2 109 - version: 1.0.2 110 '@types/react': 111 specifier: ^18.3.10 112 - version: 18.3.10 113 csstype: 114 - specifier: ^3.1.2 115 - version: 3.1.2 116 standalone-electron-types: 117 specifier: ^1.0.0 118 version: 1.0.0 ··· 123 specifier: workspace:* 124 version: link:../core 125 '@moonlight-mod/lunast': 126 - specifier: ^1.0.0 127 - version: 1.0.0 128 '@moonlight-mod/mappings': 129 - specifier: ^1.0.2 130 - version: 1.0.2(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2) 131 '@moonlight-mod/moonmap': 132 - specifier: ^1.0.2 133 - version: 1.0.2 134 '@moonlight-mod/types': 135 specifier: workspace:* 136 version: link:../types ··· 140 '@aashutoshrathi/word-wrap@1.2.6': 141 resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 142 engines: {node: '>=0.10.0'} 143 144 '@esbuild/android-arm64@0.19.3': 145 resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==} ··· 273 cpu: [x64] 274 os: [win32] 275 276 - '@eslint-community/eslint-utils@4.4.0': 277 - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 278 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 279 peerDependencies: 280 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 281 282 - '@eslint-community/regexpp@4.10.0': 283 - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} 284 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 285 286 - '@eslint/eslintrc@2.1.4': 287 - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} 288 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 289 290 - '@eslint/js@8.55.0': 291 - resolution: {integrity: sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==} 292 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 293 294 - '@humanwhocodes/config-array@0.11.13': 295 - resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} 296 - engines: {node: '>=10.10.0'} 297 - deprecated: Use @eslint/config-array instead 298 299 '@humanwhocodes/module-importer@1.0.1': 300 resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 301 engines: {node: '>=12.22'} 302 303 - '@humanwhocodes/object-schema@2.0.1': 304 - resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} 305 - deprecated: Use @eslint/object-schema instead 306 307 - '@moonlight-mod/lunast@1.0.0': 308 - resolution: {integrity: sha512-kJgf41K12i6/2LbXK97CNO+pNO7ADGh9N4bCQcOPwosocKMcwKHDEZUgPqeihNshY3c3AEW1LiyXjlsl24PdDw==} 309 310 - '@moonlight-mod/mappings@1.0.2': 311 - resolution: {integrity: sha512-PjIv4LFyt3j4LyGiokUmJ6a0L5JljoLXjUkixCynLLpNLd660qTcLe8f9tbhOovvD8joqejq+f5oqSo2V4/Vfg==} 312 peerDependencies: 313 - '@moonlight-mod/lunast': ^1.0.0 314 - '@moonlight-mod/moonmap': ^1.0.0 315 316 - '@moonlight-mod/moonmap@1.0.2': 317 - resolution: {integrity: sha512-dqMFwk8o0duRfvBNYo6EwalEUWWR3bNF5V2N04ogHp4gYON6/5+XOUrTlQ9BFBDj9anZwGgVwGqnxV42Qs9pPw==} 318 319 '@nodelib/fs.scandir@2.1.5': 320 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} ··· 328 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 329 engines: {node: '>= 8'} 330 331 - '@pkgr/utils@2.4.2': 332 - resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} 333 engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 334 335 '@types/estree-jsx@1.0.5': 336 resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} 337 338 '@types/estree@1.0.6': 339 resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 340 341 '@types/fbemitter@2.0.35': 342 resolution: {integrity: sha512-Xem6d7qUfmouCHntCrRYgDBwbf+WWRd6G+7WEFlEZFZ67LZXiYRvT2LV8wcZa6mIaAil95+ABQdKgB6hPIsnng==} 343 344 '@types/flux@3.1.14': 345 resolution: {integrity: sha512-WRXN0kQPCnqxN0/PgNgc7WBF6c8rbSHsEep3/qBLpsQ824RONdOmTs0TV7XhIW2GDNRAHO2CqCgAFLR5PChosw==} 346 347 '@types/json-schema@7.0.15': 348 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 349 350 '@types/node@18.17.17': 351 resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} 352 353 - '@types/node@20.16.10': 354 - resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==} 355 356 '@types/prop-types@15.7.13': 357 resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} 358 359 - '@types/react@18.3.10': 360 - resolution: {integrity: sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==} 361 362 - '@types/readable-stream@4.0.15': 363 - resolution: {integrity: sha512-oAZ3kw+kJFkEqyh7xORZOku1YAKvsFTogRY8kVl4vHpEKiDkfnSA/My8haRE7fvmix5Zyy+1pwzOi7yycGLBJw==} 364 - 365 - '@types/semver@7.5.6': 366 - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} 367 - 368 - '@typescript-eslint/eslint-plugin@6.13.2': 369 - resolution: {integrity: sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==} 370 - engines: {node: ^16.0.0 || >=18.0.0} 371 peerDependencies: 372 - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha 373 - eslint: ^7.0.0 || ^8.0.0 374 - typescript: '*' 375 - peerDependenciesMeta: 376 - typescript: 377 - optional: true 378 379 - '@typescript-eslint/parser@6.13.2': 380 - resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==} 381 - engines: {node: ^16.0.0 || >=18.0.0} 382 peerDependencies: 383 - eslint: ^7.0.0 || ^8.0.0 384 - typescript: '*' 385 - peerDependenciesMeta: 386 - typescript: 387 - optional: true 388 389 - '@typescript-eslint/scope-manager@6.13.2': 390 - resolution: {integrity: sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==} 391 - engines: {node: ^16.0.0 || >=18.0.0} 392 393 - '@typescript-eslint/type-utils@6.13.2': 394 - resolution: {integrity: sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==} 395 - engines: {node: ^16.0.0 || >=18.0.0} 396 peerDependencies: 397 - eslint: ^7.0.0 || ^8.0.0 398 - typescript: '*' 399 - peerDependenciesMeta: 400 - typescript: 401 - optional: true 402 403 - '@typescript-eslint/types@6.13.2': 404 - resolution: {integrity: sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==} 405 - engines: {node: ^16.0.0 || >=18.0.0} 406 407 - '@typescript-eslint/typescript-estree@6.13.2': 408 - resolution: {integrity: sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==} 409 - engines: {node: ^16.0.0 || >=18.0.0} 410 peerDependencies: 411 - typescript: '*' 412 - peerDependenciesMeta: 413 - typescript: 414 - optional: true 415 416 - '@typescript-eslint/utils@6.13.2': 417 - resolution: {integrity: sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==} 418 - engines: {node: ^16.0.0 || >=18.0.0} 419 peerDependencies: 420 - eslint: ^7.0.0 || ^8.0.0 421 422 - '@typescript-eslint/visitor-keys@6.13.2': 423 - resolution: {integrity: sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==} 424 - engines: {node: ^16.0.0 || >=18.0.0} 425 426 - '@ungap/structured-clone@1.2.0': 427 - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} 428 429 - '@zenfs/core@1.0.2': 430 - resolution: {integrity: sha512-LMTD4ntn6Ag1y+IeOSVykDDvYC12dsGFtsX8M/54OQrLs7v+YnX4bpo0o2osbm8XFmU2MTNMX/G3PLsvzgWzrg==} 431 - engines: {node: '>= 16'} 432 hasBin: true 433 434 - '@zenfs/dom@0.2.16': 435 - resolution: {integrity: sha512-6Ev+ol9hZIgQECNZR+xxjQ/a99EhhrWeiQttm/+U7YJK3HdTjiKfU39DsfGeH64vSqhpa5Vj+LWRx75SHkjw0Q==} 436 engines: {node: '>= 18'} 437 peerDependencies: 438 - '@zenfs/core': ^1.0.0 439 440 abort-controller@3.0.0: 441 resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} ··· 446 peerDependencies: 447 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 448 449 - acorn@8.12.1: 450 - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} 451 engines: {node: '>=0.4.0'} 452 hasBin: true 453 454 ajv@6.12.6: 455 resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 456 457 - ansi-regex@5.0.1: 458 - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 459 - engines: {node: '>=8'} 460 - 461 ansi-styles@4.3.0: 462 resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 463 engines: {node: '>=8'} 464 465 argparse@2.0.1: 466 resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 467 468 - array-buffer-byte-length@1.0.0: 469 - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} 470 471 - array-includes@3.1.7: 472 - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} 473 engines: {node: '>= 0.4'} 474 475 - array-union@2.1.0: 476 - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 477 - engines: {node: '>=8'} 478 479 - array.prototype.flat@1.3.2: 480 - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} 481 engines: {node: '>= 0.4'} 482 483 - array.prototype.flatmap@1.3.2: 484 - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} 485 engines: {node: '>= 0.4'} 486 487 - array.prototype.tosorted@1.1.2: 488 - resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} 489 490 - arraybuffer.prototype.slice@1.0.2: 491 - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} 492 engines: {node: '>= 0.4'} 493 494 astring@1.9.0: 495 resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} 496 hasBin: true 497 498 - asynciterator.prototype@1.0.0: 499 - resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} 500 501 - available-typed-arrays@1.0.5: 502 - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} 503 engines: {node: '>= 0.4'} 504 505 balanced-match@1.0.2: ··· 508 base64-js@1.5.1: 509 resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 510 511 - big-integer@1.6.52: 512 - resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} 513 - engines: {node: '>=0.6'} 514 - 515 - bplist-parser@0.2.0: 516 - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} 517 - engines: {node: '>= 5.10.0'} 518 - 519 brace-expansion@1.1.11: 520 resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 521 522 brace-expansion@2.0.1: 523 resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 524 525 - braces@3.0.2: 526 - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 527 engines: {node: '>=8'} 528 529 buffer@6.0.3: 530 resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 531 532 - bundle-name@3.0.0: 533 - resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} 534 - engines: {node: '>=12'} 535 536 - call-bind@1.0.5: 537 - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} 538 539 callsites@3.1.0: 540 resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} ··· 554 concat-map@0.0.1: 555 resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 556 557 - cross-spawn@7.0.3: 558 - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 559 engines: {node: '>= 8'} 560 561 - csstype@3.1.2: 562 - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} 563 - 564 csstype@3.1.3: 565 resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 566 567 - debug@4.3.4: 568 - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 569 engines: {node: '>=6.0'} 570 peerDependencies: 571 supports-color: '*' ··· 576 deep-is@0.1.4: 577 resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 578 579 - default-browser-id@3.0.0: 580 - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} 581 - engines: {node: '>=12'} 582 - 583 - default-browser@4.0.0: 584 - resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} 585 - engines: {node: '>=14.16'} 586 - 587 - define-data-property@1.1.1: 588 - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} 589 engines: {node: '>= 0.4'} 590 591 - define-lazy-prop@3.0.0: 592 - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} 593 - engines: {node: '>=12'} 594 - 595 define-properties@1.2.1: 596 resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} 597 engines: {node: '>= 0.4'} 598 599 - dir-glob@3.0.1: 600 - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 601 - engines: {node: '>=8'} 602 603 doctrine@2.1.0: 604 resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} 605 engines: {node: '>=0.10.0'} 606 607 - doctrine@3.0.0: 608 - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 609 - engines: {node: '>=6.0.0'} 610 611 - es-abstract@1.22.3: 612 - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} 613 engines: {node: '>= 0.4'} 614 615 - es-iterator-helpers@1.0.15: 616 - resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} 617 618 - es-set-tostringtag@2.0.2: 619 - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} 620 engines: {node: '>= 0.4'} 621 622 - es-shim-unscopables@1.0.2: 623 - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} 624 625 - es-to-primitive@1.2.1: 626 - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} 627 engines: {node: '>= 0.4'} 628 629 esbuild-copy-static-files@0.1.0: ··· 644 peerDependencies: 645 eslint: '>=7.0.0' 646 647 - eslint-plugin-prettier@5.0.1: 648 - resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} 649 engines: {node: ^14.18.0 || >=16.0.0} 650 peerDependencies: 651 '@types/eslint': '>=8.0.0' 652 eslint: '>=8.0.0' 653 - eslint-config-prettier: '*' 654 prettier: '>=3.0.0' 655 peerDependenciesMeta: 656 '@types/eslint': ··· 658 eslint-config-prettier: 659 optional: true 660 661 - eslint-plugin-react@7.33.2: 662 - resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} 663 engines: {node: '>=4'} 664 peerDependencies: 665 - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 666 667 - eslint-scope@7.2.2: 668 - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 669 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 670 671 eslint-visitor-keys@3.4.3: 672 resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 673 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 674 675 - eslint@8.55.0: 676 - resolution: {integrity: sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==} 677 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 678 - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. 679 hasBin: true 680 681 - espree@9.6.1: 682 - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 683 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 684 685 - esquery@1.5.0: 686 - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 687 engines: {node: '>=0.10'} 688 689 esrecurse@4.3.0: ··· 712 resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 713 engines: {node: '>=0.8.x'} 714 715 - execa@5.1.1: 716 - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} 717 - engines: {node: '>=10'} 718 - 719 - execa@7.2.0: 720 - resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} 721 - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} 722 - 723 fast-deep-equal@3.1.3: 724 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 725 ··· 736 fast-levenshtein@2.0.6: 737 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 738 739 - fastq@1.15.0: 740 - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 741 742 - file-entry-cache@6.0.1: 743 - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 744 - engines: {node: ^10.12.0 || >=12.0.0} 745 746 - fill-range@7.0.1: 747 - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 748 engines: {node: '>=8'} 749 750 find-up@5.0.0: 751 resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 752 engines: {node: '>=10'} 753 754 - flat-cache@3.2.0: 755 - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} 756 - engines: {node: ^10.12.0 || >=12.0.0} 757 758 flatted@3.2.9: 759 resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} 760 761 - for-each@0.3.3: 762 - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} 763 - 764 - fs.realpath@1.0.0: 765 - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 766 767 function-bind@1.1.2: 768 resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 769 770 - function.prototype.name@1.1.6: 771 - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} 772 engines: {node: '>= 0.4'} 773 774 functions-have-names@1.2.3: 775 resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} 776 777 - get-intrinsic@1.2.2: 778 - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} 779 780 - get-stream@6.0.1: 781 - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} 782 - engines: {node: '>=10'} 783 784 - get-symbol-description@1.0.0: 785 - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} 786 engines: {node: '>= 0.4'} 787 788 glob-parent@5.1.2: ··· 793 resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 794 engines: {node: '>=10.13.0'} 795 796 - glob@7.2.3: 797 - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 798 - deprecated: Glob versions prior to v9 are no longer supported 799 800 - globals@13.23.0: 801 - resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} 802 - engines: {node: '>=8'} 803 - 804 - globalthis@1.0.3: 805 - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} 806 engines: {node: '>= 0.4'} 807 808 - globby@11.1.0: 809 - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 810 - engines: {node: '>=10'} 811 - 812 - gopd@1.0.1: 813 - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} 814 815 graphemer@1.4.0: 816 resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 817 818 - has-bigints@1.0.2: 819 - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} 820 821 has-flag@4.0.0: 822 resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 823 engines: {node: '>=8'} 824 825 - has-property-descriptors@1.0.1: 826 - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} 827 828 - has-proto@1.0.1: 829 - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} 830 engines: {node: '>= 0.4'} 831 832 - has-symbols@1.0.3: 833 - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 834 engines: {node: '>= 0.4'} 835 836 - has-tostringtag@1.0.0: 837 - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} 838 engines: {node: '>= 0.4'} 839 840 - hasown@2.0.0: 841 - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} 842 engines: {node: '>= 0.4'} 843 844 - human-signals@2.1.0: 845 - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} 846 - engines: {node: '>=10.17.0'} 847 - 848 - human-signals@4.3.1: 849 - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} 850 - engines: {node: '>=14.18.0'} 851 - 852 husky@8.0.3: 853 resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} 854 engines: {node: '>=14'} ··· 857 ieee754@1.2.1: 858 resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 859 860 - ignore@5.3.0: 861 - resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} 862 engines: {node: '>= 4'} 863 864 import-fresh@3.3.0: ··· 869 resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 870 engines: {node: '>=0.8.19'} 871 872 - inflight@1.0.6: 873 - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 874 - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. 875 - 876 - inherits@2.0.4: 877 - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 878 - 879 - internal-slot@1.0.6: 880 - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} 881 engines: {node: '>= 0.4'} 882 883 - is-array-buffer@3.0.2: 884 - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} 885 886 - is-async-function@2.0.0: 887 - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} 888 engines: {node: '>= 0.4'} 889 890 - is-bigint@1.0.4: 891 - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} 892 893 - is-boolean-object@1.1.2: 894 - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} 895 engines: {node: '>= 0.4'} 896 897 is-callable@1.2.7: 898 resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 899 engines: {node: '>= 0.4'} 900 901 - is-core-module@2.13.1: 902 - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} 903 904 - is-date-object@1.0.5: 905 - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} 906 engines: {node: '>= 0.4'} 907 908 - is-docker@2.2.1: 909 - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} 910 - engines: {node: '>=8'} 911 - hasBin: true 912 - 913 - is-docker@3.0.0: 914 - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} 915 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 916 - hasBin: true 917 918 is-extglob@2.1.1: 919 resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 920 engines: {node: '>=0.10.0'} 921 922 - is-finalizationregistry@1.0.2: 923 - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} 924 925 - is-generator-function@1.0.10: 926 - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} 927 engines: {node: '>= 0.4'} 928 929 is-glob@4.0.3: 930 resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 931 engines: {node: '>=0.10.0'} 932 933 - is-inside-container@1.0.0: 934 - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} 935 - engines: {node: '>=14.16'} 936 - hasBin: true 937 - 938 - is-map@2.0.2: 939 - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} 940 - 941 - is-negative-zero@2.0.2: 942 - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} 943 engines: {node: '>= 0.4'} 944 945 - is-number-object@1.0.7: 946 - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} 947 engines: {node: '>= 0.4'} 948 949 is-number@7.0.0: 950 resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 951 engines: {node: '>=0.12.0'} 952 953 - is-path-inside@3.0.3: 954 - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 955 - engines: {node: '>=8'} 956 957 - is-regex@1.1.4: 958 - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} 959 engines: {node: '>= 0.4'} 960 961 - is-set@2.0.2: 962 - resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} 963 964 - is-shared-array-buffer@1.0.2: 965 - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} 966 967 - is-stream@2.0.1: 968 - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 969 - engines: {node: '>=8'} 970 971 - is-stream@3.0.0: 972 - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} 973 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 974 975 - is-string@1.0.7: 976 - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} 977 engines: {node: '>= 0.4'} 978 979 - is-symbol@1.0.4: 980 - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} 981 engines: {node: '>= 0.4'} 982 983 - is-typed-array@1.1.12: 984 - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} 985 engines: {node: '>= 0.4'} 986 987 - is-weakmap@2.0.1: 988 - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} 989 - 990 - is-weakref@1.0.2: 991 - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} 992 - 993 - is-weakset@2.0.2: 994 - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} 995 - 996 - is-wsl@2.2.0: 997 - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} 998 - engines: {node: '>=8'} 999 - 1000 isarray@2.0.5: 1001 resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} 1002 1003 isexe@2.0.0: 1004 resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1005 1006 - iterator.prototype@1.1.2: 1007 - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} 1008 1009 js-tokens@4.0.0: 1010 resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} ··· 1044 resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 1045 hasBin: true 1046 1047 - lru-cache@6.0.0: 1048 - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 1049 - engines: {node: '>=10'} 1050 - 1051 - merge-stream@2.0.0: 1052 - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 1053 1054 merge2@1.4.1: 1055 resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} ··· 1059 resolution: {integrity: sha512-OyvYIOgpzXREySYJ1cqEb2pOKdeQMTfF9M8dRU6nC4hi/GXMmNpe9ssZCrSoTHazu05BSAoRBN/uYeco+ymfOg==} 1060 engines: {node: '>=18.0.0'} 1061 1062 - micromatch@4.0.5: 1063 - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 1064 - engines: {node: '>=8.6'} 1065 1066 - mimic-fn@2.1.0: 1067 - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 1068 - engines: {node: '>=6'} 1069 1070 - mimic-fn@4.0.0: 1071 - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} 1072 - engines: {node: '>=12'} 1073 1074 minimatch@3.1.2: 1075 resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} ··· 1078 resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1079 engines: {node: '>=16 || 14 >=14.17'} 1080 1081 - ms@2.1.2: 1082 - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1083 1084 nanotar@0.1.1: 1085 resolution: {integrity: sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ==} ··· 1087 natural-compare@1.4.0: 1088 resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1089 1090 - npm-run-path@4.0.1: 1091 - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 1092 - engines: {node: '>=8'} 1093 - 1094 - npm-run-path@5.1.0: 1095 - resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} 1096 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1097 1098 object-assign@4.1.1: 1099 resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 1100 engines: {node: '>=0.10.0'} 1101 1102 - object-inspect@1.13.1: 1103 - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} 1104 1105 object-keys@1.1.1: 1106 resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 1107 engines: {node: '>= 0.4'} 1108 1109 - object.assign@4.1.5: 1110 - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} 1111 engines: {node: '>= 0.4'} 1112 1113 - object.entries@1.1.7: 1114 - resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} 1115 engines: {node: '>= 0.4'} 1116 1117 - object.fromentries@2.0.7: 1118 - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} 1119 engines: {node: '>= 0.4'} 1120 1121 - object.hasown@1.1.3: 1122 - resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} 1123 - 1124 - object.values@1.1.7: 1125 - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} 1126 engines: {node: '>= 0.4'} 1127 1128 - once@1.4.0: 1129 - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1130 1131 - onetime@5.1.2: 1132 - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 1133 - engines: {node: '>=6'} 1134 - 1135 - onetime@6.0.0: 1136 - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} 1137 - engines: {node: '>=12'} 1138 - 1139 - open@9.1.0: 1140 - resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} 1141 - engines: {node: '>=14.16'} 1142 1143 optionator@0.9.3: 1144 resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1145 engines: {node: '>= 0.8.0'} 1146 1147 p-limit@3.1.0: 1148 resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1149 engines: {node: '>=10'} ··· 1152 resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1153 engines: {node: '>=10'} 1154 1155 parent-module@1.0.1: 1156 resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1157 engines: {node: '>=6'} ··· 1159 path-exists@4.0.0: 1160 resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1161 engines: {node: '>=8'} 1162 - 1163 - path-is-absolute@1.0.1: 1164 - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1165 - engines: {node: '>=0.10.0'} 1166 1167 path-key@3.1.1: 1168 resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1169 engines: {node: '>=8'} 1170 1171 - path-key@4.0.0: 1172 - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} 1173 - engines: {node: '>=12'} 1174 - 1175 path-parse@1.0.7: 1176 resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1177 1178 - path-type@4.0.0: 1179 - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1180 - engines: {node: '>=8'} 1181 - 1182 - picocolors@1.0.0: 1183 - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 1184 1185 picomatch@2.3.1: 1186 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1187 engines: {node: '>=8.6'} 1188 1189 prelude-ls@1.2.1: 1190 resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} ··· 1209 punycode@2.3.1: 1210 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1211 engines: {node: '>=6'} 1212 1213 queue-microtask@1.2.3: 1214 resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} ··· 1220 resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} 1221 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1222 1223 - reflect.getprototypeof@1.0.4: 1224 - resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} 1225 engines: {node: '>= 0.4'} 1226 1227 - regexp.prototype.flags@1.5.1: 1228 - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} 1229 engines: {node: '>= 0.4'} 1230 1231 resolve-from@4.0.0: ··· 1236 resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} 1237 hasBin: true 1238 1239 reusify@1.0.4: 1240 resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1241 engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1242 - 1243 - rimraf@3.0.2: 1244 - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1245 - deprecated: Rimraf versions prior to v4 are no longer supported 1246 - hasBin: true 1247 - 1248 - run-applescript@5.0.0: 1249 - resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} 1250 - engines: {node: '>=12'} 1251 1252 run-parallel@1.2.0: 1253 resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1254 1255 - safe-array-concat@1.0.1: 1256 - resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} 1257 engines: {node: '>=0.4'} 1258 1259 - safe-buffer@5.1.2: 1260 - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 1261 - 1262 safe-buffer@5.2.1: 1263 resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1264 1265 - safe-regex-test@1.0.0: 1266 - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} 1267 1268 semver@6.3.1: 1269 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1270 hasBin: true 1271 1272 - semver@7.5.4: 1273 - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 1274 engines: {node: '>=10'} 1275 hasBin: true 1276 1277 - set-function-length@1.1.1: 1278 - resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} 1279 engines: {node: '>= 0.4'} 1280 1281 - set-function-name@2.0.1: 1282 - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} 1283 engines: {node: '>= 0.4'} 1284 1285 shebang-command@2.0.0: ··· 1290 resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1291 engines: {node: '>=8'} 1292 1293 - side-channel@1.0.4: 1294 - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} 1295 1296 - signal-exit@3.0.7: 1297 - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 1298 1299 - slash@3.0.0: 1300 - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1301 - engines: {node: '>=8'} 1302 1303 standalone-electron-types@1.0.0: 1304 resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} 1305 1306 - string.prototype.matchall@4.0.10: 1307 - resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} 1308 1309 - string.prototype.trim@1.2.8: 1310 - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} 1311 engines: {node: '>= 0.4'} 1312 1313 - string.prototype.trimend@1.0.7: 1314 - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} 1315 1316 - string.prototype.trimstart@1.0.7: 1317 - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} 1318 1319 string_decoder@1.3.0: 1320 resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1321 1322 - strip-ansi@6.0.1: 1323 - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1324 - engines: {node: '>=8'} 1325 - 1326 - strip-final-newline@2.0.0: 1327 - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 1328 - engines: {node: '>=6'} 1329 - 1330 - strip-final-newline@3.0.0: 1331 - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} 1332 - engines: {node: '>=12'} 1333 - 1334 strip-json-comments@3.1.1: 1335 resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1336 engines: {node: '>=8'} ··· 1343 resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1344 engines: {node: '>= 0.4'} 1345 1346 - synckit@0.8.6: 1347 - resolution: {integrity: sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==} 1348 engines: {node: ^14.18.0 || >=16.0.0} 1349 1350 - text-table@0.2.0: 1351 - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 1352 1353 - titleize@3.0.0: 1354 - resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} 1355 - engines: {node: '>=12'} 1356 1357 to-regex-range@5.0.1: 1358 resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1359 engines: {node: '>=8.0'} 1360 1361 - ts-api-utils@1.0.3: 1362 - resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} 1363 - engines: {node: '>=16.13.0'} 1364 peerDependencies: 1365 - typescript: '>=4.2.0' 1366 1367 - tslib@2.6.2: 1368 - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} 1369 1370 type-check@0.4.0: 1371 resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1372 engines: {node: '>= 0.8.0'} 1373 1374 - type-fest@0.20.2: 1375 - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1376 - engines: {node: '>=10'} 1377 1378 - typed-array-buffer@1.0.0: 1379 - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} 1380 engines: {node: '>= 0.4'} 1381 1382 - typed-array-byte-length@1.0.0: 1383 - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} 1384 engines: {node: '>= 0.4'} 1385 1386 - typed-array-byte-offset@1.0.0: 1387 - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} 1388 engines: {node: '>= 0.4'} 1389 1390 - typed-array-length@1.0.4: 1391 - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} 1392 1393 - typescript@5.3.2: 1394 - resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==} 1395 engines: {node: '>=14.17'} 1396 hasBin: true 1397 1398 - unbox-primitive@1.0.2: 1399 - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} 1400 1401 - undici-types@6.19.8: 1402 - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} 1403 1404 - untildify@4.0.0: 1405 - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} 1406 - engines: {node: '>=8'} 1407 1408 uri-js@4.4.1: 1409 resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1410 1411 - utilium@0.7.1: 1412 - resolution: {integrity: sha512-2ocvTkI7U8LERmwxL0LhFUvEfN66UqcjF6tMiURvUwSyU7U1QC9gST+3iSUSiGccFfnP3f2EXwHNXOnOzx+lAg==} 1413 1414 - which-boxed-primitive@1.0.2: 1415 - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} 1416 1417 - which-builtin-type@1.1.3: 1418 - resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} 1419 engines: {node: '>= 0.4'} 1420 1421 - which-collection@1.0.1: 1422 - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} 1423 1424 - which-typed-array@1.1.13: 1425 - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} 1426 engines: {node: '>= 0.4'} 1427 1428 which@2.0.2: ··· 1430 engines: {node: '>= 8'} 1431 hasBin: true 1432 1433 - wrappy@1.0.2: 1434 - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1435 - 1436 - yallist@4.0.0: 1437 - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1438 1439 yocto-queue@0.1.0: 1440 resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1441 engines: {node: '>=10'} 1442 1443 snapshots: 1444 1445 '@aashutoshrathi/word-wrap@1.2.6': {} 1446 1447 '@esbuild/android-arm64@0.19.3': 1448 optional: true ··· 1510 '@esbuild/win32-x64@0.19.3': 1511 optional: true 1512 1513 - '@eslint-community/eslint-utils@4.4.0(eslint@8.55.0)': 1514 dependencies: 1515 - eslint: 8.55.0 1516 eslint-visitor-keys: 3.4.3 1517 1518 - '@eslint-community/regexpp@4.10.0': {} 1519 1520 - '@eslint/eslintrc@2.1.4': 1521 dependencies: 1522 ajv: 6.12.6 1523 - debug: 4.3.4 1524 - espree: 9.6.1 1525 - globals: 13.23.0 1526 - ignore: 5.3.0 1527 import-fresh: 3.3.0 1528 js-yaml: 4.1.0 1529 minimatch: 3.1.2 ··· 1531 transitivePeerDependencies: 1532 - supports-color 1533 1534 - '@eslint/js@8.55.0': {} 1535 1536 - '@humanwhocodes/config-array@0.11.13': 1537 dependencies: 1538 - '@humanwhocodes/object-schema': 2.0.1 1539 - debug: 4.3.4 1540 - minimatch: 3.1.2 1541 - transitivePeerDependencies: 1542 - - supports-color 1543 1544 '@humanwhocodes/module-importer@1.0.1': {} 1545 1546 - '@humanwhocodes/object-schema@2.0.1': {} 1547 1548 - '@moonlight-mod/lunast@1.0.0': 1549 dependencies: 1550 astring: 1.9.0 1551 estree-toolkit: 1.7.8 1552 meriyah: 6.0.1 1553 1554 - '@moonlight-mod/mappings@1.0.2(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2)': 1555 dependencies: 1556 - '@moonlight-mod/lunast': 1.0.0 1557 - '@moonlight-mod/moonmap': 1.0.2 1558 '@types/flux': 3.1.14 1559 - '@types/react': 18.3.10 1560 csstype: 3.1.3 1561 1562 - '@moonlight-mod/moonmap@1.0.2': {} 1563 1564 '@nodelib/fs.scandir@2.1.5': 1565 dependencies: ··· 1571 '@nodelib/fs.walk@1.2.8': 1572 dependencies: 1573 '@nodelib/fs.scandir': 2.1.5 1574 - fastq: 1.15.0 1575 1576 - '@pkgr/utils@2.4.2': 1577 dependencies: 1578 - cross-spawn: 7.0.3 1579 - fast-glob: 3.3.2 1580 - is-glob: 4.0.3 1581 - open: 9.1.0 1582 - picocolors: 1.0.0 1583 - tslib: 2.6.2 1584 1585 '@types/estree-jsx@1.0.5': 1586 dependencies: ··· 1588 1589 '@types/estree@1.0.6': {} 1590 1591 '@types/fbemitter@2.0.35': {} 1592 1593 '@types/flux@3.1.14': 1594 dependencies: 1595 '@types/fbemitter': 2.0.35 1596 - '@types/react': 18.3.10 1597 1598 '@types/json-schema@7.0.15': {} 1599 1600 '@types/node@18.17.17': {} 1601 1602 - '@types/node@20.16.10': 1603 dependencies: 1604 - undici-types: 6.19.8 1605 1606 '@types/prop-types@15.7.13': {} 1607 1608 - '@types/react@18.3.10': 1609 dependencies: 1610 '@types/prop-types': 15.7.13 1611 csstype: 3.1.3 1612 1613 - '@types/readable-stream@4.0.15': 1614 dependencies: 1615 - '@types/node': 20.16.10 1616 - safe-buffer: 5.1.2 1617 - 1618 - '@types/semver@7.5.6': {} 1619 - 1620 - '@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2))(eslint@8.55.0)(typescript@5.3.2)': 1621 - dependencies: 1622 - '@eslint-community/regexpp': 4.10.0 1623 - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1624 - '@typescript-eslint/scope-manager': 6.13.2 1625 - '@typescript-eslint/type-utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1626 - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1627 - '@typescript-eslint/visitor-keys': 6.13.2 1628 - debug: 4.3.4 1629 - eslint: 8.55.0 1630 graphemer: 1.4.0 1631 - ignore: 5.3.0 1632 natural-compare: 1.4.0 1633 - semver: 7.5.4 1634 - ts-api-utils: 1.0.3(typescript@5.3.2) 1635 - optionalDependencies: 1636 - typescript: 5.3.2 1637 transitivePeerDependencies: 1638 - supports-color 1639 1640 - '@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2)': 1641 dependencies: 1642 - '@typescript-eslint/scope-manager': 6.13.2 1643 - '@typescript-eslint/types': 6.13.2 1644 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 1645 - '@typescript-eslint/visitor-keys': 6.13.2 1646 - debug: 4.3.4 1647 - eslint: 8.55.0 1648 - optionalDependencies: 1649 - typescript: 5.3.2 1650 transitivePeerDependencies: 1651 - supports-color 1652 1653 - '@typescript-eslint/scope-manager@6.13.2': 1654 dependencies: 1655 - '@typescript-eslint/types': 6.13.2 1656 - '@typescript-eslint/visitor-keys': 6.13.2 1657 1658 - '@typescript-eslint/type-utils@6.13.2(eslint@8.55.0)(typescript@5.3.2)': 1659 dependencies: 1660 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 1661 - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1662 - debug: 4.3.4 1663 - eslint: 8.55.0 1664 - ts-api-utils: 1.0.3(typescript@5.3.2) 1665 - optionalDependencies: 1666 - typescript: 5.3.2 1667 transitivePeerDependencies: 1668 - supports-color 1669 1670 - '@typescript-eslint/types@6.13.2': {} 1671 1672 - '@typescript-eslint/typescript-estree@6.13.2(typescript@5.3.2)': 1673 dependencies: 1674 - '@typescript-eslint/types': 6.13.2 1675 - '@typescript-eslint/visitor-keys': 6.13.2 1676 - debug: 4.3.4 1677 - globby: 11.1.0 1678 is-glob: 4.0.3 1679 - semver: 7.5.4 1680 - ts-api-utils: 1.0.3(typescript@5.3.2) 1681 - optionalDependencies: 1682 - typescript: 5.3.2 1683 transitivePeerDependencies: 1684 - supports-color 1685 1686 - '@typescript-eslint/utils@6.13.2(eslint@8.55.0)(typescript@5.3.2)': 1687 dependencies: 1688 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) 1689 - '@types/json-schema': 7.0.15 1690 - '@types/semver': 7.5.6 1691 - '@typescript-eslint/scope-manager': 6.13.2 1692 - '@typescript-eslint/types': 6.13.2 1693 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 1694 - eslint: 8.55.0 1695 - semver: 7.5.4 1696 transitivePeerDependencies: 1697 - supports-color 1698 - - typescript 1699 1700 - '@typescript-eslint/visitor-keys@6.13.2': 1701 dependencies: 1702 - '@typescript-eslint/types': 6.13.2 1703 - eslint-visitor-keys: 3.4.3 1704 1705 - '@ungap/structured-clone@1.2.0': {} 1706 1707 - '@zenfs/core@1.0.2': 1708 dependencies: 1709 - '@types/node': 20.16.10 1710 - '@types/readable-stream': 4.0.15 1711 buffer: 6.0.3 1712 eventemitter3: 5.0.1 1713 - minimatch: 9.0.5 1714 readable-stream: 4.5.2 1715 - utilium: 0.7.1 1716 1717 - '@zenfs/dom@0.2.16(@zenfs/core@1.0.2)': 1718 dependencies: 1719 - '@zenfs/core': 1.0.2 1720 1721 abort-controller@3.0.0: 1722 dependencies: 1723 event-target-shim: 5.0.1 1724 1725 - acorn-jsx@5.3.2(acorn@8.12.1): 1726 dependencies: 1727 - acorn: 8.12.1 1728 1729 - acorn@8.12.1: {} 1730 1731 ajv@6.12.6: 1732 dependencies: ··· 1734 fast-json-stable-stringify: 2.1.0 1735 json-schema-traverse: 0.4.1 1736 uri-js: 4.4.1 1737 - 1738 - ansi-regex@5.0.1: {} 1739 1740 ansi-styles@4.3.0: 1741 dependencies: 1742 color-convert: 2.0.1 1743 1744 argparse@2.0.1: {} 1745 1746 - array-buffer-byte-length@1.0.0: 1747 dependencies: 1748 - call-bind: 1.0.5 1749 - is-array-buffer: 3.0.2 1750 1751 - array-includes@3.1.7: 1752 dependencies: 1753 - call-bind: 1.0.5 1754 define-properties: 1.2.1 1755 - es-abstract: 1.22.3 1756 - get-intrinsic: 1.2.2 1757 - is-string: 1.0.7 1758 1759 - array-union@2.1.0: {} 1760 1761 - array.prototype.flat@1.3.2: 1762 dependencies: 1763 - call-bind: 1.0.5 1764 define-properties: 1.2.1 1765 - es-abstract: 1.22.3 1766 - es-shim-unscopables: 1.0.2 1767 1768 - array.prototype.flatmap@1.3.2: 1769 dependencies: 1770 - call-bind: 1.0.5 1771 define-properties: 1.2.1 1772 - es-abstract: 1.22.3 1773 - es-shim-unscopables: 1.0.2 1774 1775 - array.prototype.tosorted@1.1.2: 1776 dependencies: 1777 - call-bind: 1.0.5 1778 define-properties: 1.2.1 1779 - es-abstract: 1.22.3 1780 - es-shim-unscopables: 1.0.2 1781 - get-intrinsic: 1.2.2 1782 1783 - arraybuffer.prototype.slice@1.0.2: 1784 dependencies: 1785 - array-buffer-byte-length: 1.0.0 1786 - call-bind: 1.0.5 1787 define-properties: 1.2.1 1788 - es-abstract: 1.22.3 1789 - get-intrinsic: 1.2.2 1790 - is-array-buffer: 3.0.2 1791 - is-shared-array-buffer: 1.0.2 1792 1793 astring@1.9.0: {} 1794 1795 - asynciterator.prototype@1.0.0: 1796 - dependencies: 1797 - has-symbols: 1.0.3 1798 1799 - available-typed-arrays@1.0.5: {} 1800 1801 balanced-match@1.0.2: {} 1802 1803 base64-js@1.5.1: {} 1804 1805 - big-integer@1.6.52: {} 1806 - 1807 - bplist-parser@0.2.0: 1808 - dependencies: 1809 - big-integer: 1.6.52 1810 - 1811 brace-expansion@1.1.11: 1812 dependencies: 1813 balanced-match: 1.0.2 ··· 1817 dependencies: 1818 balanced-match: 1.0.2 1819 1820 - braces@3.0.2: 1821 dependencies: 1822 - fill-range: 7.0.1 1823 1824 buffer@6.0.3: 1825 dependencies: 1826 base64-js: 1.5.1 1827 ieee754: 1.2.1 1828 1829 - bundle-name@3.0.0: 1830 - dependencies: 1831 - run-applescript: 5.0.0 1832 1833 - call-bind@1.0.5: 1834 dependencies: 1835 function-bind: 1.1.2 1836 - get-intrinsic: 1.2.2 1837 - set-function-length: 1.1.1 1838 1839 callsites@3.1.0: {} 1840 ··· 1851 1852 concat-map@0.0.1: {} 1853 1854 - cross-spawn@7.0.3: 1855 dependencies: 1856 path-key: 3.1.1 1857 shebang-command: 2.0.0 1858 which: 2.0.2 1859 1860 - csstype@3.1.2: {} 1861 - 1862 csstype@3.1.3: {} 1863 1864 - debug@4.3.4: 1865 dependencies: 1866 - ms: 2.1.2 1867 - 1868 - deep-is@0.1.4: {} 1869 1870 - default-browser-id@3.0.0: 1871 dependencies: 1872 - bplist-parser: 0.2.0 1873 - untildify: 4.0.0 1874 1875 - default-browser@4.0.0: 1876 dependencies: 1877 - bundle-name: 3.0.0 1878 - default-browser-id: 3.0.0 1879 - execa: 7.2.0 1880 - titleize: 3.0.0 1881 1882 - define-data-property@1.1.1: 1883 dependencies: 1884 - get-intrinsic: 1.2.2 1885 - gopd: 1.0.1 1886 - has-property-descriptors: 1.0.1 1887 1888 - define-lazy-prop@3.0.0: {} 1889 1890 define-properties@1.2.1: 1891 dependencies: 1892 - define-data-property: 1.1.1 1893 - has-property-descriptors: 1.0.1 1894 object-keys: 1.1.1 1895 1896 - dir-glob@3.0.1: 1897 - dependencies: 1898 - path-type: 4.0.0 1899 1900 doctrine@2.1.0: 1901 dependencies: 1902 esutils: 2.0.3 1903 1904 - doctrine@3.0.0: 1905 dependencies: 1906 - esutils: 2.0.3 1907 1908 - es-abstract@1.22.3: 1909 dependencies: 1910 - array-buffer-byte-length: 1.0.0 1911 - arraybuffer.prototype.slice: 1.0.2 1912 - available-typed-arrays: 1.0.5 1913 - call-bind: 1.0.5 1914 - es-set-tostringtag: 2.0.2 1915 - es-to-primitive: 1.2.1 1916 - function.prototype.name: 1.1.6 1917 - get-intrinsic: 1.2.2 1918 - get-symbol-description: 1.0.0 1919 - globalthis: 1.0.3 1920 - gopd: 1.0.1 1921 - has-property-descriptors: 1.0.1 1922 - has-proto: 1.0.1 1923 - has-symbols: 1.0.3 1924 - hasown: 2.0.0 1925 - internal-slot: 1.0.6 1926 - is-array-buffer: 3.0.2 1927 is-callable: 1.2.7 1928 - is-negative-zero: 2.0.2 1929 - is-regex: 1.1.4 1930 - is-shared-array-buffer: 1.0.2 1931 - is-string: 1.0.7 1932 - is-typed-array: 1.1.12 1933 - is-weakref: 1.0.2 1934 - object-inspect: 1.13.1 1935 object-keys: 1.1.1 1936 - object.assign: 4.1.5 1937 - regexp.prototype.flags: 1.5.1 1938 - safe-array-concat: 1.0.1 1939 - safe-regex-test: 1.0.0 1940 - string.prototype.trim: 1.2.8 1941 - string.prototype.trimend: 1.0.7 1942 - string.prototype.trimstart: 1.0.7 1943 - typed-array-buffer: 1.0.0 1944 - typed-array-byte-length: 1.0.0 1945 - typed-array-byte-offset: 1.0.0 1946 - typed-array-length: 1.0.4 1947 - unbox-primitive: 1.0.2 1948 - which-typed-array: 1.1.13 1949 1950 - es-iterator-helpers@1.0.15: 1951 dependencies: 1952 - asynciterator.prototype: 1.0.0 1953 - call-bind: 1.0.5 1954 define-properties: 1.2.1 1955 - es-abstract: 1.22.3 1956 - es-set-tostringtag: 2.0.2 1957 function-bind: 1.1.2 1958 - get-intrinsic: 1.2.2 1959 - globalthis: 1.0.3 1960 - has-property-descriptors: 1.0.1 1961 - has-proto: 1.0.1 1962 - has-symbols: 1.0.3 1963 - internal-slot: 1.0.6 1964 - iterator.prototype: 1.1.2 1965 - safe-array-concat: 1.0.1 1966 1967 - es-set-tostringtag@2.0.2: 1968 dependencies: 1969 - get-intrinsic: 1.2.2 1970 - has-tostringtag: 1.0.0 1971 - hasown: 2.0.0 1972 1973 - es-shim-unscopables@1.0.2: 1974 dependencies: 1975 - hasown: 2.0.0 1976 1977 - es-to-primitive@1.2.1: 1978 dependencies: 1979 is-callable: 1.2.7 1980 - is-date-object: 1.0.5 1981 - is-symbol: 1.0.4 1982 1983 esbuild-copy-static-files@0.1.0: {} 1984 ··· 2009 2010 escape-string-regexp@4.0.0: {} 2011 2012 - eslint-config-prettier@9.1.0(eslint@8.55.0): 2013 dependencies: 2014 - eslint: 8.55.0 2015 2016 - eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.1.0(eslint@8.55.0))(eslint@8.55.0)(prettier@3.1.0): 2017 dependencies: 2018 - eslint: 8.55.0 2019 prettier: 3.1.0 2020 prettier-linter-helpers: 1.0.0 2021 - synckit: 0.8.6 2022 optionalDependencies: 2023 - eslint-config-prettier: 9.1.0(eslint@8.55.0) 2024 2025 - eslint-plugin-react@7.33.2(eslint@8.55.0): 2026 dependencies: 2027 - array-includes: 3.1.7 2028 - array.prototype.flatmap: 1.3.2 2029 - array.prototype.tosorted: 1.1.2 2030 doctrine: 2.1.0 2031 - es-iterator-helpers: 1.0.15 2032 - eslint: 8.55.0 2033 estraverse: 5.3.0 2034 jsx-ast-utils: 3.3.5 2035 minimatch: 3.1.2 2036 - object.entries: 1.1.7 2037 - object.fromentries: 2.0.7 2038 - object.hasown: 1.1.3 2039 - object.values: 1.1.7 2040 prop-types: 15.8.1 2041 resolve: 2.0.0-next.5 2042 semver: 6.3.1 2043 - string.prototype.matchall: 4.0.10 2044 2045 - eslint-scope@7.2.2: 2046 dependencies: 2047 esrecurse: 4.3.0 2048 estraverse: 5.3.0 2049 2050 eslint-visitor-keys@3.4.3: {} 2051 2052 - eslint@8.55.0: 2053 dependencies: 2054 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) 2055 - '@eslint-community/regexpp': 4.10.0 2056 - '@eslint/eslintrc': 2.1.4 2057 - '@eslint/js': 8.55.0 2058 - '@humanwhocodes/config-array': 0.11.13 2059 '@humanwhocodes/module-importer': 1.0.1 2060 - '@nodelib/fs.walk': 1.2.8 2061 - '@ungap/structured-clone': 1.2.0 2062 ajv: 6.12.6 2063 chalk: 4.1.2 2064 - cross-spawn: 7.0.3 2065 - debug: 4.3.4 2066 - doctrine: 3.0.0 2067 escape-string-regexp: 4.0.0 2068 - eslint-scope: 7.2.2 2069 - eslint-visitor-keys: 3.4.3 2070 - espree: 9.6.1 2071 - esquery: 1.5.0 2072 esutils: 2.0.3 2073 fast-deep-equal: 3.1.3 2074 - file-entry-cache: 6.0.1 2075 find-up: 5.0.0 2076 glob-parent: 6.0.2 2077 - globals: 13.23.0 2078 - graphemer: 1.4.0 2079 - ignore: 5.3.0 2080 imurmurhash: 0.1.4 2081 is-glob: 4.0.3 2082 - is-path-inside: 3.0.3 2083 - js-yaml: 4.1.0 2084 json-stable-stringify-without-jsonify: 1.0.1 2085 - levn: 0.4.1 2086 lodash.merge: 4.6.2 2087 minimatch: 3.1.2 2088 natural-compare: 1.4.0 2089 optionator: 0.9.3 2090 - strip-ansi: 6.0.1 2091 - text-table: 0.2.0 2092 transitivePeerDependencies: 2093 - supports-color 2094 2095 - espree@9.6.1: 2096 dependencies: 2097 - acorn: 8.12.1 2098 - acorn-jsx: 5.3.2(acorn@8.12.1) 2099 - eslint-visitor-keys: 3.4.3 2100 2101 - esquery@1.5.0: 2102 dependencies: 2103 estraverse: 5.3.0 2104 ··· 2121 2122 events@3.3.0: {} 2123 2124 - execa@5.1.1: 2125 - dependencies: 2126 - cross-spawn: 7.0.3 2127 - get-stream: 6.0.1 2128 - human-signals: 2.1.0 2129 - is-stream: 2.0.1 2130 - merge-stream: 2.0.0 2131 - npm-run-path: 4.0.1 2132 - onetime: 5.1.2 2133 - signal-exit: 3.0.7 2134 - strip-final-newline: 2.0.0 2135 - 2136 - execa@7.2.0: 2137 - dependencies: 2138 - cross-spawn: 7.0.3 2139 - get-stream: 6.0.1 2140 - human-signals: 4.3.1 2141 - is-stream: 3.0.0 2142 - merge-stream: 2.0.0 2143 - npm-run-path: 5.1.0 2144 - onetime: 6.0.0 2145 - signal-exit: 3.0.7 2146 - strip-final-newline: 3.0.0 2147 - 2148 fast-deep-equal@3.1.3: {} 2149 2150 fast-diff@1.3.0: {} ··· 2155 '@nodelib/fs.walk': 1.2.8 2156 glob-parent: 5.1.2 2157 merge2: 1.4.1 2158 - micromatch: 4.0.5 2159 2160 fast-json-stable-stringify@2.1.0: {} 2161 2162 fast-levenshtein@2.0.6: {} 2163 2164 - fastq@1.15.0: 2165 dependencies: 2166 reusify: 1.0.4 2167 2168 - file-entry-cache@6.0.1: 2169 dependencies: 2170 - flat-cache: 3.2.0 2171 2172 - fill-range@7.0.1: 2173 dependencies: 2174 to-regex-range: 5.0.1 2175 2176 find-up@5.0.0: 2177 dependencies: 2178 locate-path: 6.0.0 2179 path-exists: 4.0.0 2180 2181 - flat-cache@3.2.0: 2182 dependencies: 2183 flatted: 3.2.9 2184 keyv: 4.5.4 2185 - rimraf: 3.0.2 2186 2187 flatted@3.2.9: {} 2188 2189 - for-each@0.3.3: 2190 dependencies: 2191 is-callable: 1.2.7 2192 2193 - fs.realpath@1.0.0: {} 2194 - 2195 function-bind@1.1.2: {} 2196 2197 - function.prototype.name@1.1.6: 2198 dependencies: 2199 - call-bind: 1.0.5 2200 define-properties: 1.2.1 2201 - es-abstract: 1.22.3 2202 functions-have-names: 1.2.3 2203 2204 functions-have-names@1.2.3: {} 2205 2206 - get-intrinsic@1.2.2: 2207 dependencies: 2208 function-bind: 1.1.2 2209 - has-proto: 1.0.1 2210 - has-symbols: 1.0.3 2211 - hasown: 2.0.0 2212 2213 - get-stream@6.0.1: {} 2214 2215 - get-symbol-description@1.0.0: 2216 dependencies: 2217 - call-bind: 1.0.5 2218 - get-intrinsic: 1.2.2 2219 2220 glob-parent@5.1.2: 2221 dependencies: ··· 2225 dependencies: 2226 is-glob: 4.0.3 2227 2228 - glob@7.2.3: 2229 - dependencies: 2230 - fs.realpath: 1.0.0 2231 - inflight: 1.0.6 2232 - inherits: 2.0.4 2233 - minimatch: 3.1.2 2234 - once: 1.4.0 2235 - path-is-absolute: 1.0.1 2236 2237 - globals@13.23.0: 2238 - dependencies: 2239 - type-fest: 0.20.2 2240 - 2241 - globalthis@1.0.3: 2242 dependencies: 2243 define-properties: 1.2.1 2244 - 2245 - globby@11.1.0: 2246 - dependencies: 2247 - array-union: 2.1.0 2248 - dir-glob: 3.0.1 2249 - fast-glob: 3.3.2 2250 - ignore: 5.3.0 2251 - merge2: 1.4.1 2252 - slash: 3.0.0 2253 2254 - gopd@1.0.1: 2255 - dependencies: 2256 - get-intrinsic: 1.2.2 2257 2258 graphemer@1.4.0: {} 2259 2260 - has-bigints@1.0.2: {} 2261 2262 has-flag@4.0.0: {} 2263 2264 - has-property-descriptors@1.0.1: 2265 dependencies: 2266 - get-intrinsic: 1.2.2 2267 2268 - has-proto@1.0.1: {} 2269 2270 - has-symbols@1.0.3: {} 2271 2272 - has-tostringtag@1.0.0: 2273 dependencies: 2274 - has-symbols: 1.0.3 2275 2276 - hasown@2.0.0: 2277 dependencies: 2278 function-bind: 1.1.2 2279 2280 - human-signals@2.1.0: {} 2281 - 2282 - human-signals@4.3.1: {} 2283 - 2284 husky@8.0.3: {} 2285 2286 ieee754@1.2.1: {} 2287 2288 - ignore@5.3.0: {} 2289 2290 import-fresh@3.3.0: 2291 dependencies: ··· 2294 2295 imurmurhash@0.1.4: {} 2296 2297 - inflight@1.0.6: 2298 dependencies: 2299 - once: 1.4.0 2300 - wrappy: 1.0.2 2301 2302 - inherits@2.0.4: {} 2303 - 2304 - internal-slot@1.0.6: 2305 dependencies: 2306 - get-intrinsic: 1.2.2 2307 - hasown: 2.0.0 2308 - side-channel: 1.0.4 2309 - 2310 - is-array-buffer@3.0.2: 2311 - dependencies: 2312 - call-bind: 1.0.5 2313 - get-intrinsic: 1.2.2 2314 - is-typed-array: 1.1.12 2315 2316 - is-async-function@2.0.0: 2317 dependencies: 2318 - has-tostringtag: 1.0.0 2319 2320 - is-bigint@1.0.4: 2321 dependencies: 2322 - has-bigints: 1.0.2 2323 2324 - is-boolean-object@1.1.2: 2325 dependencies: 2326 - call-bind: 1.0.5 2327 - has-tostringtag: 1.0.0 2328 2329 is-callable@1.2.7: {} 2330 2331 - is-core-module@2.13.1: 2332 dependencies: 2333 - hasown: 2.0.0 2334 2335 - is-date-object@1.0.5: 2336 dependencies: 2337 - has-tostringtag: 1.0.0 2338 2339 - is-docker@2.2.1: {} 2340 - 2341 - is-docker@3.0.0: {} 2342 2343 is-extglob@2.1.1: {} 2344 2345 - is-finalizationregistry@1.0.2: 2346 dependencies: 2347 - call-bind: 1.0.5 2348 2349 - is-generator-function@1.0.10: 2350 dependencies: 2351 - has-tostringtag: 1.0.0 2352 2353 is-glob@4.0.3: 2354 dependencies: 2355 is-extglob: 2.1.1 2356 2357 - is-inside-container@1.0.0: 2358 - dependencies: 2359 - is-docker: 3.0.0 2360 2361 - is-map@2.0.2: {} 2362 - 2363 - is-negative-zero@2.0.2: {} 2364 - 2365 - is-number-object@1.0.7: 2366 dependencies: 2367 - has-tostringtag: 1.0.0 2368 2369 is-number@7.0.0: {} 2370 2371 - is-path-inside@3.0.3: {} 2372 - 2373 - is-regex@1.1.4: 2374 dependencies: 2375 - call-bind: 1.0.5 2376 - has-tostringtag: 1.0.0 2377 2378 - is-set@2.0.2: {} 2379 2380 - is-shared-array-buffer@1.0.2: 2381 dependencies: 2382 - call-bind: 1.0.5 2383 - 2384 - is-stream@2.0.1: {} 2385 - 2386 - is-stream@3.0.0: {} 2387 2388 - is-string@1.0.7: 2389 dependencies: 2390 - has-tostringtag: 1.0.0 2391 2392 - is-symbol@1.0.4: 2393 dependencies: 2394 - has-symbols: 1.0.3 2395 2396 - is-typed-array@1.1.12: 2397 dependencies: 2398 - which-typed-array: 1.1.13 2399 2400 - is-weakmap@2.0.1: {} 2401 2402 - is-weakref@1.0.2: 2403 dependencies: 2404 - call-bind: 1.0.5 2405 2406 - is-weakset@2.0.2: 2407 dependencies: 2408 - call-bind: 1.0.5 2409 - get-intrinsic: 1.2.2 2410 - 2411 - is-wsl@2.2.0: 2412 - dependencies: 2413 - is-docker: 2.2.1 2414 2415 isarray@2.0.5: {} 2416 2417 isexe@2.0.0: {} 2418 2419 - iterator.prototype@1.1.2: 2420 dependencies: 2421 - define-properties: 1.2.1 2422 - get-intrinsic: 1.2.2 2423 - has-symbols: 1.0.3 2424 - reflect.getprototypeof: 1.0.4 2425 - set-function-name: 2.0.1 2426 2427 js-tokens@4.0.0: {} 2428 ··· 2438 2439 jsx-ast-utils@3.3.5: 2440 dependencies: 2441 - array-includes: 3.1.7 2442 - array.prototype.flat: 1.3.2 2443 - object.assign: 4.1.5 2444 - object.values: 1.1.7 2445 2446 keyv@4.5.4: 2447 dependencies: ··· 2462 dependencies: 2463 js-tokens: 4.0.0 2464 2465 - lru-cache@6.0.0: 2466 - dependencies: 2467 - yallist: 4.0.0 2468 - 2469 - merge-stream@2.0.0: {} 2470 2471 merge2@1.4.1: {} 2472 2473 meriyah@6.0.1: {} 2474 2475 - micromatch@4.0.5: 2476 dependencies: 2477 - braces: 3.0.2 2478 picomatch: 2.3.1 2479 2480 - mimic-fn@2.1.0: {} 2481 - 2482 - mimic-fn@4.0.0: {} 2483 2484 minimatch@3.1.2: 2485 dependencies: ··· 2489 dependencies: 2490 brace-expansion: 2.0.1 2491 2492 - ms@2.1.2: {} 2493 2494 nanotar@0.1.1: {} 2495 2496 natural-compare@1.4.0: {} 2497 2498 - npm-run-path@4.0.1: 2499 - dependencies: 2500 - path-key: 3.1.1 2501 - 2502 - npm-run-path@5.1.0: 2503 - dependencies: 2504 - path-key: 4.0.0 2505 2506 object-assign@4.1.1: {} 2507 2508 - object-inspect@1.13.1: {} 2509 2510 object-keys@1.1.1: {} 2511 2512 - object.assign@4.1.5: 2513 dependencies: 2514 - call-bind: 1.0.5 2515 define-properties: 1.2.1 2516 - has-symbols: 1.0.3 2517 object-keys: 1.1.1 2518 2519 - object.entries@1.1.7: 2520 dependencies: 2521 - call-bind: 1.0.5 2522 define-properties: 1.2.1 2523 - es-abstract: 1.22.3 2524 2525 - object.fromentries@2.0.7: 2526 dependencies: 2527 - call-bind: 1.0.5 2528 - define-properties: 1.2.1 2529 - es-abstract: 1.22.3 2530 - 2531 - object.hasown@1.1.3: 2532 - dependencies: 2533 define-properties: 1.2.1 2534 - es-abstract: 1.22.3 2535 2536 - object.values@1.1.7: 2537 dependencies: 2538 - call-bind: 1.0.5 2539 define-properties: 1.2.1 2540 - es-abstract: 1.22.3 2541 2542 - once@1.4.0: 2543 dependencies: 2544 - wrappy: 1.0.2 2545 - 2546 - onetime@5.1.2: 2547 - dependencies: 2548 - mimic-fn: 2.1.0 2549 - 2550 - onetime@6.0.0: 2551 - dependencies: 2552 - mimic-fn: 4.0.0 2553 2554 - open@9.1.0: 2555 dependencies: 2556 - default-browser: 4.0.0 2557 - define-lazy-prop: 3.0.0 2558 - is-inside-container: 1.0.0 2559 - is-wsl: 2.2.0 2560 2561 optionator@0.9.3: 2562 dependencies: ··· 2567 prelude-ls: 1.2.1 2568 type-check: 0.4.0 2569 2570 p-limit@3.1.0: 2571 dependencies: 2572 yocto-queue: 0.1.0 ··· 2574 p-locate@5.0.0: 2575 dependencies: 2576 p-limit: 3.1.0 2577 2578 parent-module@1.0.1: 2579 dependencies: ··· 2581 2582 path-exists@4.0.0: {} 2583 2584 - path-is-absolute@1.0.1: {} 2585 - 2586 path-key@3.1.1: {} 2587 2588 - path-key@4.0.0: {} 2589 - 2590 path-parse@1.0.7: {} 2591 2592 - path-type@4.0.0: {} 2593 2594 - picocolors@1.0.0: {} 2595 2596 - picomatch@2.3.1: {} 2597 2598 prelude-ls@1.2.1: {} 2599 ··· 2613 2614 punycode@2.3.1: {} 2615 2616 queue-microtask@1.2.3: {} 2617 2618 react-is@16.13.1: {} ··· 2625 process: 0.11.10 2626 string_decoder: 1.3.0 2627 2628 - reflect.getprototypeof@1.0.4: 2629 dependencies: 2630 - call-bind: 1.0.5 2631 define-properties: 1.2.1 2632 - es-abstract: 1.22.3 2633 - get-intrinsic: 1.2.2 2634 - globalthis: 1.0.3 2635 - which-builtin-type: 1.1.3 2636 2637 - regexp.prototype.flags@1.5.1: 2638 dependencies: 2639 - call-bind: 1.0.5 2640 define-properties: 1.2.1 2641 - set-function-name: 2.0.1 2642 2643 resolve-from@4.0.0: {} 2644 2645 resolve@2.0.0-next.5: 2646 dependencies: 2647 - is-core-module: 2.13.1 2648 path-parse: 1.0.7 2649 supports-preserve-symlinks-flag: 1.0.0 2650 2651 - reusify@1.0.4: {} 2652 - 2653 - rimraf@3.0.2: 2654 dependencies: 2655 - glob: 7.2.3 2656 2657 - run-applescript@5.0.0: 2658 - dependencies: 2659 - execa: 5.1.1 2660 2661 run-parallel@1.2.0: 2662 dependencies: 2663 queue-microtask: 1.2.3 2664 2665 - safe-array-concat@1.0.1: 2666 dependencies: 2667 - call-bind: 1.0.5 2668 - get-intrinsic: 1.2.2 2669 - has-symbols: 1.0.3 2670 isarray: 2.0.5 2671 2672 - safe-buffer@5.1.2: {} 2673 - 2674 safe-buffer@5.2.1: {} 2675 2676 - safe-regex-test@1.0.0: 2677 dependencies: 2678 - call-bind: 1.0.5 2679 - get-intrinsic: 1.2.2 2680 - is-regex: 1.1.4 2681 2682 semver@6.3.1: {} 2683 2684 - semver@7.5.4: 2685 - dependencies: 2686 - lru-cache: 6.0.0 2687 2688 - set-function-length@1.1.1: 2689 dependencies: 2690 - define-data-property: 1.1.1 2691 - get-intrinsic: 1.2.2 2692 - gopd: 1.0.1 2693 - has-property-descriptors: 1.0.1 2694 2695 - set-function-name@2.0.1: 2696 dependencies: 2697 - define-data-property: 1.1.1 2698 functions-have-names: 1.2.3 2699 - has-property-descriptors: 1.0.1 2700 2701 shebang-command@2.0.0: 2702 dependencies: ··· 2704 2705 shebang-regex@3.0.0: {} 2706 2707 - side-channel@1.0.4: 2708 dependencies: 2709 - call-bind: 1.0.5 2710 - get-intrinsic: 1.2.2 2711 - object-inspect: 1.13.1 2712 2713 - signal-exit@3.0.7: {} 2714 2715 - slash@3.0.0: {} 2716 2717 standalone-electron-types@1.0.0: 2718 dependencies: 2719 '@types/node': 18.17.17 2720 2721 - string.prototype.matchall@4.0.10: 2722 dependencies: 2723 - call-bind: 1.0.5 2724 define-properties: 1.2.1 2725 - es-abstract: 1.22.3 2726 - get-intrinsic: 1.2.2 2727 - has-symbols: 1.0.3 2728 - internal-slot: 1.0.6 2729 - regexp.prototype.flags: 1.5.1 2730 - set-function-name: 2.0.1 2731 - side-channel: 1.0.4 2732 2733 - string.prototype.trim@1.2.8: 2734 dependencies: 2735 - call-bind: 1.0.5 2736 define-properties: 1.2.1 2737 - es-abstract: 1.22.3 2738 2739 - string.prototype.trimend@1.0.7: 2740 dependencies: 2741 - call-bind: 1.0.5 2742 define-properties: 1.2.1 2743 - es-abstract: 1.22.3 2744 2745 - string.prototype.trimstart@1.0.7: 2746 dependencies: 2747 - call-bind: 1.0.5 2748 define-properties: 1.2.1 2749 - es-abstract: 1.22.3 2750 2751 - string_decoder@1.3.0: 2752 dependencies: 2753 - safe-buffer: 5.2.1 2754 2755 - strip-ansi@6.0.1: 2756 dependencies: 2757 - ansi-regex: 5.0.1 2758 - 2759 - strip-final-newline@2.0.0: {} 2760 - 2761 - strip-final-newline@3.0.0: {} 2762 2763 strip-json-comments@3.1.1: {} 2764 ··· 2768 2769 supports-preserve-symlinks-flag@1.0.0: {} 2770 2771 - synckit@0.8.6: 2772 dependencies: 2773 - '@pkgr/utils': 2.4.2 2774 - tslib: 2.6.2 2775 2776 - text-table@0.2.0: {} 2777 2778 - titleize@3.0.0: {} 2779 2780 to-regex-range@5.0.1: 2781 dependencies: 2782 is-number: 7.0.0 2783 2784 - ts-api-utils@1.0.3(typescript@5.3.2): 2785 dependencies: 2786 - typescript: 5.3.2 2787 2788 - tslib@2.6.2: {} 2789 2790 type-check@0.4.0: 2791 dependencies: 2792 prelude-ls: 1.2.1 2793 2794 - type-fest@0.20.2: {} 2795 2796 - typed-array-buffer@1.0.0: 2797 dependencies: 2798 - call-bind: 1.0.5 2799 - get-intrinsic: 1.2.2 2800 - is-typed-array: 1.1.12 2801 2802 - typed-array-byte-length@1.0.0: 2803 dependencies: 2804 - call-bind: 1.0.5 2805 - for-each: 0.3.3 2806 - has-proto: 1.0.1 2807 - is-typed-array: 1.1.12 2808 2809 - typed-array-byte-offset@1.0.0: 2810 dependencies: 2811 - available-typed-arrays: 1.0.5 2812 - call-bind: 1.0.5 2813 - for-each: 0.3.3 2814 - has-proto: 1.0.1 2815 - is-typed-array: 1.1.12 2816 2817 - typed-array-length@1.0.4: 2818 dependencies: 2819 - call-bind: 1.0.5 2820 - for-each: 0.3.3 2821 - is-typed-array: 1.1.12 2822 2823 - typescript@5.3.2: {} 2824 2825 - unbox-primitive@1.0.2: 2826 dependencies: 2827 - call-bind: 1.0.5 2828 - has-bigints: 1.0.2 2829 - has-symbols: 1.0.3 2830 - which-boxed-primitive: 1.0.2 2831 2832 - undici-types@6.19.8: {} 2833 2834 - untildify@4.0.0: {} 2835 2836 uri-js@4.4.1: 2837 dependencies: 2838 punycode: 2.3.1 2839 2840 - utilium@0.7.1: 2841 dependencies: 2842 eventemitter3: 5.0.1 2843 2844 - which-boxed-primitive@1.0.2: 2845 dependencies: 2846 - is-bigint: 1.0.4 2847 - is-boolean-object: 1.1.2 2848 - is-number-object: 1.0.7 2849 - is-string: 1.0.7 2850 - is-symbol: 1.0.4 2851 2852 - which-builtin-type@1.1.3: 2853 dependencies: 2854 - function.prototype.name: 1.1.6 2855 - has-tostringtag: 1.0.0 2856 - is-async-function: 2.0.0 2857 - is-date-object: 1.0.5 2858 - is-finalizationregistry: 1.0.2 2859 - is-generator-function: 1.0.10 2860 - is-regex: 1.1.4 2861 - is-weakref: 1.0.2 2862 isarray: 2.0.5 2863 - which-boxed-primitive: 1.0.2 2864 - which-collection: 1.0.1 2865 - which-typed-array: 1.1.13 2866 2867 - which-collection@1.0.1: 2868 dependencies: 2869 - is-map: 2.0.2 2870 - is-set: 2.0.2 2871 - is-weakmap: 2.0.1 2872 - is-weakset: 2.0.2 2873 2874 - which-typed-array@1.1.13: 2875 dependencies: 2876 - available-typed-arrays: 1.0.5 2877 - call-bind: 1.0.5 2878 - for-each: 0.3.3 2879 - gopd: 1.0.1 2880 - has-tostringtag: 1.0.0 2881 2882 which@2.0.2: 2883 dependencies: 2884 isexe: 2.0.0 2885 2886 - wrappy@1.0.2: {} 2887 - 2888 - yallist@4.0.0: {} 2889 2890 yocto-queue@0.1.0: {}
··· 4 autoInstallPeers: true 5 excludeLinksFromLockfile: false 6 7 + catalogs: 8 + dev: 9 + '@moonlight-mod/eslint-config': 10 + specifier: github:moonlight-mod/eslint-config 11 + version: 1.0.1 12 + '@types/chrome': 13 + specifier: ^0.0.313 14 + version: 0.0.313 15 + '@types/node': 16 + specifier: ^22.14.0 17 + version: 22.14.0 18 + esbuild: 19 + specifier: ^0.19.3 20 + version: 0.19.3 21 + esbuild-copy-static-files: 22 + specifier: ^0.1.0 23 + version: 0.1.0 24 + eslint: 25 + specifier: ^9.12.0 26 + version: 9.23.0 27 + husky: 28 + specifier: ^8.0.3 29 + version: 8.0.3 30 + prettier: 31 + specifier: ^3.1.0 32 + version: 3.1.0 33 + taze: 34 + specifier: ^19.0.4 35 + version: 19.0.4 36 + typescript: 37 + specifier: ^5.3.3 38 + version: 5.8.2 39 + prod: 40 + '@moonlight-mod/lunast': 41 + specifier: ^1.0.1 42 + version: 1.0.1 43 + '@moonlight-mod/mappings': 44 + specifier: ^1.1.25 45 + version: 1.1.25 46 + '@moonlight-mod/moonmap': 47 + specifier: ^1.0.5 48 + version: 1.0.5 49 + '@zenfs/core': 50 + specifier: ^2.0.0 51 + version: 2.0.0 52 + '@zenfs/dom': 53 + specifier: ^1.1.3 54 + version: 1.1.6 55 + microdiff: 56 + specifier: ^1.5.0 57 + version: 1.5.0 58 + nanotar: 59 + specifier: ^0.1.1 60 + version: 0.1.1 61 + 62 importers: 63 64 .: 65 devDependencies: 66 + '@moonlight-mod/eslint-config': 67 + specifier: catalog:dev 68 + version: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9(@types/eslint@9.6.1)(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0)(typescript@5.8.2) 69 + '@types/node': 70 + specifier: catalog:dev 71 + version: 22.14.0 72 esbuild: 73 + specifier: catalog:dev 74 version: 0.19.3 75 esbuild-copy-static-files: 76 + specifier: catalog:dev 77 version: 0.1.0 78 eslint: 79 + specifier: catalog:dev 80 + version: 9.23.0(jiti@2.4.2) 81 husky: 82 + specifier: catalog:dev 83 version: 8.0.3 84 prettier: 85 + specifier: catalog:dev 86 version: 3.1.0 87 + taze: 88 + specifier: catalog:dev 89 + version: 19.0.4 90 typescript: 91 + specifier: catalog:dev 92 + version: 5.8.2 93 94 packages/browser: 95 dependencies: ··· 103 specifier: workspace:* 104 version: link:../web-preload 105 '@zenfs/core': 106 + specifier: catalog:prod 107 + version: 2.0.0 108 '@zenfs/dom': 109 + specifier: catalog:prod 110 + version: 1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1) 111 + devDependencies: 112 + '@types/chrome': 113 + specifier: catalog:dev 114 + version: 0.0.313 115 116 packages/core: 117 dependencies: ··· 127 '@moonlight-mod/types': 128 specifier: workspace:* 129 version: link:../types 130 + microdiff: 131 + specifier: catalog:prod 132 + version: 1.5.0 133 nanotar: 134 + specifier: catalog:prod 135 version: 0.1.1 136 137 packages/injector: ··· 155 packages/types: 156 dependencies: 157 '@moonlight-mod/lunast': 158 + specifier: ^1.0.1 159 + version: 1.0.1 160 '@moonlight-mod/mappings': 161 + specifier: ^1.1.25 162 + version: 1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5) 163 '@moonlight-mod/moonmap': 164 + specifier: ^1.0.5 165 + version: 1.0.5 166 '@types/react': 167 specifier: ^18.3.10 168 + version: 18.3.20 169 csstype: 170 + specifier: ^3.1.3 171 + version: 3.1.3 172 standalone-electron-types: 173 specifier: ^1.0.0 174 version: 1.0.0 ··· 179 specifier: workspace:* 180 version: link:../core 181 '@moonlight-mod/lunast': 182 + specifier: catalog:prod 183 + version: 1.0.1 184 '@moonlight-mod/mappings': 185 + specifier: catalog:prod 186 + version: 1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5) 187 '@moonlight-mod/moonmap': 188 + specifier: catalog:prod 189 + version: 1.0.5 190 '@moonlight-mod/types': 191 specifier: workspace:* 192 version: link:../types ··· 196 '@aashutoshrathi/word-wrap@1.2.6': 197 resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 198 engines: {node: '>=0.10.0'} 199 + 200 + '@antfu/ni@24.3.0': 201 + resolution: {integrity: sha512-wBSav4mBxvHEW9RbdSo1SWLQ6MAlT0Dc423weC58yOWqW4OcMvtnNDdDrxOZeJ88fEIyPK93gDUWIelBxzSf8g==} 202 + hasBin: true 203 204 '@esbuild/android-arm64@0.19.3': 205 resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==} ··· 333 cpu: [x64] 334 os: [win32] 335 336 + '@eslint-community/eslint-utils@4.5.1': 337 + resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} 338 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 339 peerDependencies: 340 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 341 342 + '@eslint-community/regexpp@4.12.1': 343 + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} 344 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 345 346 + '@eslint/config-array@0.19.2': 347 + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} 348 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 349 + 350 + '@eslint/config-helpers@0.2.1': 351 + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} 352 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 353 + 354 + '@eslint/core@0.12.0': 355 + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} 356 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 357 + 358 + '@eslint/core@0.13.0': 359 + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} 360 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 361 + 362 + '@eslint/eslintrc@3.3.1': 363 + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} 364 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 365 366 + '@eslint/js@9.23.0': 367 + resolution: {integrity: sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==} 368 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 369 370 + '@eslint/object-schema@2.1.6': 371 + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} 372 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 373 + 374 + '@eslint/plugin-kit@0.2.8': 375 + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} 376 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 377 + 378 + '@humanfs/core@0.19.1': 379 + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 380 + engines: {node: '>=18.18.0'} 381 + 382 + '@humanfs/node@0.16.6': 383 + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} 384 + engines: {node: '>=18.18.0'} 385 386 '@humanwhocodes/module-importer@1.0.1': 387 resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 388 engines: {node: '>=12.22'} 389 390 + '@humanwhocodes/retry@0.3.1': 391 + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} 392 + engines: {node: '>=18.18'} 393 394 + '@humanwhocodes/retry@0.4.2': 395 + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} 396 + engines: {node: '>=18.18'} 397 398 + '@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9': 399 + resolution: {tarball: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9} 400 + version: 1.0.1 401 peerDependencies: 402 + eslint: '>= 9' 403 + typescript: '>= 5.3' 404 + 405 + '@moonlight-mod/lunast@1.0.1': 406 + resolution: {integrity: sha512-K3vxzDlfFuYKjciIW2FMlcZ1qrrkAGDGpSBlNqYGtJ0sMt9bRCd2lpSpg6AX/giSljDtmAUXa/5mOfUoDQxjBA==} 407 408 + '@moonlight-mod/mappings@1.1.25': 409 + resolution: {integrity: sha512-bgnSN9H/IBdMGxGev6RQKXuzhQxwo1090NhIDHnflguZnjiu2pg/usPfh76bqyhxRuX4SS7tiZSNTwBoSflCLg==} 410 + engines: {node: '>=22', npm: pnpm, pnpm: '>=10', yarn: pnpm} 411 + peerDependencies: 412 + '@moonlight-mod/lunast': ^1.0.1 413 + '@moonlight-mod/moonmap': ^1.0.5 414 + 415 + '@moonlight-mod/moonmap@1.0.5': 416 + resolution: {integrity: sha512-Fdpxj8ghdulKB6TlTnchlCPey2YUKgEf1chuO1ofOIcvlqnVPBcQwSf2S80naOUQpXCDo4dQ+LWSE2fmhdDiiw==} 417 418 '@nodelib/fs.scandir@2.1.5': 419 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} ··· 427 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 428 engines: {node: '>= 8'} 429 430 + '@pkgr/core@0.2.0': 431 + resolution: {integrity: sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==} 432 engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 433 434 + '@quansync/fs@0.1.2': 435 + resolution: {integrity: sha512-ezIadUb1aFhwJLd++WVqVpi9rnlX8vnd4ju7saPhwLHJN1mJgOv0puePTGV+FbtSnWtwoHDT8lAm4kagDZmpCg==} 436 + engines: {node: '>=20.0.0'} 437 + 438 + '@types/chroma-js@3.1.0': 439 + resolution: {integrity: sha512-Uwl3SOtUkbQ6Ye6ZYu4q4xdLGBzmY839sEHYtOT7i691neeyd+7fXWT5VIkcUSfNwIFrIjQutNYQn9h4q5HFvg==} 440 + 441 + '@types/chrome@0.0.313': 442 + resolution: {integrity: sha512-9R5T7gTaYZhkxlu+Ho4wk9FL+y/werWQY2yjGWSqCuiTsqS7nL/BE5UMTP6rU7J+oIG2FRKqrEycHhJATeltVA==} 443 + 444 + '@types/eslint@9.6.1': 445 + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} 446 + 447 '@types/estree-jsx@1.0.5': 448 resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} 449 450 '@types/estree@1.0.6': 451 resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 452 453 + '@types/estree@1.0.7': 454 + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} 455 + 456 '@types/fbemitter@2.0.35': 457 resolution: {integrity: sha512-Xem6d7qUfmouCHntCrRYgDBwbf+WWRd6G+7WEFlEZFZ67LZXiYRvT2LV8wcZa6mIaAil95+ABQdKgB6hPIsnng==} 458 459 + '@types/filesystem@0.0.36': 460 + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} 461 + 462 + '@types/filewriter@0.0.33': 463 + resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} 464 + 465 '@types/flux@3.1.14': 466 resolution: {integrity: sha512-WRXN0kQPCnqxN0/PgNgc7WBF6c8rbSHsEep3/qBLpsQ824RONdOmTs0TV7XhIW2GDNRAHO2CqCgAFLR5PChosw==} 467 468 + '@types/har-format@1.2.16': 469 + resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} 470 + 471 + '@types/highlightjs@9.12.6': 472 + resolution: {integrity: sha512-Qfd1DUrwE851Hc3tExADJY4qY8yeZMt06Xw9AJm/UtpneepJS3MZY29c33BY0wP899veaaHD4gZzYiSuQm84Fg==} 473 + 474 '@types/json-schema@7.0.15': 475 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 476 477 + '@types/lodash@4.17.14': 478 + resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} 479 + 480 '@types/node@18.17.17': 481 resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} 482 483 + '@types/node@22.13.6': 484 + resolution: {integrity: sha512-GYmF65GI7417CpZXsEXMjT8goQQDnpRnJnDw6jIYa+le3V/lMazPZ4vZmK1B/9R17fh2VLr2zuy9d/h5xgrLAg==} 485 + 486 + '@types/node@22.14.0': 487 + resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} 488 + 489 + '@types/platform@1.3.6': 490 + resolution: {integrity: sha512-ZmSaqHuvzv+jC232cFoz2QqPUkaj6EvMmCrWcx3WRr7xTPVFCMUOTcOq8m2d+Zw1iKRc1kDiaA+jtNrV0hkVew==} 491 492 '@types/prop-types@15.7.13': 493 resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} 494 495 + '@types/react@18.3.20': 496 + resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==} 497 498 + '@typescript-eslint/eslint-plugin@8.29.0': 499 + resolution: {integrity: sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==} 500 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 501 peerDependencies: 502 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 503 + eslint: ^8.57.0 || ^9.0.0 504 + typescript: '>=4.8.4 <5.9.0' 505 506 + '@typescript-eslint/parser@8.29.0': 507 + resolution: {integrity: sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==} 508 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 509 peerDependencies: 510 + eslint: ^8.57.0 || ^9.0.0 511 + typescript: '>=4.8.4 <5.9.0' 512 513 + '@typescript-eslint/scope-manager@8.29.0': 514 + resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==} 515 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 516 517 + '@typescript-eslint/type-utils@8.29.0': 518 + resolution: {integrity: sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==} 519 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 520 peerDependencies: 521 + eslint: ^8.57.0 || ^9.0.0 522 + typescript: '>=4.8.4 <5.9.0' 523 524 + '@typescript-eslint/types@8.29.0': 525 + resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==} 526 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 527 528 + '@typescript-eslint/typescript-estree@8.29.0': 529 + resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==} 530 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 531 peerDependencies: 532 + typescript: '>=4.8.4 <5.9.0' 533 534 + '@typescript-eslint/utils@8.29.0': 535 + resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==} 536 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 537 peerDependencies: 538 + eslint: ^8.57.0 || ^9.0.0 539 + typescript: '>=4.8.4 <5.9.0' 540 541 + '@typescript-eslint/visitor-keys@8.29.0': 542 + resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==} 543 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 544 545 + '@xterm/xterm@5.5.0': 546 + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} 547 548 + '@zenfs/core@2.0.0': 549 + resolution: {integrity: sha512-wOKNFTY1DJ1vdLqKdU7M8cRh0nVYZcDVu7WHuk/3u49hrSwTZVm4PzGxJUjFd8O9Wi3U5nYTbZoN7RX5mS2ldA==} 550 + engines: {node: '>= 18'} 551 hasBin: true 552 553 + '@zenfs/dom@1.1.6': 554 + resolution: {integrity: sha512-7SBTWgA0esuEv/TE+N/xk6W/XJf8uBF+LhlPNHQdXds0H7aOy/UYsWv/8glvARe+meDMMidoeWFLzUWoMXfjlA==} 555 engines: {node: '>= 18'} 556 peerDependencies: 557 + '@zenfs/core': ^2.0.0 558 + utilium: ^1.9.0 559 560 abort-controller@3.0.0: 561 resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} ··· 566 peerDependencies: 567 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 568 569 + acorn@8.14.1: 570 + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 571 engines: {node: '>=0.4.0'} 572 hasBin: true 573 574 ajv@6.12.6: 575 resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 576 577 ansi-styles@4.3.0: 578 resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 579 engines: {node: '>=8'} 580 581 + ansis@3.17.0: 582 + resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} 583 + engines: {node: '>=14'} 584 + 585 argparse@2.0.1: 586 resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 587 588 + array-buffer-byte-length@1.0.2: 589 + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} 590 + engines: {node: '>= 0.4'} 591 592 + array-includes@3.1.8: 593 + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} 594 engines: {node: '>= 0.4'} 595 596 + array.prototype.findlast@1.2.5: 597 + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} 598 + engines: {node: '>= 0.4'} 599 600 + array.prototype.flat@1.3.3: 601 + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} 602 engines: {node: '>= 0.4'} 603 604 + array.prototype.flatmap@1.3.3: 605 + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} 606 engines: {node: '>= 0.4'} 607 608 + array.prototype.tosorted@1.1.4: 609 + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} 610 + engines: {node: '>= 0.4'} 611 612 + arraybuffer.prototype.slice@1.0.4: 613 + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} 614 engines: {node: '>= 0.4'} 615 616 astring@1.9.0: 617 resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} 618 hasBin: true 619 620 + async-function@1.0.0: 621 + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} 622 + engines: {node: '>= 0.4'} 623 624 + available-typed-arrays@1.0.7: 625 + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} 626 engines: {node: '>= 0.4'} 627 628 balanced-match@1.0.2: ··· 631 base64-js@1.5.1: 632 resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 633 634 brace-expansion@1.1.11: 635 resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 636 637 brace-expansion@2.0.1: 638 resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 639 640 + braces@3.0.3: 641 + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 642 engines: {node: '>=8'} 643 644 buffer@6.0.3: 645 resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 646 647 + cac@6.7.14: 648 + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 649 + engines: {node: '>=8'} 650 651 + call-bind-apply-helpers@1.0.2: 652 + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 653 + engines: {node: '>= 0.4'} 654 + 655 + call-bind@1.0.8: 656 + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} 657 + engines: {node: '>= 0.4'} 658 + 659 + call-bound@1.0.4: 660 + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 661 + engines: {node: '>= 0.4'} 662 663 callsites@3.1.0: 664 resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} ··· 678 concat-map@0.0.1: 679 resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 680 681 + cross-spawn@7.0.6: 682 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 683 engines: {node: '>= 8'} 684 685 csstype@3.1.3: 686 resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 687 688 + data-view-buffer@1.0.2: 689 + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} 690 + engines: {node: '>= 0.4'} 691 + 692 + data-view-byte-length@1.0.2: 693 + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} 694 + engines: {node: '>= 0.4'} 695 + 696 + data-view-byte-offset@1.0.1: 697 + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} 698 + engines: {node: '>= 0.4'} 699 + 700 + debug@4.4.0: 701 + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 702 engines: {node: '>=6.0'} 703 peerDependencies: 704 supports-color: '*' ··· 709 deep-is@0.1.4: 710 resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 711 712 + define-data-property@1.1.4: 713 + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 714 engines: {node: '>= 0.4'} 715 716 define-properties@1.2.1: 717 resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} 718 engines: {node: '>= 0.4'} 719 720 + defu@6.1.4: 721 + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 722 + 723 + destr@2.0.4: 724 + resolution: {integrity: sha512-FCAorltMy7QwX0QU38jOkhrv20LBpsHA8ogzvMhhPHCCKVCaN6GxrB0GGaWEWBUYI4eEjjfJ95RdP6dk9IdMQA==} 725 726 doctrine@2.1.0: 727 resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} 728 engines: {node: '>=0.10.0'} 729 730 + dunder-proto@1.0.1: 731 + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 732 + engines: {node: '>= 0.4'} 733 734 + es-abstract@1.23.9: 735 + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} 736 engines: {node: '>= 0.4'} 737 738 + es-define-property@1.0.1: 739 + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 740 + engines: {node: '>= 0.4'} 741 742 + es-errors@1.3.0: 743 + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 744 engines: {node: '>= 0.4'} 745 746 + es-iterator-helpers@1.2.1: 747 + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} 748 + engines: {node: '>= 0.4'} 749 750 + es-object-atoms@1.1.1: 751 + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 752 + engines: {node: '>= 0.4'} 753 + 754 + es-set-tostringtag@2.1.0: 755 + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} 756 + engines: {node: '>= 0.4'} 757 + 758 + es-shim-unscopables@1.1.0: 759 + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} 760 + engines: {node: '>= 0.4'} 761 + 762 + es-to-primitive@1.3.0: 763 + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} 764 engines: {node: '>= 0.4'} 765 766 esbuild-copy-static-files@0.1.0: ··· 781 peerDependencies: 782 eslint: '>=7.0.0' 783 784 + eslint-plugin-prettier@5.2.6: 785 + resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==} 786 engines: {node: ^14.18.0 || >=16.0.0} 787 peerDependencies: 788 '@types/eslint': '>=8.0.0' 789 eslint: '>=8.0.0' 790 + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' 791 prettier: '>=3.0.0' 792 peerDependenciesMeta: 793 '@types/eslint': ··· 795 eslint-config-prettier: 796 optional: true 797 798 + eslint-plugin-react@7.37.5: 799 + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} 800 engines: {node: '>=4'} 801 peerDependencies: 802 + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 803 804 + eslint-scope@8.3.0: 805 + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} 806 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 807 808 eslint-visitor-keys@3.4.3: 809 resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 810 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 811 812 + eslint-visitor-keys@4.2.0: 813 + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} 814 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 815 + 816 + eslint@9.23.0: 817 + resolution: {integrity: sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==} 818 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 819 hasBin: true 820 + peerDependencies: 821 + jiti: '*' 822 + peerDependenciesMeta: 823 + jiti: 824 + optional: true 825 826 + espree@10.3.0: 827 + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} 828 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 829 830 + esquery@1.6.0: 831 + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 832 engines: {node: '>=0.10'} 833 834 esrecurse@4.3.0: ··· 857 resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 858 engines: {node: '>=0.8.x'} 859 860 fast-deep-equal@3.1.3: 861 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 862 ··· 873 fast-levenshtein@2.0.6: 874 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 875 876 + fastq@1.17.1: 877 + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} 878 879 + fdir@6.4.3: 880 + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} 881 + peerDependencies: 882 + picomatch: ^3 || ^4 883 + peerDependenciesMeta: 884 + picomatch: 885 + optional: true 886 + 887 + file-entry-cache@8.0.0: 888 + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 889 + engines: {node: '>=16.0.0'} 890 891 + fill-range@7.1.1: 892 + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 893 engines: {node: '>=8'} 894 895 + find-up-simple@1.0.1: 896 + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} 897 + engines: {node: '>=18'} 898 + 899 find-up@5.0.0: 900 resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 901 engines: {node: '>=10'} 902 903 + flat-cache@4.0.1: 904 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 905 + engines: {node: '>=16'} 906 907 flatted@3.2.9: 908 resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} 909 910 + for-each@0.3.5: 911 + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} 912 + engines: {node: '>= 0.4'} 913 914 function-bind@1.1.2: 915 resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 916 917 + function.prototype.name@1.1.8: 918 + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} 919 engines: {node: '>= 0.4'} 920 921 functions-have-names@1.2.3: 922 resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} 923 924 + fzf@0.5.2: 925 + resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==} 926 + 927 + get-intrinsic@1.3.0: 928 + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 929 + engines: {node: '>= 0.4'} 930 931 + get-proto@1.0.1: 932 + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 933 + engines: {node: '>= 0.4'} 934 935 + get-symbol-description@1.1.0: 936 + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} 937 engines: {node: '>= 0.4'} 938 939 glob-parent@5.1.2: ··· 944 resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 945 engines: {node: '>=10.13.0'} 946 947 + globals@14.0.0: 948 + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 949 + engines: {node: '>=18'} 950 951 + globalthis@1.0.4: 952 + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} 953 engines: {node: '>= 0.4'} 954 955 + gopd@1.2.0: 956 + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 957 + engines: {node: '>= 0.4'} 958 959 graphemer@1.4.0: 960 resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 961 962 + has-bigints@1.1.0: 963 + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} 964 + engines: {node: '>= 0.4'} 965 966 has-flag@4.0.0: 967 resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 968 engines: {node: '>=8'} 969 970 + has-property-descriptors@1.0.2: 971 + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} 972 973 + has-proto@1.2.0: 974 + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} 975 engines: {node: '>= 0.4'} 976 977 + has-symbols@1.1.0: 978 + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 979 engines: {node: '>= 0.4'} 980 981 + has-tostringtag@1.0.2: 982 + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 983 engines: {node: '>= 0.4'} 984 985 + hasown@2.0.2: 986 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 987 engines: {node: '>= 0.4'} 988 989 husky@8.0.3: 990 resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} 991 engines: {node: '>=14'} ··· 994 ieee754@1.2.1: 995 resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 996 997 + ignore@5.3.2: 998 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 999 engines: {node: '>= 4'} 1000 1001 import-fresh@3.3.0: ··· 1006 resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 1007 engines: {node: '>=0.8.19'} 1008 1009 + internal-slot@1.1.0: 1010 + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} 1011 engines: {node: '>= 0.4'} 1012 1013 + is-array-buffer@3.0.5: 1014 + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} 1015 + engines: {node: '>= 0.4'} 1016 1017 + is-async-function@2.1.1: 1018 + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} 1019 engines: {node: '>= 0.4'} 1020 1021 + is-bigint@1.1.0: 1022 + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} 1023 + engines: {node: '>= 0.4'} 1024 1025 + is-boolean-object@1.2.2: 1026 + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} 1027 engines: {node: '>= 0.4'} 1028 1029 is-callable@1.2.7: 1030 resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 1031 engines: {node: '>= 0.4'} 1032 1033 + is-core-module@2.16.1: 1034 + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} 1035 + engines: {node: '>= 0.4'} 1036 1037 + is-data-view@1.0.2: 1038 + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} 1039 engines: {node: '>= 0.4'} 1040 1041 + is-date-object@1.1.0: 1042 + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} 1043 + engines: {node: '>= 0.4'} 1044 1045 is-extglob@2.1.1: 1046 resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1047 engines: {node: '>=0.10.0'} 1048 1049 + is-finalizationregistry@1.1.1: 1050 + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} 1051 + engines: {node: '>= 0.4'} 1052 1053 + is-generator-function@1.1.0: 1054 + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} 1055 engines: {node: '>= 0.4'} 1056 1057 is-glob@4.0.3: 1058 resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1059 engines: {node: '>=0.10.0'} 1060 1061 + is-map@2.0.3: 1062 + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} 1063 engines: {node: '>= 0.4'} 1064 1065 + is-number-object@1.1.1: 1066 + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} 1067 engines: {node: '>= 0.4'} 1068 1069 is-number@7.0.0: 1070 resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 1071 engines: {node: '>=0.12.0'} 1072 1073 + is-regex@1.2.1: 1074 + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} 1075 + engines: {node: '>= 0.4'} 1076 1077 + is-set@2.0.3: 1078 + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} 1079 engines: {node: '>= 0.4'} 1080 1081 + is-shared-array-buffer@1.0.4: 1082 + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} 1083 + engines: {node: '>= 0.4'} 1084 1085 + is-string@1.1.1: 1086 + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} 1087 + engines: {node: '>= 0.4'} 1088 1089 + is-symbol@1.1.1: 1090 + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} 1091 + engines: {node: '>= 0.4'} 1092 1093 + is-typed-array@1.1.15: 1094 + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} 1095 + engines: {node: '>= 0.4'} 1096 1097 + is-weakmap@2.0.2: 1098 + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} 1099 engines: {node: '>= 0.4'} 1100 1101 + is-weakref@1.1.1: 1102 + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} 1103 engines: {node: '>= 0.4'} 1104 1105 + is-weakset@2.0.4: 1106 + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} 1107 engines: {node: '>= 0.4'} 1108 1109 isarray@2.0.5: 1110 resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} 1111 1112 isexe@2.0.0: 1113 resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1114 1115 + iterator.prototype@1.1.5: 1116 + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} 1117 + engines: {node: '>= 0.4'} 1118 + 1119 + jiti@2.4.2: 1120 + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} 1121 + hasBin: true 1122 1123 js-tokens@4.0.0: 1124 resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} ··· 1158 resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 1159 hasBin: true 1160 1161 + math-intrinsics@1.1.0: 1162 + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 1163 + engines: {node: '>= 0.4'} 1164 1165 merge2@1.4.1: 1166 resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} ··· 1170 resolution: {integrity: sha512-OyvYIOgpzXREySYJ1cqEb2pOKdeQMTfF9M8dRU6nC4hi/GXMmNpe9ssZCrSoTHazu05BSAoRBN/uYeco+ymfOg==} 1171 engines: {node: '>=18.0.0'} 1172 1173 + microdiff@1.5.0: 1174 + resolution: {integrity: sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==} 1175 1176 + micromatch@4.0.8: 1177 + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 1178 + engines: {node: '>=8.6'} 1179 1180 + mimic-function@5.0.1: 1181 + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} 1182 + engines: {node: '>=18'} 1183 1184 minimatch@3.1.2: 1185 resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} ··· 1188 resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1189 engines: {node: '>=16 || 14 >=14.17'} 1190 1191 + ms@2.1.3: 1192 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1193 1194 nanotar@0.1.1: 1195 resolution: {integrity: sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ==} ··· 1197 natural-compare@1.4.0: 1198 resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1199 1200 + node-fetch-native@1.6.6: 1201 + resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} 1202 1203 object-assign@4.1.1: 1204 resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 1205 engines: {node: '>=0.10.0'} 1206 1207 + object-inspect@1.13.4: 1208 + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 1209 + engines: {node: '>= 0.4'} 1210 1211 object-keys@1.1.1: 1212 resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 1213 engines: {node: '>= 0.4'} 1214 1215 + object.assign@4.1.7: 1216 + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} 1217 engines: {node: '>= 0.4'} 1218 1219 + object.entries@1.1.9: 1220 + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} 1221 engines: {node: '>= 0.4'} 1222 1223 + object.fromentries@2.0.8: 1224 + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} 1225 engines: {node: '>= 0.4'} 1226 1227 + object.values@1.2.1: 1228 + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} 1229 engines: {node: '>= 0.4'} 1230 1231 + ofetch@1.4.1: 1232 + resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} 1233 1234 + onetime@7.0.0: 1235 + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} 1236 + engines: {node: '>=18'} 1237 1238 optionator@0.9.3: 1239 resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1240 engines: {node: '>= 0.8.0'} 1241 1242 + own-keys@1.0.1: 1243 + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} 1244 + engines: {node: '>= 0.4'} 1245 + 1246 p-limit@3.1.0: 1247 resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1248 engines: {node: '>=10'} ··· 1251 resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1252 engines: {node: '>=10'} 1253 1254 + package-manager-detector@1.1.0: 1255 + resolution: {integrity: sha512-Y8f9qUlBzW8qauJjd/eu6jlpJZsuPJm2ZAV0cDVd420o4EdpH5RPdoCv+60/TdJflGatr4sDfpAL6ArWZbM5tA==} 1256 + 1257 parent-module@1.0.1: 1258 resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1259 engines: {node: '>=6'} ··· 1261 path-exists@4.0.0: 1262 resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1263 engines: {node: '>=8'} 1264 1265 path-key@3.1.1: 1266 resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1267 engines: {node: '>=8'} 1268 1269 path-parse@1.0.7: 1270 resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1271 1272 + pathe@2.0.3: 1273 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 1274 1275 picomatch@2.3.1: 1276 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1277 engines: {node: '>=8.6'} 1278 + 1279 + picomatch@4.0.2: 1280 + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 1281 + engines: {node: '>=12'} 1282 + 1283 + pnpm-workspace-yaml@0.3.1: 1284 + resolution: {integrity: sha512-3nW5RLmREmZ8Pm8MbPsO2RM+99RRjYd25ynj3NV0cFsN7CcEl4sDFzgoFmSyduFwxFQ2Qbu3y2UdCh6HlyUOeA==} 1285 + 1286 + possible-typed-array-names@1.1.0: 1287 + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} 1288 + engines: {node: '>= 0.4'} 1289 1290 prelude-ls@1.2.1: 1291 resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} ··· 1310 punycode@2.3.1: 1311 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1312 engines: {node: '>=6'} 1313 + 1314 + quansync@0.2.10: 1315 + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} 1316 1317 queue-microtask@1.2.3: 1318 resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} ··· 1324 resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} 1325 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1326 1327 + reflect.getprototypeof@1.0.10: 1328 + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} 1329 engines: {node: '>= 0.4'} 1330 1331 + regexp.prototype.flags@1.5.4: 1332 + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} 1333 engines: {node: '>= 0.4'} 1334 1335 resolve-from@4.0.0: ··· 1340 resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} 1341 hasBin: true 1342 1343 + restore-cursor@5.1.0: 1344 + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} 1345 + engines: {node: '>=18'} 1346 + 1347 reusify@1.0.4: 1348 resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1349 engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1350 1351 run-parallel@1.2.0: 1352 resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1353 1354 + safe-array-concat@1.1.3: 1355 + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} 1356 engines: {node: '>=0.4'} 1357 1358 safe-buffer@5.2.1: 1359 resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1360 1361 + safe-push-apply@1.0.0: 1362 + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} 1363 + engines: {node: '>= 0.4'} 1364 + 1365 + safe-regex-test@1.1.0: 1366 + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} 1367 + engines: {node: '>= 0.4'} 1368 1369 semver@6.3.1: 1370 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1371 hasBin: true 1372 1373 + semver@7.7.1: 1374 + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} 1375 engines: {node: '>=10'} 1376 hasBin: true 1377 1378 + set-function-length@1.2.2: 1379 + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} 1380 + engines: {node: '>= 0.4'} 1381 + 1382 + set-function-name@2.0.2: 1383 + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} 1384 engines: {node: '>= 0.4'} 1385 1386 + set-proto@1.0.0: 1387 + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} 1388 engines: {node: '>= 0.4'} 1389 1390 shebang-command@2.0.0: ··· 1395 resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1396 engines: {node: '>=8'} 1397 1398 + side-channel-list@1.0.0: 1399 + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} 1400 + engines: {node: '>= 0.4'} 1401 1402 + side-channel-map@1.0.1: 1403 + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} 1404 + engines: {node: '>= 0.4'} 1405 1406 + side-channel-weakmap@1.0.2: 1407 + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} 1408 + engines: {node: '>= 0.4'} 1409 + 1410 + side-channel@1.1.0: 1411 + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} 1412 + engines: {node: '>= 0.4'} 1413 + 1414 + signal-exit@4.1.0: 1415 + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 1416 + engines: {node: '>=14'} 1417 1418 standalone-electron-types@1.0.0: 1419 resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} 1420 1421 + string.prototype.matchall@4.0.12: 1422 + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} 1423 + engines: {node: '>= 0.4'} 1424 1425 + string.prototype.repeat@1.0.0: 1426 + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} 1427 + 1428 + string.prototype.trim@1.2.10: 1429 + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} 1430 engines: {node: '>= 0.4'} 1431 1432 + string.prototype.trimend@1.0.9: 1433 + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} 1434 + engines: {node: '>= 0.4'} 1435 1436 + string.prototype.trimstart@1.0.8: 1437 + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} 1438 + engines: {node: '>= 0.4'} 1439 1440 string_decoder@1.3.0: 1441 resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1442 1443 strip-json-comments@3.1.1: 1444 resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1445 engines: {node: '>=8'} ··· 1452 resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1453 engines: {node: '>= 0.4'} 1454 1455 + synckit@0.11.1: 1456 + resolution: {integrity: sha512-fWZqNBZNNFp/7mTUy1fSsydhKsAKJ+u90Nk7kOK5Gcq9vObaqLBLjWFDBkyVU9Vvc6Y71VbOevMuGhqv02bT+Q==} 1457 engines: {node: ^14.18.0 || >=16.0.0} 1458 1459 + taze@19.0.4: 1460 + resolution: {integrity: sha512-bviyNotzqcIWpVBCC4QYVb2yupzKyUDGQi2m/8GERdiPaudVMtgAqaE98+x0cDDaByYRMJCyhQWM04ikUL6+kQ==} 1461 + hasBin: true 1462 1463 + tinyexec@1.0.1: 1464 + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} 1465 + 1466 + tinyglobby@0.2.12: 1467 + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} 1468 + engines: {node: '>=12.0.0'} 1469 1470 to-regex-range@5.0.1: 1471 resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1472 engines: {node: '>=8.0'} 1473 1474 + ts-api-utils@2.1.0: 1475 + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} 1476 + engines: {node: '>=18.12'} 1477 peerDependencies: 1478 + typescript: '>=4.8.4' 1479 1480 + tslib@2.8.1: 1481 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1482 1483 type-check@0.4.0: 1484 resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1485 engines: {node: '>= 0.8.0'} 1486 1487 + typed-array-buffer@1.0.3: 1488 + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} 1489 + engines: {node: '>= 0.4'} 1490 1491 + typed-array-byte-length@1.0.3: 1492 + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} 1493 engines: {node: '>= 0.4'} 1494 1495 + typed-array-byte-offset@1.0.4: 1496 + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} 1497 engines: {node: '>= 0.4'} 1498 1499 + typed-array-length@1.0.7: 1500 + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} 1501 engines: {node: '>= 0.4'} 1502 1503 + typescript-eslint@8.29.0: 1504 + resolution: {integrity: sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==} 1505 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1506 + peerDependencies: 1507 + eslint: ^8.57.0 || ^9.0.0 1508 + typescript: '>=4.8.4 <5.9.0' 1509 1510 + typescript@5.8.2: 1511 + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} 1512 engines: {node: '>=14.17'} 1513 hasBin: true 1514 1515 + ufo@1.5.4: 1516 + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} 1517 + 1518 + unbox-primitive@1.1.0: 1519 + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} 1520 + engines: {node: '>= 0.4'} 1521 1522 + unconfig@7.3.1: 1523 + resolution: {integrity: sha512-LH5WL+un92tGAzWS87k7LkAfwpMdm7V0IXG2FxEjZz/QxiIW5J5LkcrKQThj0aRz6+h/lFmKI9EUXmK/T0bcrw==} 1524 1525 + undici-types@6.20.0: 1526 + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 1527 + 1528 + undici-types@6.21.0: 1529 + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 1530 1531 uri-js@4.4.1: 1532 resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1533 1534 + utilium@1.10.1: 1535 + resolution: {integrity: sha512-GQINDTb/ocyz4acQj3GXAe0wipYxws6L+9ouqaq10KlInTk9DGvW9TJd0pYa/Xu3cppNnZuB4T/sBuSXpcN2ng==} 1536 1537 + which-boxed-primitive@1.1.1: 1538 + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} 1539 + engines: {node: '>= 0.4'} 1540 1541 + which-builtin-type@1.2.1: 1542 + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} 1543 engines: {node: '>= 0.4'} 1544 1545 + which-collection@1.0.2: 1546 + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} 1547 + engines: {node: '>= 0.4'} 1548 1549 + which-typed-array@1.1.19: 1550 + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} 1551 engines: {node: '>= 0.4'} 1552 1553 which@2.0.2: ··· 1555 engines: {node: '>= 8'} 1556 hasBin: true 1557 1558 + yaml@2.7.1: 1559 + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} 1560 + engines: {node: '>= 14'} 1561 + hasBin: true 1562 1563 yocto-queue@0.1.0: 1564 resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1565 engines: {node: '>=10'} 1566 1567 + zustand@5.0.3: 1568 + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} 1569 + engines: {node: '>=12.20.0'} 1570 + peerDependencies: 1571 + '@types/react': '>=18.0.0' 1572 + immer: '>=9.0.6' 1573 + react: '>=18.0.0' 1574 + use-sync-external-store: '>=1.2.0' 1575 + peerDependenciesMeta: 1576 + '@types/react': 1577 + optional: true 1578 + immer: 1579 + optional: true 1580 + react: 1581 + optional: true 1582 + use-sync-external-store: 1583 + optional: true 1584 + 1585 snapshots: 1586 1587 '@aashutoshrathi/word-wrap@1.2.6': {} 1588 + 1589 + '@antfu/ni@24.3.0': 1590 + dependencies: 1591 + ansis: 3.17.0 1592 + fzf: 0.5.2 1593 + package-manager-detector: 1.1.0 1594 + tinyexec: 1.0.1 1595 1596 '@esbuild/android-arm64@0.19.3': 1597 optional: true ··· 1659 '@esbuild/win32-x64@0.19.3': 1660 optional: true 1661 1662 + '@eslint-community/eslint-utils@4.5.1(eslint@9.23.0(jiti@2.4.2))': 1663 dependencies: 1664 + eslint: 9.23.0(jiti@2.4.2) 1665 eslint-visitor-keys: 3.4.3 1666 1667 + '@eslint-community/regexpp@4.12.1': {} 1668 1669 + '@eslint/config-array@0.19.2': 1670 + dependencies: 1671 + '@eslint/object-schema': 2.1.6 1672 + debug: 4.4.0 1673 + minimatch: 3.1.2 1674 + transitivePeerDependencies: 1675 + - supports-color 1676 + 1677 + '@eslint/config-helpers@0.2.1': {} 1678 + 1679 + '@eslint/core@0.12.0': 1680 + dependencies: 1681 + '@types/json-schema': 7.0.15 1682 + 1683 + '@eslint/core@0.13.0': 1684 + dependencies: 1685 + '@types/json-schema': 7.0.15 1686 + 1687 + '@eslint/eslintrc@3.3.1': 1688 dependencies: 1689 ajv: 6.12.6 1690 + debug: 4.4.0 1691 + espree: 10.3.0 1692 + globals: 14.0.0 1693 + ignore: 5.3.2 1694 import-fresh: 3.3.0 1695 js-yaml: 4.1.0 1696 minimatch: 3.1.2 ··· 1698 transitivePeerDependencies: 1699 - supports-color 1700 1701 + '@eslint/js@9.23.0': {} 1702 + 1703 + '@eslint/object-schema@2.1.6': {} 1704 1705 + '@eslint/plugin-kit@0.2.8': 1706 dependencies: 1707 + '@eslint/core': 0.13.0 1708 + levn: 0.4.1 1709 + 1710 + '@humanfs/core@0.19.1': {} 1711 + 1712 + '@humanfs/node@0.16.6': 1713 + dependencies: 1714 + '@humanfs/core': 0.19.1 1715 + '@humanwhocodes/retry': 0.3.1 1716 1717 '@humanwhocodes/module-importer@1.0.1': {} 1718 1719 + '@humanwhocodes/retry@0.3.1': {} 1720 + 1721 + '@humanwhocodes/retry@0.4.2': {} 1722 1723 + '@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9(@types/eslint@9.6.1)(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0)(typescript@5.8.2)': 1724 + dependencies: 1725 + '@eslint/js': 9.23.0 1726 + eslint: 9.23.0(jiti@2.4.2) 1727 + eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@2.4.2)) 1728 + eslint-plugin-prettier: 5.2.6(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)))(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0) 1729 + eslint-plugin-react: 7.37.5(eslint@9.23.0(jiti@2.4.2)) 1730 + typescript: 5.8.2 1731 + typescript-eslint: 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) 1732 + transitivePeerDependencies: 1733 + - '@types/eslint' 1734 + - prettier 1735 + - supports-color 1736 + 1737 + '@moonlight-mod/lunast@1.0.1': 1738 dependencies: 1739 astring: 1.9.0 1740 estree-toolkit: 1.7.8 1741 meriyah: 6.0.1 1742 1743 + '@moonlight-mod/mappings@1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)': 1744 dependencies: 1745 + '@moonlight-mod/lunast': 1.0.1 1746 + '@moonlight-mod/moonmap': 1.0.5 1747 + '@types/chroma-js': 3.1.0 1748 '@types/flux': 3.1.14 1749 + '@types/highlightjs': 9.12.6 1750 + '@types/lodash': 4.17.14 1751 + '@types/platform': 1.3.6 1752 + '@types/react': 18.3.20 1753 csstype: 3.1.3 1754 + zustand: 5.0.3(@types/react@18.3.20) 1755 + transitivePeerDependencies: 1756 + - immer 1757 + - react 1758 + - use-sync-external-store 1759 1760 + '@moonlight-mod/moonmap@1.0.5': {} 1761 1762 '@nodelib/fs.scandir@2.1.5': 1763 dependencies: ··· 1769 '@nodelib/fs.walk@1.2.8': 1770 dependencies: 1771 '@nodelib/fs.scandir': 2.1.5 1772 + fastq: 1.17.1 1773 + 1774 + '@pkgr/core@0.2.0': {} 1775 1776 + '@quansync/fs@0.1.2': 1777 dependencies: 1778 + quansync: 0.2.10 1779 + 1780 + '@types/chroma-js@3.1.0': {} 1781 + 1782 + '@types/chrome@0.0.313': 1783 + dependencies: 1784 + '@types/filesystem': 0.0.36 1785 + '@types/har-format': 1.2.16 1786 + 1787 + '@types/eslint@9.6.1': 1788 + dependencies: 1789 + '@types/estree': 1.0.7 1790 + '@types/json-schema': 7.0.15 1791 + optional: true 1792 1793 '@types/estree-jsx@1.0.5': 1794 dependencies: ··· 1796 1797 '@types/estree@1.0.6': {} 1798 1799 + '@types/estree@1.0.7': 1800 + optional: true 1801 + 1802 '@types/fbemitter@2.0.35': {} 1803 1804 + '@types/filesystem@0.0.36': 1805 + dependencies: 1806 + '@types/filewriter': 0.0.33 1807 + 1808 + '@types/filewriter@0.0.33': {} 1809 + 1810 '@types/flux@3.1.14': 1811 dependencies: 1812 '@types/fbemitter': 2.0.35 1813 + '@types/react': 18.3.20 1814 + 1815 + '@types/har-format@1.2.16': {} 1816 + 1817 + '@types/highlightjs@9.12.6': {} 1818 1819 '@types/json-schema@7.0.15': {} 1820 1821 + '@types/lodash@4.17.14': {} 1822 + 1823 '@types/node@18.17.17': {} 1824 1825 + '@types/node@22.13.6': 1826 + dependencies: 1827 + undici-types: 6.20.0 1828 + 1829 + '@types/node@22.14.0': 1830 dependencies: 1831 + undici-types: 6.21.0 1832 + 1833 + '@types/platform@1.3.6': {} 1834 1835 '@types/prop-types@15.7.13': {} 1836 1837 + '@types/react@18.3.20': 1838 dependencies: 1839 '@types/prop-types': 15.7.13 1840 csstype: 3.1.3 1841 1842 + '@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 1843 dependencies: 1844 + '@eslint-community/regexpp': 4.12.1 1845 + '@typescript-eslint/parser': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) 1846 + '@typescript-eslint/scope-manager': 8.29.0 1847 + '@typescript-eslint/type-utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) 1848 + '@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) 1849 + '@typescript-eslint/visitor-keys': 8.29.0 1850 + eslint: 9.23.0(jiti@2.4.2) 1851 graphemer: 1.4.0 1852 + ignore: 5.3.2 1853 natural-compare: 1.4.0 1854 + ts-api-utils: 2.1.0(typescript@5.8.2) 1855 + typescript: 5.8.2 1856 transitivePeerDependencies: 1857 - supports-color 1858 1859 + '@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 1860 dependencies: 1861 + '@typescript-eslint/scope-manager': 8.29.0 1862 + '@typescript-eslint/types': 8.29.0 1863 + '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2) 1864 + '@typescript-eslint/visitor-keys': 8.29.0 1865 + debug: 4.4.0 1866 + eslint: 9.23.0(jiti@2.4.2) 1867 + typescript: 5.8.2 1868 transitivePeerDependencies: 1869 - supports-color 1870 1871 + '@typescript-eslint/scope-manager@8.29.0': 1872 dependencies: 1873 + '@typescript-eslint/types': 8.29.0 1874 + '@typescript-eslint/visitor-keys': 8.29.0 1875 1876 + '@typescript-eslint/type-utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 1877 dependencies: 1878 + '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2) 1879 + '@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) 1880 + debug: 4.4.0 1881 + eslint: 9.23.0(jiti@2.4.2) 1882 + ts-api-utils: 2.1.0(typescript@5.8.2) 1883 + typescript: 5.8.2 1884 transitivePeerDependencies: 1885 - supports-color 1886 1887 + '@typescript-eslint/types@8.29.0': {} 1888 1889 + '@typescript-eslint/typescript-estree@8.29.0(typescript@5.8.2)': 1890 dependencies: 1891 + '@typescript-eslint/types': 8.29.0 1892 + '@typescript-eslint/visitor-keys': 8.29.0 1893 + debug: 4.4.0 1894 + fast-glob: 3.3.2 1895 is-glob: 4.0.3 1896 + minimatch: 9.0.5 1897 + semver: 7.7.1 1898 + ts-api-utils: 2.1.0(typescript@5.8.2) 1899 + typescript: 5.8.2 1900 transitivePeerDependencies: 1901 - supports-color 1902 1903 + '@typescript-eslint/utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 1904 dependencies: 1905 + '@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2)) 1906 + '@typescript-eslint/scope-manager': 8.29.0 1907 + '@typescript-eslint/types': 8.29.0 1908 + '@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2) 1909 + eslint: 9.23.0(jiti@2.4.2) 1910 + typescript: 5.8.2 1911 transitivePeerDependencies: 1912 - supports-color 1913 1914 + '@typescript-eslint/visitor-keys@8.29.0': 1915 dependencies: 1916 + '@typescript-eslint/types': 8.29.0 1917 + eslint-visitor-keys: 4.2.0 1918 1919 + '@xterm/xterm@5.5.0': 1920 + optional: true 1921 1922 + '@zenfs/core@2.0.0': 1923 dependencies: 1924 + '@types/node': 22.13.6 1925 buffer: 6.0.3 1926 eventemitter3: 5.0.1 1927 readable-stream: 4.5.2 1928 + utilium: 1.10.1 1929 1930 + '@zenfs/dom@1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1)': 1931 dependencies: 1932 + '@zenfs/core': 2.0.0 1933 + utilium: 1.10.1 1934 1935 abort-controller@3.0.0: 1936 dependencies: 1937 event-target-shim: 5.0.1 1938 1939 + acorn-jsx@5.3.2(acorn@8.14.1): 1940 dependencies: 1941 + acorn: 8.14.1 1942 1943 + acorn@8.14.1: {} 1944 1945 ajv@6.12.6: 1946 dependencies: ··· 1948 fast-json-stable-stringify: 2.1.0 1949 json-schema-traverse: 0.4.1 1950 uri-js: 4.4.1 1951 1952 ansi-styles@4.3.0: 1953 dependencies: 1954 color-convert: 2.0.1 1955 1956 + ansis@3.17.0: {} 1957 + 1958 argparse@2.0.1: {} 1959 1960 + array-buffer-byte-length@1.0.2: 1961 dependencies: 1962 + call-bound: 1.0.4 1963 + is-array-buffer: 3.0.5 1964 1965 + array-includes@3.1.8: 1966 dependencies: 1967 + call-bind: 1.0.8 1968 define-properties: 1.2.1 1969 + es-abstract: 1.23.9 1970 + es-object-atoms: 1.1.1 1971 + get-intrinsic: 1.3.0 1972 + is-string: 1.1.1 1973 1974 + array.prototype.findlast@1.2.5: 1975 + dependencies: 1976 + call-bind: 1.0.8 1977 + define-properties: 1.2.1 1978 + es-abstract: 1.23.9 1979 + es-errors: 1.3.0 1980 + es-object-atoms: 1.1.1 1981 + es-shim-unscopables: 1.1.0 1982 1983 + array.prototype.flat@1.3.3: 1984 dependencies: 1985 + call-bind: 1.0.8 1986 define-properties: 1.2.1 1987 + es-abstract: 1.23.9 1988 + es-shim-unscopables: 1.1.0 1989 1990 + array.prototype.flatmap@1.3.3: 1991 dependencies: 1992 + call-bind: 1.0.8 1993 define-properties: 1.2.1 1994 + es-abstract: 1.23.9 1995 + es-shim-unscopables: 1.1.0 1996 1997 + array.prototype.tosorted@1.1.4: 1998 dependencies: 1999 + call-bind: 1.0.8 2000 define-properties: 1.2.1 2001 + es-abstract: 1.23.9 2002 + es-errors: 1.3.0 2003 + es-shim-unscopables: 1.1.0 2004 2005 + arraybuffer.prototype.slice@1.0.4: 2006 dependencies: 2007 + array-buffer-byte-length: 1.0.2 2008 + call-bind: 1.0.8 2009 define-properties: 1.2.1 2010 + es-abstract: 1.23.9 2011 + es-errors: 1.3.0 2012 + get-intrinsic: 1.3.0 2013 + is-array-buffer: 3.0.5 2014 2015 astring@1.9.0: {} 2016 2017 + async-function@1.0.0: {} 2018 2019 + available-typed-arrays@1.0.7: 2020 + dependencies: 2021 + possible-typed-array-names: 1.1.0 2022 2023 balanced-match@1.0.2: {} 2024 2025 base64-js@1.5.1: {} 2026 2027 brace-expansion@1.1.11: 2028 dependencies: 2029 balanced-match: 1.0.2 ··· 2033 dependencies: 2034 balanced-match: 1.0.2 2035 2036 + braces@3.0.3: 2037 dependencies: 2038 + fill-range: 7.1.1 2039 2040 buffer@6.0.3: 2041 dependencies: 2042 base64-js: 1.5.1 2043 ieee754: 1.2.1 2044 2045 + cac@6.7.14: {} 2046 2047 + call-bind-apply-helpers@1.0.2: 2048 dependencies: 2049 + es-errors: 1.3.0 2050 function-bind: 1.1.2 2051 + 2052 + call-bind@1.0.8: 2053 + dependencies: 2054 + call-bind-apply-helpers: 1.0.2 2055 + es-define-property: 1.0.1 2056 + get-intrinsic: 1.3.0 2057 + set-function-length: 1.2.2 2058 + 2059 + call-bound@1.0.4: 2060 + dependencies: 2061 + call-bind-apply-helpers: 1.0.2 2062 + get-intrinsic: 1.3.0 2063 2064 callsites@3.1.0: {} 2065 ··· 2076 2077 concat-map@0.0.1: {} 2078 2079 + cross-spawn@7.0.6: 2080 dependencies: 2081 path-key: 3.1.1 2082 shebang-command: 2.0.0 2083 which: 2.0.2 2084 2085 csstype@3.1.3: {} 2086 2087 + data-view-buffer@1.0.2: 2088 dependencies: 2089 + call-bound: 1.0.4 2090 + es-errors: 1.3.0 2091 + is-data-view: 1.0.2 2092 2093 + data-view-byte-length@1.0.2: 2094 dependencies: 2095 + call-bound: 1.0.4 2096 + es-errors: 1.3.0 2097 + is-data-view: 1.0.2 2098 2099 + data-view-byte-offset@1.0.1: 2100 dependencies: 2101 + call-bound: 1.0.4 2102 + es-errors: 1.3.0 2103 + is-data-view: 1.0.2 2104 2105 + debug@4.4.0: 2106 dependencies: 2107 + ms: 2.1.3 2108 2109 + deep-is@0.1.4: {} 2110 + 2111 + define-data-property@1.1.4: 2112 + dependencies: 2113 + es-define-property: 1.0.1 2114 + es-errors: 1.3.0 2115 + gopd: 1.2.0 2116 2117 define-properties@1.2.1: 2118 dependencies: 2119 + define-data-property: 1.1.4 2120 + has-property-descriptors: 1.0.2 2121 object-keys: 1.1.1 2122 2123 + defu@6.1.4: {} 2124 + 2125 + destr@2.0.4: {} 2126 2127 doctrine@2.1.0: 2128 dependencies: 2129 esutils: 2.0.3 2130 2131 + dunder-proto@1.0.1: 2132 dependencies: 2133 + call-bind-apply-helpers: 1.0.2 2134 + es-errors: 1.3.0 2135 + gopd: 1.2.0 2136 2137 + es-abstract@1.23.9: 2138 dependencies: 2139 + array-buffer-byte-length: 1.0.2 2140 + arraybuffer.prototype.slice: 1.0.4 2141 + available-typed-arrays: 1.0.7 2142 + call-bind: 1.0.8 2143 + call-bound: 1.0.4 2144 + data-view-buffer: 1.0.2 2145 + data-view-byte-length: 1.0.2 2146 + data-view-byte-offset: 1.0.1 2147 + es-define-property: 1.0.1 2148 + es-errors: 1.3.0 2149 + es-object-atoms: 1.1.1 2150 + es-set-tostringtag: 2.1.0 2151 + es-to-primitive: 1.3.0 2152 + function.prototype.name: 1.1.8 2153 + get-intrinsic: 1.3.0 2154 + get-proto: 1.0.1 2155 + get-symbol-description: 1.1.0 2156 + globalthis: 1.0.4 2157 + gopd: 1.2.0 2158 + has-property-descriptors: 1.0.2 2159 + has-proto: 1.2.0 2160 + has-symbols: 1.1.0 2161 + hasown: 2.0.2 2162 + internal-slot: 1.1.0 2163 + is-array-buffer: 3.0.5 2164 is-callable: 1.2.7 2165 + is-data-view: 1.0.2 2166 + is-regex: 1.2.1 2167 + is-shared-array-buffer: 1.0.4 2168 + is-string: 1.1.1 2169 + is-typed-array: 1.1.15 2170 + is-weakref: 1.1.1 2171 + math-intrinsics: 1.1.0 2172 + object-inspect: 1.13.4 2173 object-keys: 1.1.1 2174 + object.assign: 4.1.7 2175 + own-keys: 1.0.1 2176 + regexp.prototype.flags: 1.5.4 2177 + safe-array-concat: 1.1.3 2178 + safe-push-apply: 1.0.0 2179 + safe-regex-test: 1.1.0 2180 + set-proto: 1.0.0 2181 + string.prototype.trim: 1.2.10 2182 + string.prototype.trimend: 1.0.9 2183 + string.prototype.trimstart: 1.0.8 2184 + typed-array-buffer: 1.0.3 2185 + typed-array-byte-length: 1.0.3 2186 + typed-array-byte-offset: 1.0.4 2187 + typed-array-length: 1.0.7 2188 + unbox-primitive: 1.1.0 2189 + which-typed-array: 1.1.19 2190 + 2191 + es-define-property@1.0.1: {} 2192 + 2193 + es-errors@1.3.0: {} 2194 2195 + es-iterator-helpers@1.2.1: 2196 dependencies: 2197 + call-bind: 1.0.8 2198 + call-bound: 1.0.4 2199 define-properties: 1.2.1 2200 + es-abstract: 1.23.9 2201 + es-errors: 1.3.0 2202 + es-set-tostringtag: 2.1.0 2203 function-bind: 1.1.2 2204 + get-intrinsic: 1.3.0 2205 + globalthis: 1.0.4 2206 + gopd: 1.2.0 2207 + has-property-descriptors: 1.0.2 2208 + has-proto: 1.2.0 2209 + has-symbols: 1.1.0 2210 + internal-slot: 1.1.0 2211 + iterator.prototype: 1.1.5 2212 + safe-array-concat: 1.1.3 2213 2214 + es-object-atoms@1.1.1: 2215 dependencies: 2216 + es-errors: 1.3.0 2217 2218 + es-set-tostringtag@2.1.0: 2219 + dependencies: 2220 + es-errors: 1.3.0 2221 + get-intrinsic: 1.3.0 2222 + has-tostringtag: 1.0.2 2223 + hasown: 2.0.2 2224 + 2225 + es-shim-unscopables@1.1.0: 2226 dependencies: 2227 + hasown: 2.0.2 2228 2229 + es-to-primitive@1.3.0: 2230 dependencies: 2231 is-callable: 1.2.7 2232 + is-date-object: 1.1.0 2233 + is-symbol: 1.1.1 2234 2235 esbuild-copy-static-files@0.1.0: {} 2236 ··· 2261 2262 escape-string-regexp@4.0.0: {} 2263 2264 + eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)): 2265 dependencies: 2266 + eslint: 9.23.0(jiti@2.4.2) 2267 2268 + eslint-plugin-prettier@5.2.6(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)))(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0): 2269 dependencies: 2270 + eslint: 9.23.0(jiti@2.4.2) 2271 prettier: 3.1.0 2272 prettier-linter-helpers: 1.0.0 2273 + synckit: 0.11.1 2274 optionalDependencies: 2275 + '@types/eslint': 9.6.1 2276 + eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@2.4.2)) 2277 2278 + eslint-plugin-react@7.37.5(eslint@9.23.0(jiti@2.4.2)): 2279 dependencies: 2280 + array-includes: 3.1.8 2281 + array.prototype.findlast: 1.2.5 2282 + array.prototype.flatmap: 1.3.3 2283 + array.prototype.tosorted: 1.1.4 2284 doctrine: 2.1.0 2285 + es-iterator-helpers: 1.2.1 2286 + eslint: 9.23.0(jiti@2.4.2) 2287 estraverse: 5.3.0 2288 + hasown: 2.0.2 2289 jsx-ast-utils: 3.3.5 2290 minimatch: 3.1.2 2291 + object.entries: 1.1.9 2292 + object.fromentries: 2.0.8 2293 + object.values: 1.2.1 2294 prop-types: 15.8.1 2295 resolve: 2.0.0-next.5 2296 semver: 6.3.1 2297 + string.prototype.matchall: 4.0.12 2298 + string.prototype.repeat: 1.0.0 2299 2300 + eslint-scope@8.3.0: 2301 dependencies: 2302 esrecurse: 4.3.0 2303 estraverse: 5.3.0 2304 2305 eslint-visitor-keys@3.4.3: {} 2306 2307 + eslint-visitor-keys@4.2.0: {} 2308 + 2309 + eslint@9.23.0(jiti@2.4.2): 2310 dependencies: 2311 + '@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2)) 2312 + '@eslint-community/regexpp': 4.12.1 2313 + '@eslint/config-array': 0.19.2 2314 + '@eslint/config-helpers': 0.2.1 2315 + '@eslint/core': 0.12.0 2316 + '@eslint/eslintrc': 3.3.1 2317 + '@eslint/js': 9.23.0 2318 + '@eslint/plugin-kit': 0.2.8 2319 + '@humanfs/node': 0.16.6 2320 '@humanwhocodes/module-importer': 1.0.1 2321 + '@humanwhocodes/retry': 0.4.2 2322 + '@types/estree': 1.0.6 2323 + '@types/json-schema': 7.0.15 2324 ajv: 6.12.6 2325 chalk: 4.1.2 2326 + cross-spawn: 7.0.6 2327 + debug: 4.4.0 2328 escape-string-regexp: 4.0.0 2329 + eslint-scope: 8.3.0 2330 + eslint-visitor-keys: 4.2.0 2331 + espree: 10.3.0 2332 + esquery: 1.6.0 2333 esutils: 2.0.3 2334 fast-deep-equal: 3.1.3 2335 + file-entry-cache: 8.0.0 2336 find-up: 5.0.0 2337 glob-parent: 6.0.2 2338 + ignore: 5.3.2 2339 imurmurhash: 0.1.4 2340 is-glob: 4.0.3 2341 json-stable-stringify-without-jsonify: 1.0.1 2342 lodash.merge: 4.6.2 2343 minimatch: 3.1.2 2344 natural-compare: 1.4.0 2345 optionator: 0.9.3 2346 + optionalDependencies: 2347 + jiti: 2.4.2 2348 transitivePeerDependencies: 2349 - supports-color 2350 2351 + espree@10.3.0: 2352 dependencies: 2353 + acorn: 8.14.1 2354 + acorn-jsx: 5.3.2(acorn@8.14.1) 2355 + eslint-visitor-keys: 4.2.0 2356 2357 + esquery@1.6.0: 2358 dependencies: 2359 estraverse: 5.3.0 2360 ··· 2377 2378 events@3.3.0: {} 2379 2380 fast-deep-equal@3.1.3: {} 2381 2382 fast-diff@1.3.0: {} ··· 2387 '@nodelib/fs.walk': 1.2.8 2388 glob-parent: 5.1.2 2389 merge2: 1.4.1 2390 + micromatch: 4.0.8 2391 2392 fast-json-stable-stringify@2.1.0: {} 2393 2394 fast-levenshtein@2.0.6: {} 2395 2396 + fastq@1.17.1: 2397 dependencies: 2398 reusify: 1.0.4 2399 2400 + fdir@6.4.3(picomatch@4.0.2): 2401 + optionalDependencies: 2402 + picomatch: 4.0.2 2403 + 2404 + file-entry-cache@8.0.0: 2405 dependencies: 2406 + flat-cache: 4.0.1 2407 2408 + fill-range@7.1.1: 2409 dependencies: 2410 to-regex-range: 5.0.1 2411 + 2412 + find-up-simple@1.0.1: {} 2413 2414 find-up@5.0.0: 2415 dependencies: 2416 locate-path: 6.0.0 2417 path-exists: 4.0.0 2418 2419 + flat-cache@4.0.1: 2420 dependencies: 2421 flatted: 3.2.9 2422 keyv: 4.5.4 2423 2424 flatted@3.2.9: {} 2425 2426 + for-each@0.3.5: 2427 dependencies: 2428 is-callable: 1.2.7 2429 2430 function-bind@1.1.2: {} 2431 2432 + function.prototype.name@1.1.8: 2433 dependencies: 2434 + call-bind: 1.0.8 2435 + call-bound: 1.0.4 2436 define-properties: 1.2.1 2437 functions-have-names: 1.2.3 2438 + hasown: 2.0.2 2439 + is-callable: 1.2.7 2440 2441 functions-have-names@1.2.3: {} 2442 2443 + fzf@0.5.2: {} 2444 + 2445 + get-intrinsic@1.3.0: 2446 dependencies: 2447 + call-bind-apply-helpers: 1.0.2 2448 + es-define-property: 1.0.1 2449 + es-errors: 1.3.0 2450 + es-object-atoms: 1.1.1 2451 function-bind: 1.1.2 2452 + get-proto: 1.0.1 2453 + gopd: 1.2.0 2454 + has-symbols: 1.1.0 2455 + hasown: 2.0.2 2456 + math-intrinsics: 1.1.0 2457 2458 + get-proto@1.0.1: 2459 + dependencies: 2460 + dunder-proto: 1.0.1 2461 + es-object-atoms: 1.1.1 2462 2463 + get-symbol-description@1.1.0: 2464 dependencies: 2465 + call-bound: 1.0.4 2466 + es-errors: 1.3.0 2467 + get-intrinsic: 1.3.0 2468 2469 glob-parent@5.1.2: 2470 dependencies: ··· 2474 dependencies: 2475 is-glob: 4.0.3 2476 2477 + globals@14.0.0: {} 2478 2479 + globalthis@1.0.4: 2480 dependencies: 2481 define-properties: 1.2.1 2482 + gopd: 1.2.0 2483 2484 + gopd@1.2.0: {} 2485 2486 graphemer@1.4.0: {} 2487 2488 + has-bigints@1.1.0: {} 2489 2490 has-flag@4.0.0: {} 2491 2492 + has-property-descriptors@1.0.2: 2493 dependencies: 2494 + es-define-property: 1.0.1 2495 2496 + has-proto@1.2.0: 2497 + dependencies: 2498 + dunder-proto: 1.0.1 2499 2500 + has-symbols@1.1.0: {} 2501 2502 + has-tostringtag@1.0.2: 2503 dependencies: 2504 + has-symbols: 1.1.0 2505 2506 + hasown@2.0.2: 2507 dependencies: 2508 function-bind: 1.1.2 2509 2510 husky@8.0.3: {} 2511 2512 ieee754@1.2.1: {} 2513 2514 + ignore@5.3.2: {} 2515 2516 import-fresh@3.3.0: 2517 dependencies: ··· 2520 2521 imurmurhash@0.1.4: {} 2522 2523 + internal-slot@1.1.0: 2524 dependencies: 2525 + es-errors: 1.3.0 2526 + hasown: 2.0.2 2527 + side-channel: 1.1.0 2528 2529 + is-array-buffer@3.0.5: 2530 dependencies: 2531 + call-bind: 1.0.8 2532 + call-bound: 1.0.4 2533 + get-intrinsic: 1.3.0 2534 2535 + is-async-function@2.1.1: 2536 dependencies: 2537 + async-function: 1.0.0 2538 + call-bound: 1.0.4 2539 + get-proto: 1.0.1 2540 + has-tostringtag: 1.0.2 2541 + safe-regex-test: 1.1.0 2542 2543 + is-bigint@1.1.0: 2544 dependencies: 2545 + has-bigints: 1.1.0 2546 2547 + is-boolean-object@1.2.2: 2548 dependencies: 2549 + call-bound: 1.0.4 2550 + has-tostringtag: 1.0.2 2551 2552 is-callable@1.2.7: {} 2553 2554 + is-core-module@2.16.1: 2555 dependencies: 2556 + hasown: 2.0.2 2557 2558 + is-data-view@1.0.2: 2559 dependencies: 2560 + call-bound: 1.0.4 2561 + get-intrinsic: 1.3.0 2562 + is-typed-array: 1.1.15 2563 2564 + is-date-object@1.1.0: 2565 + dependencies: 2566 + call-bound: 1.0.4 2567 + has-tostringtag: 1.0.2 2568 2569 is-extglob@2.1.1: {} 2570 2571 + is-finalizationregistry@1.1.1: 2572 dependencies: 2573 + call-bound: 1.0.4 2574 2575 + is-generator-function@1.1.0: 2576 dependencies: 2577 + call-bound: 1.0.4 2578 + get-proto: 1.0.1 2579 + has-tostringtag: 1.0.2 2580 + safe-regex-test: 1.1.0 2581 2582 is-glob@4.0.3: 2583 dependencies: 2584 is-extglob: 2.1.1 2585 2586 + is-map@2.0.3: {} 2587 2588 + is-number-object@1.1.1: 2589 dependencies: 2590 + call-bound: 1.0.4 2591 + has-tostringtag: 1.0.2 2592 2593 is-number@7.0.0: {} 2594 2595 + is-regex@1.2.1: 2596 dependencies: 2597 + call-bound: 1.0.4 2598 + gopd: 1.2.0 2599 + has-tostringtag: 1.0.2 2600 + hasown: 2.0.2 2601 2602 + is-set@2.0.3: {} 2603 2604 + is-shared-array-buffer@1.0.4: 2605 dependencies: 2606 + call-bound: 1.0.4 2607 2608 + is-string@1.1.1: 2609 dependencies: 2610 + call-bound: 1.0.4 2611 + has-tostringtag: 1.0.2 2612 2613 + is-symbol@1.1.1: 2614 dependencies: 2615 + call-bound: 1.0.4 2616 + has-symbols: 1.1.0 2617 + safe-regex-test: 1.1.0 2618 2619 + is-typed-array@1.1.15: 2620 dependencies: 2621 + which-typed-array: 1.1.19 2622 2623 + is-weakmap@2.0.2: {} 2624 2625 + is-weakref@1.1.1: 2626 dependencies: 2627 + call-bound: 1.0.4 2628 2629 + is-weakset@2.0.4: 2630 dependencies: 2631 + call-bound: 1.0.4 2632 + get-intrinsic: 1.3.0 2633 2634 isarray@2.0.5: {} 2635 2636 isexe@2.0.0: {} 2637 2638 + iterator.prototype@1.1.5: 2639 dependencies: 2640 + define-data-property: 1.1.4 2641 + es-object-atoms: 1.1.1 2642 + get-intrinsic: 1.3.0 2643 + get-proto: 1.0.1 2644 + has-symbols: 1.1.0 2645 + set-function-name: 2.0.2 2646 + 2647 + jiti@2.4.2: {} 2648 2649 js-tokens@4.0.0: {} 2650 ··· 2660 2661 jsx-ast-utils@3.3.5: 2662 dependencies: 2663 + array-includes: 3.1.8 2664 + array.prototype.flat: 1.3.3 2665 + object.assign: 4.1.7 2666 + object.values: 1.2.1 2667 2668 keyv@4.5.4: 2669 dependencies: ··· 2684 dependencies: 2685 js-tokens: 4.0.0 2686 2687 + math-intrinsics@1.1.0: {} 2688 2689 merge2@1.4.1: {} 2690 2691 meriyah@6.0.1: {} 2692 2693 + microdiff@1.5.0: {} 2694 + 2695 + micromatch@4.0.8: 2696 dependencies: 2697 + braces: 3.0.3 2698 picomatch: 2.3.1 2699 2700 + mimic-function@5.0.1: {} 2701 2702 minimatch@3.1.2: 2703 dependencies: ··· 2707 dependencies: 2708 brace-expansion: 2.0.1 2709 2710 + ms@2.1.3: {} 2711 2712 nanotar@0.1.1: {} 2713 2714 natural-compare@1.4.0: {} 2715 2716 + node-fetch-native@1.6.6: {} 2717 2718 object-assign@4.1.1: {} 2719 2720 + object-inspect@1.13.4: {} 2721 2722 object-keys@1.1.1: {} 2723 2724 + object.assign@4.1.7: 2725 dependencies: 2726 + call-bind: 1.0.8 2727 + call-bound: 1.0.4 2728 define-properties: 1.2.1 2729 + es-object-atoms: 1.1.1 2730 + has-symbols: 1.1.0 2731 object-keys: 1.1.1 2732 2733 + object.entries@1.1.9: 2734 dependencies: 2735 + call-bind: 1.0.8 2736 + call-bound: 1.0.4 2737 define-properties: 1.2.1 2738 + es-object-atoms: 1.1.1 2739 2740 + object.fromentries@2.0.8: 2741 dependencies: 2742 + call-bind: 1.0.8 2743 define-properties: 1.2.1 2744 + es-abstract: 1.23.9 2745 + es-object-atoms: 1.1.1 2746 2747 + object.values@1.2.1: 2748 dependencies: 2749 + call-bind: 1.0.8 2750 + call-bound: 1.0.4 2751 define-properties: 1.2.1 2752 + es-object-atoms: 1.1.1 2753 2754 + ofetch@1.4.1: 2755 dependencies: 2756 + destr: 2.0.4 2757 + node-fetch-native: 1.6.6 2758 + ufo: 1.5.4 2759 2760 + onetime@7.0.0: 2761 dependencies: 2762 + mimic-function: 5.0.1 2763 2764 optionator@0.9.3: 2765 dependencies: ··· 2770 prelude-ls: 1.2.1 2771 type-check: 0.4.0 2772 2773 + own-keys@1.0.1: 2774 + dependencies: 2775 + get-intrinsic: 1.3.0 2776 + object-keys: 1.1.1 2777 + safe-push-apply: 1.0.0 2778 + 2779 p-limit@3.1.0: 2780 dependencies: 2781 yocto-queue: 0.1.0 ··· 2783 p-locate@5.0.0: 2784 dependencies: 2785 p-limit: 3.1.0 2786 + 2787 + package-manager-detector@1.1.0: {} 2788 2789 parent-module@1.0.1: 2790 dependencies: ··· 2792 2793 path-exists@4.0.0: {} 2794 2795 path-key@3.1.1: {} 2796 2797 path-parse@1.0.7: {} 2798 2799 + pathe@2.0.3: {} 2800 2801 + picomatch@2.3.1: {} 2802 + 2803 + picomatch@4.0.2: {} 2804 + 2805 + pnpm-workspace-yaml@0.3.1: 2806 + dependencies: 2807 + yaml: 2.7.1 2808 2809 + possible-typed-array-names@1.1.0: {} 2810 2811 prelude-ls@1.2.1: {} 2812 ··· 2826 2827 punycode@2.3.1: {} 2828 2829 + quansync@0.2.10: {} 2830 + 2831 queue-microtask@1.2.3: {} 2832 2833 react-is@16.13.1: {} ··· 2840 process: 0.11.10 2841 string_decoder: 1.3.0 2842 2843 + reflect.getprototypeof@1.0.10: 2844 dependencies: 2845 + call-bind: 1.0.8 2846 define-properties: 1.2.1 2847 + es-abstract: 1.23.9 2848 + es-errors: 1.3.0 2849 + es-object-atoms: 1.1.1 2850 + get-intrinsic: 1.3.0 2851 + get-proto: 1.0.1 2852 + which-builtin-type: 1.2.1 2853 2854 + regexp.prototype.flags@1.5.4: 2855 dependencies: 2856 + call-bind: 1.0.8 2857 define-properties: 1.2.1 2858 + es-errors: 1.3.0 2859 + get-proto: 1.0.1 2860 + gopd: 1.2.0 2861 + set-function-name: 2.0.2 2862 2863 resolve-from@4.0.0: {} 2864 2865 resolve@2.0.0-next.5: 2866 dependencies: 2867 + is-core-module: 2.16.1 2868 path-parse: 1.0.7 2869 supports-preserve-symlinks-flag: 1.0.0 2870 2871 + restore-cursor@5.1.0: 2872 dependencies: 2873 + onetime: 7.0.0 2874 + signal-exit: 4.1.0 2875 2876 + reusify@1.0.4: {} 2877 2878 run-parallel@1.2.0: 2879 dependencies: 2880 queue-microtask: 1.2.3 2881 2882 + safe-array-concat@1.1.3: 2883 dependencies: 2884 + call-bind: 1.0.8 2885 + call-bound: 1.0.4 2886 + get-intrinsic: 1.3.0 2887 + has-symbols: 1.1.0 2888 isarray: 2.0.5 2889 2890 safe-buffer@5.2.1: {} 2891 2892 + safe-push-apply@1.0.0: 2893 dependencies: 2894 + es-errors: 1.3.0 2895 + isarray: 2.0.5 2896 + 2897 + safe-regex-test@1.1.0: 2898 + dependencies: 2899 + call-bound: 1.0.4 2900 + es-errors: 1.3.0 2901 + is-regex: 1.2.1 2902 2903 semver@6.3.1: {} 2904 2905 + semver@7.7.1: {} 2906 2907 + set-function-length@1.2.2: 2908 dependencies: 2909 + define-data-property: 1.1.4 2910 + es-errors: 1.3.0 2911 + function-bind: 1.1.2 2912 + get-intrinsic: 1.3.0 2913 + gopd: 1.2.0 2914 + has-property-descriptors: 1.0.2 2915 2916 + set-function-name@2.0.2: 2917 dependencies: 2918 + define-data-property: 1.1.4 2919 + es-errors: 1.3.0 2920 functions-have-names: 1.2.3 2921 + has-property-descriptors: 1.0.2 2922 + 2923 + set-proto@1.0.0: 2924 + dependencies: 2925 + dunder-proto: 1.0.1 2926 + es-errors: 1.3.0 2927 + es-object-atoms: 1.1.1 2928 2929 shebang-command@2.0.0: 2930 dependencies: ··· 2932 2933 shebang-regex@3.0.0: {} 2934 2935 + side-channel-list@1.0.0: 2936 dependencies: 2937 + es-errors: 1.3.0 2938 + object-inspect: 1.13.4 2939 2940 + side-channel-map@1.0.1: 2941 + dependencies: 2942 + call-bound: 1.0.4 2943 + es-errors: 1.3.0 2944 + get-intrinsic: 1.3.0 2945 + object-inspect: 1.13.4 2946 2947 + side-channel-weakmap@1.0.2: 2948 + dependencies: 2949 + call-bound: 1.0.4 2950 + es-errors: 1.3.0 2951 + get-intrinsic: 1.3.0 2952 + object-inspect: 1.13.4 2953 + side-channel-map: 1.0.1 2954 + 2955 + side-channel@1.1.0: 2956 + dependencies: 2957 + es-errors: 1.3.0 2958 + object-inspect: 1.13.4 2959 + side-channel-list: 1.0.0 2960 + side-channel-map: 1.0.1 2961 + side-channel-weakmap: 1.0.2 2962 + 2963 + signal-exit@4.1.0: {} 2964 2965 standalone-electron-types@1.0.0: 2966 dependencies: 2967 '@types/node': 18.17.17 2968 2969 + string.prototype.matchall@4.0.12: 2970 dependencies: 2971 + call-bind: 1.0.8 2972 + call-bound: 1.0.4 2973 define-properties: 1.2.1 2974 + es-abstract: 1.23.9 2975 + es-errors: 1.3.0 2976 + es-object-atoms: 1.1.1 2977 + get-intrinsic: 1.3.0 2978 + gopd: 1.2.0 2979 + has-symbols: 1.1.0 2980 + internal-slot: 1.1.0 2981 + regexp.prototype.flags: 1.5.4 2982 + set-function-name: 2.0.2 2983 + side-channel: 1.1.0 2984 2985 + string.prototype.repeat@1.0.0: 2986 dependencies: 2987 define-properties: 1.2.1 2988 + es-abstract: 1.23.9 2989 2990 + string.prototype.trim@1.2.10: 2991 dependencies: 2992 + call-bind: 1.0.8 2993 + call-bound: 1.0.4 2994 + define-data-property: 1.1.4 2995 define-properties: 1.2.1 2996 + es-abstract: 1.23.9 2997 + es-object-atoms: 1.1.1 2998 + has-property-descriptors: 1.0.2 2999 3000 + string.prototype.trimend@1.0.9: 3001 dependencies: 3002 + call-bind: 1.0.8 3003 + call-bound: 1.0.4 3004 define-properties: 1.2.1 3005 + es-object-atoms: 1.1.1 3006 3007 + string.prototype.trimstart@1.0.8: 3008 dependencies: 3009 + call-bind: 1.0.8 3010 + define-properties: 1.2.1 3011 + es-object-atoms: 1.1.1 3012 3013 + string_decoder@1.3.0: 3014 dependencies: 3015 + safe-buffer: 5.2.1 3016 3017 strip-json-comments@3.1.1: {} 3018 ··· 3022 3023 supports-preserve-symlinks-flag@1.0.0: {} 3024 3025 + synckit@0.11.1: 3026 dependencies: 3027 + '@pkgr/core': 0.2.0 3028 + tslib: 2.8.1 3029 3030 + taze@19.0.4: 3031 + dependencies: 3032 + '@antfu/ni': 24.3.0 3033 + cac: 6.7.14 3034 + find-up-simple: 1.0.1 3035 + ofetch: 1.4.1 3036 + package-manager-detector: 1.1.0 3037 + pathe: 2.0.3 3038 + pnpm-workspace-yaml: 0.3.1 3039 + restore-cursor: 5.1.0 3040 + tinyexec: 1.0.1 3041 + tinyglobby: 0.2.12 3042 + unconfig: 7.3.1 3043 + yaml: 2.7.1 3044 3045 + tinyexec@1.0.1: {} 3046 + 3047 + tinyglobby@0.2.12: 3048 + dependencies: 3049 + fdir: 6.4.3(picomatch@4.0.2) 3050 + picomatch: 4.0.2 3051 3052 to-regex-range@5.0.1: 3053 dependencies: 3054 is-number: 7.0.0 3055 3056 + ts-api-utils@2.1.0(typescript@5.8.2): 3057 dependencies: 3058 + typescript: 5.8.2 3059 3060 + tslib@2.8.1: {} 3061 3062 type-check@0.4.0: 3063 dependencies: 3064 prelude-ls: 1.2.1 3065 3066 + typed-array-buffer@1.0.3: 3067 + dependencies: 3068 + call-bound: 1.0.4 3069 + es-errors: 1.3.0 3070 + is-typed-array: 1.1.15 3071 3072 + typed-array-byte-length@1.0.3: 3073 dependencies: 3074 + call-bind: 1.0.8 3075 + for-each: 0.3.5 3076 + gopd: 1.2.0 3077 + has-proto: 1.2.0 3078 + is-typed-array: 1.1.15 3079 3080 + typed-array-byte-offset@1.0.4: 3081 dependencies: 3082 + available-typed-arrays: 1.0.7 3083 + call-bind: 1.0.8 3084 + for-each: 0.3.5 3085 + gopd: 1.2.0 3086 + has-proto: 1.2.0 3087 + is-typed-array: 1.1.15 3088 + reflect.getprototypeof: 1.0.10 3089 3090 + typed-array-length@1.0.7: 3091 dependencies: 3092 + call-bind: 1.0.8 3093 + for-each: 0.3.5 3094 + gopd: 1.2.0 3095 + is-typed-array: 1.1.15 3096 + possible-typed-array-names: 1.1.0 3097 + reflect.getprototypeof: 1.0.10 3098 3099 + typescript-eslint@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2): 3100 dependencies: 3101 + '@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) 3102 + '@typescript-eslint/parser': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) 3103 + '@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2) 3104 + eslint: 9.23.0(jiti@2.4.2) 3105 + typescript: 5.8.2 3106 + transitivePeerDependencies: 3107 + - supports-color 3108 + 3109 + typescript@5.8.2: {} 3110 3111 + ufo@1.5.4: {} 3112 3113 + unbox-primitive@1.1.0: 3114 dependencies: 3115 + call-bound: 1.0.4 3116 + has-bigints: 1.1.0 3117 + has-symbols: 1.1.0 3118 + which-boxed-primitive: 1.1.1 3119 3120 + unconfig@7.3.1: 3121 + dependencies: 3122 + '@quansync/fs': 0.1.2 3123 + defu: 6.1.4 3124 + jiti: 2.4.2 3125 + quansync: 0.2.10 3126 + 3127 + undici-types@6.20.0: {} 3128 3129 + undici-types@6.21.0: {} 3130 3131 uri-js@4.4.1: 3132 dependencies: 3133 punycode: 2.3.1 3134 3135 + utilium@1.10.1: 3136 dependencies: 3137 eventemitter3: 5.0.1 3138 + optionalDependencies: 3139 + '@xterm/xterm': 5.5.0 3140 3141 + which-boxed-primitive@1.1.1: 3142 dependencies: 3143 + is-bigint: 1.1.0 3144 + is-boolean-object: 1.2.2 3145 + is-number-object: 1.1.1 3146 + is-string: 1.1.1 3147 + is-symbol: 1.1.1 3148 3149 + which-builtin-type@1.2.1: 3150 dependencies: 3151 + call-bound: 1.0.4 3152 + function.prototype.name: 1.1.8 3153 + has-tostringtag: 1.0.2 3154 + is-async-function: 2.1.1 3155 + is-date-object: 1.1.0 3156 + is-finalizationregistry: 1.1.1 3157 + is-generator-function: 1.1.0 3158 + is-regex: 1.2.1 3159 + is-weakref: 1.1.1 3160 isarray: 2.0.5 3161 + which-boxed-primitive: 1.1.1 3162 + which-collection: 1.0.2 3163 + which-typed-array: 1.1.19 3164 3165 + which-collection@1.0.2: 3166 dependencies: 3167 + is-map: 2.0.3 3168 + is-set: 2.0.3 3169 + is-weakmap: 2.0.2 3170 + is-weakset: 2.0.4 3171 3172 + which-typed-array@1.1.19: 3173 dependencies: 3174 + available-typed-arrays: 1.0.7 3175 + call-bind: 1.0.8 3176 + call-bound: 1.0.4 3177 + for-each: 0.3.5 3178 + get-proto: 1.0.1 3179 + gopd: 1.2.0 3180 + has-tostringtag: 1.0.2 3181 3182 which@2.0.2: 3183 dependencies: 3184 isexe: 2.0.0 3185 3186 + yaml@2.7.1: {} 3187 3188 yocto-queue@0.1.0: {} 3189 + 3190 + zustand@5.0.3(@types/react@18.3.20): 3191 + optionalDependencies: 3192 + '@types/react': 18.3.20
+31 -1
pnpm-workspace.yaml
··· 1 packages: 2 - - "packages/*"
··· 1 packages: 2 + - packages/* 3 + 4 + catalogs: 5 + dev: 6 + esbuild: ^0.19.3 7 + esbuild-copy-static-files: ^0.1.0 8 + "@types/node": ^22.14.0 9 + "@moonlight-mod/eslint-config": "github:moonlight-mod/eslint-config" 10 + eslint: ^9.12.0 11 + "@types/chrome": ^0.0.313 12 + husky: ^8.0.3 13 + prettier: ^3.1.0 14 + typescript: ^5.3.3 15 + taze: ^19.0.4 16 + prod: 17 + "@moonlight-mod/lunast": ^1.0.1 18 + "@moonlight-mod/mappings": ^1.1.25 19 + "@moonlight-mod/moonmap": ^1.0.5 20 + microdiff: ^1.5.0 21 + nanotar: ^0.1.1 22 + "@zenfs/core": ^2.0.0 23 + "@zenfs/dom": ^1.1.3 24 + 25 + onlyBuiltDependencies: 26 + - esbuild 27 + 28 + engineStrict: true 29 + strictSsl: true 30 + strictDepBuilds: true 31 + packageManagerStrict: true 32 + registry: https://registry.npmjs.org/
-70
scripts/link.js
··· 1 - // Janky script to get around pnpm link issues 2 - // Probably don't use this. Probably 3 - /* eslint-disable no-console */ 4 - const fs = require("fs"); 5 - const path = require("path"); 6 - const child_process = require("child_process"); 7 - 8 - const onDisk = { 9 - "@moonlight-mod/lunast": "../lunast", 10 - "@moonlight-mod/moonmap": "../moonmap", 11 - "@moonlight-mod/mappings": "../mappings" 12 - }; 13 - 14 - function exec(cmd, dir) { 15 - child_process.execSync(cmd, { cwd: dir, stdio: "inherit" }); 16 - } 17 - 18 - function getDeps(packageJSON) { 19 - const ret = {}; 20 - Object.assign(ret, packageJSON.dependencies || {}); 21 - Object.assign(ret, packageJSON.devDependencies || {}); 22 - Object.assign(ret, packageJSON.peerDependencies || {}); 23 - return ret; 24 - } 25 - 26 - function link(dir) { 27 - const packageJSON = JSON.parse( 28 - fs.readFileSync(path.join(dir, "package.json"), "utf8") 29 - ); 30 - const deps = getDeps(packageJSON); 31 - 32 - for (const [dep, path] of Object.entries(onDisk)) { 33 - if (deps[dep]) { 34 - exec(`pnpm link ${path}`, dir); 35 - } 36 - } 37 - } 38 - 39 - function undo(dir) { 40 - exec("pnpm unlink", dir); 41 - try { 42 - exec("git restore pnpm-lock.yaml", dir); 43 - } catch { 44 - // ignored 45 - } 46 - } 47 - 48 - const shouldUndo = process.argv.includes("--undo"); 49 - const packages = fs.readdirSync("./packages"); 50 - 51 - for (const path of Object.values(onDisk)) { 52 - console.log(path); 53 - if (shouldUndo) { 54 - undo(path); 55 - } else { 56 - link(path); 57 - } 58 - } 59 - 60 - if (shouldUndo) { 61 - const dir = __dirname; 62 - console.log(dir); 63 - undo(dir); 64 - } else { 65 - for (const pkg of packages) { 66 - const dir = path.join(__dirname, "packages", pkg); 67 - console.log(dir); 68 - link(dir); 69 - } 70 - }
···
+78
scripts/link.mjs
···
··· 1 + // Janky script to get around pnpm link issues 2 + // Probably don't use this. Probably 3 + /* eslint-disable no-console */ 4 + const fs = require("fs"); 5 + const path = require("path"); 6 + const child_process = require("child_process"); 7 + 8 + const cwd = process.cwd(); 9 + const onDisk = { 10 + //"@moonlight-mod/lunast": "../lunast", 11 + //"@moonlight-mod/moonmap": "../moonmap", 12 + "@moonlight-mod/mappings": "../mappings" 13 + }; 14 + 15 + function exec(cmd, dir) { 16 + child_process.execSync(cmd, { cwd: dir, stdio: "inherit" }); 17 + } 18 + 19 + function getDeps(packageJSON) { 20 + const ret = {}; 21 + Object.assign(ret, packageJSON.dependencies || {}); 22 + Object.assign(ret, packageJSON.devDependencies || {}); 23 + Object.assign(ret, packageJSON.peerDependencies || {}); 24 + return ret; 25 + } 26 + 27 + function link(dir) { 28 + const packageJSONPath = path.join(dir, "package.json"); 29 + if (!fs.existsSync(packageJSONPath)) return; 30 + const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, "utf8")); 31 + const deps = getDeps(packageJSON); 32 + 33 + for (const [dep, relativePath] of Object.entries(onDisk)) { 34 + const fullPath = path.join(cwd, relativePath); 35 + if (deps[dep]) { 36 + exec(`pnpm link ${fullPath}`, dir); 37 + } 38 + } 39 + } 40 + 41 + function undo(dir) { 42 + exec("pnpm unlink", dir); 43 + try { 44 + if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) { 45 + exec("git restore pnpm-lock.yaml", dir); 46 + } 47 + } catch { 48 + // ignored 49 + } 50 + } 51 + 52 + const shouldUndo = process.argv.includes("--undo"); 53 + const packages = fs.readdirSync("./packages"); 54 + 55 + for (const path of Object.values(onDisk)) { 56 + console.log(path); 57 + if (shouldUndo) { 58 + undo(path); 59 + } else { 60 + link(path); 61 + } 62 + } 63 + 64 + if (shouldUndo) { 65 + console.log(cwd); 66 + undo(cwd); 67 + for (const pkg of packages) { 68 + const dir = path.join(cwd, "packages", pkg); 69 + console.log(dir); 70 + undo(dir); 71 + } 72 + } else { 73 + for (const pkg of packages) { 74 + const dir = path.join(cwd, "packages", pkg); 75 + console.log(dir); 76 + link(dir); 77 + } 78 + }
-31
scripts/update.js
··· 1 - // Update dependencies in all packages 2 - /* eslint-disable no-console */ 3 - const fs = require("fs"); 4 - const path = require("path"); 5 - const child_process = require("child_process"); 6 - 7 - const packageToUpdate = process.argv[2]; 8 - 9 - function getDeps(packageJSON) { 10 - const ret = {}; 11 - Object.assign(ret, packageJSON.dependencies || {}); 12 - Object.assign(ret, packageJSON.devDependencies || {}); 13 - Object.assign(ret, packageJSON.peerDependencies || {}); 14 - return ret; 15 - } 16 - 17 - function exec(cmd, dir) { 18 - child_process.execSync(cmd, { cwd: dir, stdio: "inherit" }); 19 - } 20 - 21 - for (const package of fs.readdirSync("./packages")) { 22 - const packageJSON = JSON.parse( 23 - fs.readFileSync(path.join("./packages", package, "package.json"), "utf8") 24 - ); 25 - 26 - const deps = getDeps(packageJSON); 27 - if (Object.keys(deps).includes(packageToUpdate)) { 28 - console.log(`Updating ${packageToUpdate} in ${package}`); 29 - exec(`pnpm update ${packageToUpdate}`, path.join("./packages", package)); 30 - } 31 - }
···
+35
tsconfig.base.json
···
··· 1 + { 2 + "$schema": "https://json.schemastore.org/tsconfig.json", 3 + "display": "Base", 4 + "_version": "1.0.0", 5 + "compilerOptions": { 6 + "incremental": true, 7 + "target": "ES2022", 8 + "jsx": "react", 9 + "lib": ["ESNext", "ESNext.Disposable", "DOM", "DOM.Iterable"], 10 + "module": "ES2020", 11 + "moduleResolution": "Bundler", 12 + "resolveJsonModule": true, 13 + "allowArbitraryExtensions": false, 14 + "allowImportingTsExtensions": true, 15 + "allowJs": true, 16 + "strict": true, 17 + "strictNullChecks": true, 18 + 19 + // disable unreachable code detection because it breaks with esbuild labels 20 + "allowUnreachableCode": true, 21 + "noFallthroughCasesInSwitch": true, 22 + "noImplicitReturns": true, 23 + "declaration": true, 24 + "declarationMap": true, 25 + "outDir": "dist", 26 + "sourceMap": true, 27 + "stripInternal": true, 28 + "esModuleInterop": true, 29 + "forceConsistentCasingInFileNames": true, 30 + "noErrorTruncation": true, 31 + "verbatimModuleSyntax": false, 32 + // meriyah has a broken import lol 33 + "skipLibCheck": true 34 + } 35 + }
+7 -16
tsconfig.json
··· 1 { 2 "compilerOptions": { 3 - "target": "es2022", 4 - "module": "es6", 5 - "esModuleInterop": true, 6 - "forceConsistentCasingInFileNames": true, 7 - "strict": true, 8 - "moduleResolution": "bundler", 9 "baseUrl": "./packages/", 10 - "jsx": "react", 11 - "noEmit": true, 12 - 13 - // meriyah has a broken import lol 14 - "skipLibCheck": true, 15 - 16 - // disable unreachable code detection because it breaks with esbuild labels 17 - "allowUnreachableCode": true 18 }, 19 - "include": ["./packages/**/*", "./env.d.ts"], 20 - "exclude": ["node_modules"] 21 }
··· 1 { 2 + "extends": ["./tsconfig.base.json"], 3 "compilerOptions": { 4 "baseUrl": "./packages/", 5 + "noEmit": true 6 }, 7 + "exclude": [ 8 + "**/node_modules/**", 9 + "**/dist/**", 10 + "**/build/**" 11 + ] 12 }