this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+10781 -4105
.github
.vscode
img
nix
packages
browser
core
core-extensions
injector
node-preload
types
web-preload
scripts
-57
.eslintrc.json
··· 1 - { 2 - "extends": [ 3 - "eslint:recommended", 4 - "plugin:@typescript-eslint/recommended", 5 - "plugin:prettier/recommended", 6 - "plugin:react/recommended" 7 - ], 8 - "plugins": ["@typescript-eslint", "prettier", "react"], 9 - "parser": "@typescript-eslint/parser", 10 - "env": { 11 - "browser": true, 12 - "node": true 13 - }, 14 - "parserOptions": { 15 - "ecmaFeatures": { 16 - "jsx": true 17 - }, 18 - "ecmaVersion": "latest", 19 - "sourceType": "module" 20 - }, 21 - "rules": { 22 - "indent": "off", 23 - "eqeqeq": [ 24 - "error", 25 - "always", 26 - { 27 - "null": "ignore" 28 - } 29 - ], 30 - "quotes": [ 31 - "error", 32 - "double", 33 - { "avoidEscape": true, "allowTemplateLiterals": true } 34 - ], 35 - "@typescript-eslint/no-unused-vars": [ 36 - "error", 37 - { "args": "none", "varsIgnorePattern": "^_" } 38 - ], 39 - // Mostly so we don't forget to leave these in when committing 40 - "no-console": "error", 41 - "no-debugger": "error", 42 - 43 - // Quite honestly we're interacting with so much unknown within Discord that 44 - // this being enabled is a hinderance 45 - "@typescript-eslint/no-explicit-any": "off", 46 - 47 - "@typescript-eslint/no-var-requires": "off", 48 - 49 - // https://canary.discord.com/channels/1154257010532032512/1154275441788583996/1181760413231230976 50 - "no-unused-labels": "off" 51 - }, 52 - "settings": { 53 - "react": { 54 - "version": "18.2" 55 - } 56 - } 57 - }
+41
.github/workflows/browser.yml
··· 1 + name: Browser extension builds 2 + 3 + on: 4 + push: 5 + branches: 6 + - develop 7 + 8 + jobs: 9 + browser: 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 21 + run: pnpm install --frozen-lockfile 22 + - name: Build moonlight 23 + env: 24 + NODE_ENV: production 25 + run: pnpm run build 26 + 27 + - name: Build MV3 28 + run: pnpm run browser 29 + - name: Build MV2 30 + run: pnpm run browser-mv2 31 + 32 + - name: Upload MV3 33 + uses: actions/upload-artifact@v4 34 + with: 35 + name: browser 36 + path: ./dist/browser 37 + - name: Upload MV2 38 + uses: actions/upload-artifact@v4 39 + with: 40 + name: browser-mv2 41 + path: ./dist/browser-mv2
+4 -8
.github/workflows/lint.yml
··· 9 9 name: Lint commits 10 10 runs-on: ubuntu-latest 11 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 12 + - uses: actions/checkout@v4 13 + - uses: pnpm/action-setup@v4 14 + - uses: actions/setup-node@v4 19 15 with: 20 - node-version: 18 16 + node-version: 22 21 17 cache: pnpm 22 18 23 19 - name: Install dependencies
+9 -11
.github/workflows/nightly.yml
··· 15 15 name: Nightly builds on GitHub Pages 16 16 runs-on: ubuntu-latest 17 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 18 + - uses: actions/checkout@v4 19 + - uses: pnpm/action-setup@v4 20 + - uses: actions/setup-node@v4 25 21 with: 26 - node-version: 18 22 + node-version: 22 27 23 cache: pnpm 28 24 29 25 - name: Install dependencies ··· 31 27 - name: Build moonlight 32 28 env: 33 29 NODE_ENV: production 30 + MOONLIGHT_BRANCH: nightly 31 + MOONLIGHT_VERSION: ${{ github.sha }} 34 32 run: pnpm run build 35 33 36 34 - name: Write ref/commit to file ··· 45 43 echo "$(date +%s)" >> ./dist/ref 46 44 47 45 - name: Setup GitHub Pages 48 - uses: actions/configure-pages@v3 46 + uses: actions/configure-pages@v5 49 47 - name: Upload artifact 50 - uses: actions/upload-pages-artifact@v1 48 + uses: actions/upload-pages-artifact@v3 51 49 with: 52 50 path: ./dist 53 51 - name: Deploy to GitHub Pages 54 - uses: actions/deploy-pages@v2 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
+6 -8
.github/workflows/release.yml
··· 13 13 name: Release builds to GitHub Releases 14 14 runs-on: ubuntu-latest 15 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 16 + - uses: actions/checkout@v4 17 + - uses: pnpm/action-setup@v4 18 + - uses: actions/setup-node@v4 23 19 with: 24 - node-version: 18 20 + node-version: 22 25 21 cache: pnpm 26 22 27 23 - name: Install dependencies ··· 29 25 - name: Build moonlight 30 26 env: 31 27 NODE_ENV: production 28 + MOONLIGHT_BRANCH: stable 29 + MOONLIGHT_VERSION: ${{ github.ref_name }} 32 30 run: pnpm run build 33 31 - name: Create archive 34 32 run: |
+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 3 dist.tar.gz 4 4 .DS_Store 5 5 eslint_report.json 6 - 6 + .eslintcache 7 7 # Nix 8 8 /result 9 9 *.drv 10 + 11 + # IDEs 12 + .vscode/ 13 + .idea/
+1 -1
.prettierignore
··· 1 - pnpm-lock.yml 1 + pnpm-lock.yaml
+4 -4
.prettierrc
··· 1 1 { 2 - "printWidth": 80, 3 - "trailingComma": "none", 4 - "tabWidth": 2, 5 - "singleQuote": false 2 + "printWidth": 120, 3 + "trailingComma": "none", 4 + "tabWidth": 2, 5 + "singleQuote": false 6 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
+15 -4
README.md
··· 1 1 <h3 align="center"> 2 - <img src="./img/wordmark.png" alt="moonlight" /> 2 + <picture> 3 + <source media="(prefers-color-scheme: dark)" srcset="./img/wordmark-light.png"> 4 + <source media="(prefers-color-scheme: light)" srcset="./img/wordmark.png"> 5 + <img src="./img/wordmark.png" alt="moonlight" /> 6 + </picture> 3 7 4 - <a href="https://discord.gg/FdZBTFCP6F">Discord server</a> 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> 5 11 \- <a href="https://github.com/moonlight-mod/moonlight">GitHub</a> 6 - \- <a href="https://moonlight-mod.github.io/">Docs</a> 7 12 8 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> 9 20 </h3> 10 21 11 22 **moonlight** is yet another Discord client mod, focused on providing a decent user and developer experience. 12 23 13 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. 14 25 15 - **_This is an experimental passion project._** moonlight was not created out of malicious intent nor intended to seriously compete with other mods. Anything and everything is subject to change. 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. 16 27 17 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.
+127 -41
build.mjs
··· 13 13 14 14 const prod = process.env.NODE_ENV === "production"; 15 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"; 16 22 17 23 const external = [ 18 24 "electron", 19 25 "fs", 20 26 "path", 21 27 "module", 22 - "events", 23 - "original-fs", // wtf asar? 28 + "discord", // mappings 24 29 25 30 // Silence an esbuild warning 26 31 "./node-preload.js" ··· 65 70 name: "build-log", 66 71 setup(build) { 67 72 build.onEnd((result) => { 68 - console.log( 69 - `[${timeFormatter.format(new Date())}] [${tag}] build finished` 70 - ); 73 + console.log(`[${timeFormatter.format(new Date())}] [${tag}] build finished`); 71 74 }); 72 75 } 73 76 }); 74 77 75 78 async function build(name, entry) { 76 - const outfile = path.join("./dist", name + ".js"); 79 + let outfile = path.join("./dist", name + ".js"); 80 + const browserDir = mv2 ? "browser-mv2" : "browser"; 81 + if (name === "browser") outfile = path.join("./dist", browserDir, "index.js"); 77 82 78 83 const dropLabels = []; 79 - if (name !== "injector") dropLabels.push("injector"); 80 - if (name !== "node-preload") dropLabels.push("nodePreload"); 81 - if (name !== "web-preload") dropLabels.push("webPreload"); 84 + const labels = { 85 + injector: ["injector"], 86 + nodePreload: ["node-preload"], 87 + webPreload: ["web-preload"], 88 + browser: ["browser"], 89 + 90 + webTarget: ["web-preload", "browser"], 91 + nodeTarget: ["node-preload", "injector"] 92 + }; 93 + for (const [label, targets] of Object.entries(labels)) { 94 + if (!targets.includes(name)) { 95 + dropLabels.push(label); 96 + } 97 + } 82 98 83 99 const define = { 84 100 MOONLIGHT_ENV: `"${name}"`, 85 - MOONLIGHT_PROD: prod.toString() 101 + MOONLIGHT_PROD: prod.toString(), 102 + MOONLIGHT_BRANCH: `"${buildBranch}"`, 103 + MOONLIGHT_VERSION: `"${buildVersion}"` 86 104 }; 87 105 88 - for (const iterName of Object.keys(config)) { 106 + for (const iterName of ["injector", "node-preload", "web-preload", "browser"]) { 89 107 const snake = iterName.replace(/-/g, "_").toUpperCase(); 90 108 define[`MOONLIGHT_${snake}`] = (name === iterName).toString(); 91 109 } ··· 93 111 const nodeDependencies = ["glob"]; 94 112 const ignoredExternal = name === "web-preload" ? nodeDependencies : []; 95 113 114 + const plugins = [deduplicatedLogging, taggedBuildLog(name)]; 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 + ); 122 + 123 + if (!mv2) { 124 + plugins.push( 125 + copyStaticFiles({ 126 + src: "./packages/browser/modifyResponseHeaders.json", 127 + dest: `./dist/${browserDir}/modifyResponseHeaders.json` 128 + }) 129 + ); 130 + plugins.push( 131 + copyStaticFiles({ 132 + src: "./packages/browser/blockLoading.json", 133 + dest: `./dist/${browserDir}/blockLoading.json` 134 + }) 135 + ); 136 + } 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 + ); 144 + } 145 + 96 146 /** @type {import("esbuild").BuildOptions} */ 97 147 const esbuildConfig = { 98 148 entryPoints: [entry], 99 149 outfile, 100 150 101 - format: "cjs", 102 - platform: name === "web-preload" ? "browser" : "node", 151 + format: "iife", 152 + globalName: "module.exports", 153 + 154 + platform: ["web-preload", "browser"].includes(name) ? "browser" : "node", 103 155 104 156 treeShaking: true, 105 157 bundle: true, ··· 112 164 dropLabels, 113 165 114 166 logLevel: "silent", 115 - plugins: [deduplicatedLogging, taggedBuildLog(name)] 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 116 176 }; 117 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) { 184 + const filePath = dir + "/" + file; 185 + const normalizedPath = filePath.replace("./dist/core-extensions/", ""); 186 + if (fs.statSync(filePath).isDirectory()) { 187 + readDir(filePath); 188 + } else { 189 + coreExtensionsJson[normalizedPath] = fs.readFileSync(filePath, "utf8"); 190 + } 191 + } 192 + } 193 + 194 + readDir("./dist/core-extensions"); 195 + 196 + esbuildConfig.banner = { 197 + js: `window._moonlight_coreExtensionsStr = ${JSON.stringify(JSON.stringify(coreExtensionsJson))};` 198 + }; 199 + } 200 + 118 201 if (watch) { 119 202 const ctx = await esbuild.context(esbuildConfig); 120 203 await ctx.watch(); ··· 123 206 } 124 207 } 125 208 126 - async function buildExt(ext, side, copyManifest, fileExt) { 209 + async function buildExt(ext, side, fileExt) { 127 210 const outdir = path.join("./dist", "core-extensions", ext); 128 211 if (!fs.existsSync(outdir)) { 129 212 fs.mkdirSync(outdir, { recursive: true }); 130 213 } 131 214 132 - const entryPoints = [ 133 - `packages/core-extensions/src/${ext}/${side}.${fileExt}` 134 - ]; 215 + const entryPoints = [`packages/core-extensions/src/${ext}/${side}.${fileExt}`]; 135 216 136 217 const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`; 137 218 if (fs.existsSync(wpModulesDir) && side === "index") { 138 219 const wpModules = fs.opendirSync(wpModulesDir); 139 220 for await (const wpModule of wpModules) { 140 221 if (wpModule.isFile()) { 141 - entryPoints.push( 142 - `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}` 143 - ); 222 + entryPoints.push(`packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`); 144 223 } else { 145 224 for (const fileExt of ["ts", "tsx"]) { 146 225 const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`; ··· 168 247 } 169 248 }; 170 249 250 + const styleInput = `packages/core-extensions/src/${ext}/style.css`; 251 + const styleOutput = `dist/core-extensions/${ext}/style.css`; 252 + 171 253 const esbuildConfig = { 172 254 entryPoints, 173 255 outdir, 174 256 175 - format: "cjs", 257 + format: "iife", 258 + globalName: "module.exports", 176 259 platform: "node", 177 260 178 261 treeShaking: true, ··· 186 269 }, 187 270 logLevel: "silent", 188 271 plugins: [ 189 - ...(copyManifest 272 + copyStaticFiles({ 273 + src: `./packages/core-extensions/src/${ext}/manifest.json`, 274 + dest: `./dist/core-extensions/${ext}/manifest.json` 275 + }), 276 + ...(fs.existsSync(styleInput) 190 277 ? [ 191 278 copyStaticFiles({ 192 - src: `./packages/core-extensions/src/${ext}/manifest.json`, 193 - dest: `./dist/core-extensions/${ext}/manifest.json` 279 + src: styleInput, 280 + dest: styleOutput 194 281 }) 195 282 ] 196 283 : []), ··· 210 297 211 298 const promises = []; 212 299 213 - for (const [name, entry] of Object.entries(config)) { 214 - promises.push(build(name, entry)); 215 - } 216 - 217 - const coreExtensions = fs.readdirSync("./packages/core-extensions/src"); 218 - for (const ext of coreExtensions) { 219 - let copiedManifest = false; 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)) { 306 + promises.push(build(name, entry)); 307 + } 220 308 221 - for (const fileExt of ["ts", "tsx"]) { 222 - for (const type of ["index", "node", "host"]) { 223 - if ( 224 - fs.existsSync( 225 - `./packages/core-extensions/src/${ext}/${type}.${fileExt}` 226 - ) 227 - ) { 228 - promises.push(buildExt(ext, type, !copiedManifest, fileExt)); 229 - copiedManifest = true; 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 + } 230 316 } 231 317 } 232 318 }
+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 18 "type": "github" 19 19 } 20 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 21 "nixpkgs": { 40 22 "locked": { 41 - "lastModified": 1704295289, 42 - "narHash": "sha256-9WZDRfpMqCYL6g/HNWVvXF0hxdaAgwgIGeLYiOhmes8=", 23 + "lastModified": 1744232761, 24 + "narHash": "sha256-gbl9hE39nQRpZaLjhWKmEu5ejtQsgI5TWYrIVVJn30U=", 43 25 "owner": "NixOS", 44 26 "repo": "nixpkgs", 45 - "rev": "b0b2c5445c64191fd8d0b31f2b1a34e45a64547d", 27 + "rev": "f675531bc7e6657c10a18b565cfebd8aa9e24c14", 46 28 "type": "github" 47 29 }, 48 30 "original": { 49 31 "owner": "NixOS", 50 - "ref": "nixos-23.11", 51 - "repo": "nixpkgs", 52 - "type": "github" 53 - } 54 - }, 55 - "nixpkgs_2": { 56 - "locked": { 57 - "lastModified": 1702151865, 58 - "narHash": "sha256-9VAt19t6yQa7pHZLDbil/QctAgVsA66DLnzdRGqDisg=", 59 - "owner": "nixos", 60 - "repo": "nixpkgs", 61 - "rev": "666fc80e7b2afb570462423cb0e1cf1a3a34fedd", 62 - "type": "github" 63 - }, 64 - "original": { 65 - "owner": "nixos", 66 32 "ref": "nixos-unstable", 67 33 "repo": "nixpkgs", 68 34 "type": "github" 69 35 } 70 36 }, 71 - "pnpm2nix": { 72 - "inputs": { 73 - "flake-utils": "flake-utils_2", 74 - "nixpkgs": "nixpkgs_2" 75 - }, 76 - "locked": { 77 - "lastModified": 1709572248, 78 - "narHash": "sha256-WhaKD4cIvZLbwI2vZTkpH/oEeqGiyMvdW3bLi24P0eU=", 79 - "owner": "mojotech", 80 - "repo": "pnpm2nix-nzbr", 81 - "rev": "c3cfff81ea297cfb9dc18928652f375314dc287d", 82 - "type": "github" 83 - }, 84 - "original": { 85 - "owner": "mojotech", 86 - "repo": "pnpm2nix-nzbr", 87 - "type": "github" 88 - } 89 - }, 90 37 "root": { 91 38 "inputs": { 92 39 "flake-utils": "flake-utils", 93 - "nixpkgs": "nixpkgs", 94 - "pnpm2nix": "pnpm2nix" 40 + "nixpkgs": "nixpkgs" 95 41 } 96 42 }, 97 43 "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 44 "locked": { 114 45 "lastModified": 1681028828, 115 46 "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+5 -89
flake.nix
··· 2 2 description = "Yet another Discord mod"; 3 3 4 4 inputs = { 5 - nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 6 flake-utils.url = "github:numtide/flake-utils"; 7 - pnpm2nix.url = "github:mojotech/pnpm2nix-nzbr"; 8 7 }; 9 8 10 - outputs = { self, nixpkgs, flake-utils, pnpm2nix }: 11 - let 12 - mkMoonlight = { pkgs, mkPnpmPackage }: 13 - mkPnpmPackage rec { 14 - workspace = ./.; 15 - src = ./.; 16 - components = [ 17 - "packages/core" 18 - "packages/core-extensions" 19 - "packages/injector" 20 - "packages/node-preload" 21 - "packages/types" 22 - "packages/web-preload" 23 - ]; 24 - distDirs = [ "dist" ]; 25 - 26 - copyNodeModules = true; 27 - buildPhase = "pnpm run build"; 28 - installPhase = "cp -r dist $out"; 29 - 30 - meta = with pkgs.lib; { 31 - description = "Yet another Discord mod"; 32 - homepage = "https://moonlight-mod.github.io/"; 33 - license = licenses.lgpl3; 34 - maintainers = with maintainers; [ notnite ]; 35 - }; 36 - }; 37 - 38 - nameTable = { 39 - discord = "Discord"; 40 - discord-ptb = "DiscordPTB"; 41 - discord-canary = "DiscordCanary"; 42 - discord-development = "DiscordDevelopment"; 43 - }; 44 - 45 - darwinNameTable = { 46 - discord = "Discord"; 47 - discord-ptb = "Discord PTB"; 48 - discord-canary = "Discord Canary"; 49 - discord-development = "Discord Development"; 50 - }; 51 - 52 - mkOverride = prev: moonlight: name: 53 - let discord = prev.${name}; 54 - in discord.overrideAttrs (old: { 55 - installPhase = let 56 - folderName = nameTable.${name}; 57 - darwinFolderName = darwinNameTable.${name}; 58 - 59 - injected = '' 60 - require("${moonlight}/injector").inject( 61 - require("path").join(__dirname, "../_app.asar") 62 - ); 63 - ''; 64 - 65 - packageJson = '' 66 - {"name":"discord","main":"./injector.js","private":true} 67 - ''; 68 - 69 - in old.installPhase + "\n" + '' 70 - resources="$out/opt/${folderName}/resources" 71 - if [ ! -d "$resources" ]; then 72 - resources="$out/Applications/${darwinFolderName}.app/Contents/Resources" 73 - fi 74 - 75 - mv "$resources/app.asar" "$resources/_app.asar" 76 - mkdir -p "$resources/app" 77 - 78 - cat > "$resources/app/injector.js" <<EOF 79 - ${injected} 80 - EOF 81 - 82 - echo '${packageJson}' > "$resources/app/package.json" 83 - ''; 84 - }); 85 - 86 - overlay = final: prev: rec { 87 - moonlight-mod = mkMoonlight { 88 - pkgs = final; 89 - mkPnpmPackage = pnpm2nix.packages.${final.system}.mkPnpmPackage; 90 - }; 91 - discord = mkOverride prev moonlight-mod "discord"; 92 - discord-ptb = mkOverride prev moonlight-mod "discord-ptb"; 93 - discord-canary = mkOverride prev moonlight-mod "discord-canary"; 94 - discord-development = 95 - mkOverride prev moonlight-mod "discord-development"; 96 - }; 9 + outputs = { self, nixpkgs, flake-utils }: 10 + let overlay = import ./nix/overlay.nix { }; 97 11 in flake-utils.lib.eachDefaultSystem (system: 98 12 let 99 13 pkgs = import nixpkgs { ··· 102 16 overlays = [ overlay ]; 103 17 }; 104 18 in { 19 + # Don't use these unless you're testing things 105 20 packages.default = pkgs.moonlight-mod; 106 21 packages.moonlight-mod = pkgs.moonlight-mod; 107 22 ··· 111 26 packages.discord-development = pkgs.discord-development; 112 27 }) // { 113 28 overlays.default = overlay; 29 + homeModules.default = ./nix/home-manager.nix; 114 30 }; 115 31 }
img/wordmark-light.png

This is a binary file and will not be displayed.

+57
nix/default.nix
··· 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 + outputs = [ "out" "firefox" ]; 15 + 16 + nativeBuildInputs = [ 17 + nodejs_22 18 + pnpm_10.configHook 19 + ]; 20 + 21 + pnpmDeps = pnpm_10.fetchDeps { 22 + inherit (finalAttrs) pname version src; 23 + hash = "sha256-I+zRCUqJabpGJRFBGW0NrM9xzyzeCjioF54zlCpynBU="; 24 + }; 25 + 26 + env = { 27 + NODE_ENV = "production"; 28 + MOONLIGHT_VERSION = "v${finalAttrs.version}"; 29 + }; 30 + 31 + buildPhase = '' 32 + runHook preBuild 33 + 34 + pnpm run build 35 + pnpm run browser-mv2 36 + 37 + runHook postBuild 38 + ''; 39 + 40 + installPhase = '' 41 + runHook preInstall 42 + 43 + cp -r dist $out 44 + 45 + mkdir -p $firefox/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/ 46 + mv $out/browser-mv2 $firefox/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/{0fb6d66f-f22d-4555-a87b-34ef4bea5e2a} 47 + 48 + runHook postInstall 49 + ''; 50 + 51 + meta = with lib; { 52 + description = "Yet another Discord mod"; 53 + homepage = "https://moonlight-mod.github.io/"; 54 + license = licenses.lgpl3; 55 + maintainers = with maintainers; [ notnite ]; 56 + }; 57 + })
+56
nix/home-manager.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + let cfg = config.programs.moonlight-mod; 4 + in { 5 + options.programs.moonlight-mod = { 6 + enable = lib.mkEnableOption "Yet another Discord mod"; 7 + 8 + configs = let 9 + # TODO: type this 10 + type = lib.types.nullOr (lib.types.attrs); 11 + default = null; 12 + in { 13 + stable = lib.mkOption { 14 + inherit type default; 15 + description = "Configuration for Discord Stable"; 16 + }; 17 + 18 + ptb = lib.mkOption { 19 + inherit type default; 20 + description = "Configuration for Discord PTB"; 21 + }; 22 + 23 + canary = lib.mkOption { 24 + inherit type default; 25 + description = "Configuration for Discord Canary"; 26 + }; 27 + 28 + development = lib.mkOption { 29 + inherit type default; 30 + description = "Configuration for Discord Development"; 31 + }; 32 + }; 33 + }; 34 + 35 + config = lib.mkIf cfg.enable { 36 + xdg.configFile."moonlight-mod/stable.json" = 37 + lib.mkIf (cfg.configs.stable != null) { 38 + text = builtins.toJSON cfg.configs.stable; 39 + }; 40 + 41 + xdg.configFile."moonlight-mod/ptb.json" = 42 + lib.mkIf (cfg.configs.ptb != null) { 43 + text = builtins.toJSON cfg.configs.ptb; 44 + }; 45 + 46 + xdg.configFile."moonlight-mod/canary.json" = 47 + lib.mkIf (cfg.configs.canary != null) { 48 + text = builtins.toJSON cfg.configs.canary; 49 + }; 50 + 51 + xdg.configFile."moonlight-mod/development.json" = 52 + lib.mkIf (cfg.configs.development != null) { 53 + text = builtins.toJSON cfg.configs.development; 54 + }; 55 + }; 56 + }
+57
nix/overlay.nix
··· 1 + { ... }: 2 + 3 + let 4 + nameTable = { 5 + discord = "Discord"; 6 + discord-ptb = "DiscordPTB"; 7 + discord-canary = "DiscordCanary"; 8 + discord-development = "DiscordDevelopment"; 9 + }; 10 + 11 + darwinNameTable = { 12 + discord = "Discord"; 13 + discord-ptb = "Discord PTB"; 14 + discord-canary = "Discord Canary"; 15 + discord-development = "Discord Development"; 16 + }; 17 + 18 + mkOverride = prev: moonlight: name: 19 + let discord = prev.${name}; 20 + in discord.overrideAttrs (old: { 21 + installPhase = let 22 + folderName = nameTable.${name}; 23 + darwinFolderName = darwinNameTable.${name}; 24 + 25 + injected = '' 26 + require("${moonlight}/injector").inject( 27 + require("path").join(__dirname, "../_app.asar") 28 + ); 29 + ''; 30 + 31 + packageJson = '' 32 + {"name":"${name}","main":"./injector.js","private":true} 33 + ''; 34 + 35 + in old.installPhase + "\n" + '' 36 + resources="$out/opt/${folderName}/resources" 37 + if [ ! -d "$resources" ]; then 38 + resources="$out/Applications/${darwinFolderName}.app/Contents/Resources" 39 + fi 40 + 41 + mv "$resources/app.asar" "$resources/_app.asar" 42 + mkdir -p "$resources/app" 43 + 44 + cat > "$resources/app/injector.js" <<EOF 45 + ${injected} 46 + EOF 47 + 48 + echo '${packageJson}' > "$resources/app/package.json" 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"; 56 + discord-development = mkOverride prev moonlight-mod "discord-development"; 57 + }
+26 -16
package.json
··· 1 1 { 2 2 "name": "moonlight", 3 - "version": "1.0.10", 3 + "version": "1.3.14", 4 + "packageManager": "pnpm@10.7.1", 4 5 "description": "Yet another Discord mod", 5 - "homepage": "https://moonlight-mod.github.io/", 6 6 "license": "LGPL-3.0-or-later", 7 + "homepage": "https://moonlight-mod.github.io/", 7 8 "repository": { 8 9 "type": "git", 9 10 "url": "git+https://github.com/moonlight-mod/moonlight.git" ··· 11 12 "bugs": { 12 13 "url": "https://github.com/moonlight-mod/moonlight/issues" 13 14 }, 15 + "engineStrict": true, 16 + "engines": { 17 + "node": ">=22", 18 + "pnpm": ">=10", 19 + "npm": "pnpm", 20 + "yarn": "pnpm" 21 + }, 14 22 "scripts": { 15 23 "build": "node build.mjs", 16 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", 17 28 "lint": "eslint packages", 18 - "lint:fix": "eslint packages", 19 - "lint:report": "eslint --output-file eslint_report.json --format json packages", 29 + "lint:fix": "pnpm lint --fix", 30 + "lint:report": "pnpm lint --output-file eslint_report.json --format json", 20 31 "typecheck": "tsc --noEmit", 21 32 "check": "pnpm run lint && pnpm run typecheck", 22 - "prepare": "husky install" 33 + "prepare": "husky install", 34 + "updates": "pnpm taze -r" 23 35 }, 24 36 "devDependencies": { 25 - "@typescript-eslint/eslint-plugin": "^6.13.2", 26 - "@typescript-eslint/parser": "^6.13.2", 27 - "esbuild": "^0.19.3", 28 - "esbuild-copy-static-files": "^0.1.0", 29 - "eslint": "^8.55.0", 30 - "eslint-config-prettier": "^9.1.0", 31 - "eslint-plugin-prettier": "^5.0.1", 32 - "eslint-plugin-react": "^7.33.2", 33 - "husky": "^8.0.3", 34 - "prettier": "^3.1.0", 35 - "typescript": "^5.3.2" 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" 36 46 } 37 47 }
+14
packages/browser/blockLoading.json
··· 1 + [ 2 + { 3 + "id": 2, 4 + "priority": 1, 5 + "action": { 6 + "type": "block" 7 + }, 8 + "condition": { 9 + "requestDomains": ["discord.com", "discordapp.com"], 10 + "urlFilter": "*/assets/*.js", 11 + "resourceTypes": ["script"] 12 + } 13 + } 14 + ]
+46
packages/browser/manifest.json
··· 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 + } 21 + ], 22 + "declarative_net_request": { 23 + "rule_resources": [ 24 + { 25 + "id": "modifyResponseHeaders", 26 + "enabled": true, 27 + "path": "modifyResponseHeaders.json" 28 + }, 29 + { 30 + "id": "blockLoading", 31 + "enabled": true, 32 + "path": "blockLoading.json" 33 + } 34 + ] 35 + }, 36 + "background": { 37 + "service_worker": "background.js", 38 + "type": "module" 39 + }, 40 + "web_accessible_resources": [ 41 + { 42 + "resources": ["index.js"], 43 + "matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"] 44 + } 45 + ] 46 + }
+33
packages/browser/manifestv2.json
··· 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"] 19 + }, 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 + } 27 + ], 28 + "browser_specific_settings": { 29 + "gecko": { 30 + "id": "{0fb6d66f-f22d-4555-a87b-34ef4bea5e2a}" 31 + } 32 + } 33 + }
+19
packages/browser/modifyResponseHeaders.json
··· 1 + [ 2 + { 3 + "id": 1, 4 + "priority": 2, 5 + "action": { 6 + "type": "modifyHeaders", 7 + "responseHeaders": [ 8 + { 9 + "header": "Content-Security-Policy", 10 + "operation": "remove" 11 + } 12 + ] 13 + }, 14 + "condition": { 15 + "resourceTypes": ["main_frame"], 16 + "requestDomains": ["discord.com"] 17 + } 18 + } 19 + ]
+21
packages/browser/package.json
··· 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 + }
+84
packages/browser/src/background-mv2.js
··· 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 + ); 73 + 74 + chrome.webRequest.onHeadersReceived.addListener( 75 + (details) => { 76 + return { 77 + responseHeaders: details.responseHeaders.filter( 78 + (header) => header.name.toLowerCase() !== "content-security-policy" 79 + ) 80 + }; 81 + }, 82 + { urls: ["https://*.discord.com/*", "https://*.discordapp.com/*"] }, 83 + ["blocking", "responseHeaders"] 84 + );
+111
packages/browser/src/background.js
··· 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 { 41 + await chrome.scripting.executeScript({ 42 + target: { tabId: details.tabId }, 43 + world: "MAIN", 44 + files: ["index.js"] 45 + }); 46 + } catch (e) { 47 + console.error(e); 48 + } 49 + 50 + console.log("Initializing moonlight"); 51 + try { 52 + await chrome.scripting.executeScript({ 53 + target: { tabId: details.tabId }, 54 + world: "MAIN", 55 + func: async () => { 56 + try { 57 + await window._moonlightBrowserInit(); 58 + } catch (e) { 59 + console.error(e); 60 + } 61 + } 62 + }); 63 + } catch (e) { 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"); 78 + try { 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 + );
+161
packages/browser/src/index.ts
··· 1 + import "@moonlight-mod/web-preload"; 2 + import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 3 + import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; 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); 16 + return path.split("/"); 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 + basename(path) { 95 + const parts = getParts(path); 96 + return parts[parts.length - 1]; 97 + } 98 + }, 99 + // TODO 100 + addCors(url) {}, 101 + addBlocked(url) {} 102 + }; 103 + 104 + // Actual loading begins here 105 + let config = await readConfig(); 106 + initLogger(config); 107 + 108 + const extensions = await getExtensions(); 109 + const processedExtensions = await loadExtensions(extensions); 110 + 111 + const moonlightNode: MoonlightNode = { 112 + get config() { 113 + return config; 114 + }, 115 + extensions, 116 + processedExtensions, 117 + nativesCache: {}, 118 + isBrowser: true, 119 + events: createEventEmitter<NodeEventType, NodeEventPayloads>(), 120 + 121 + version: MOONLIGHT_VERSION, 122 + branch: MOONLIGHT_BRANCH as MoonlightBranch, 123 + 124 + getConfig(ext) { 125 + return getConfig(ext, config); 126 + }, 127 + getConfigOption(ext, name) { 128 + const manifest = getManifest(extensions, ext); 129 + return getConfigOption(ext, name, config, manifest?.settings); 130 + }, 131 + async setConfigOption(ext, name, value) { 132 + setConfigOption(config, ext, name, value); 133 + await this.writeConfig(config); 134 + }, 135 + 136 + getNatives: () => {}, 137 + getLogger: (id: string) => { 138 + return new Logger(id); 139 + }, 140 + 141 + getMoonlightDir() { 142 + return "/"; 143 + }, 144 + getExtensionDir: (ext: string) => { 145 + return `/extensions/${ext}`; 146 + }, 147 + 148 + async writeConfig(newConfig) { 149 + await writeConfig(newConfig); 150 + config = newConfig; 151 + this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig); 152 + } 153 + }; 154 + 155 + Object.assign(window, { 156 + moonlightNode 157 + }); 158 + 159 + // This is set by web-preload for us 160 + await window._moonlightWebLoad!(); 161 + };
+7
packages/browser/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["DOM", "ESNext", "ESNext.AsyncIterable"], 5 + "module": "ES2022" 6 + } 7 + }
+7
packages/core/package.json
··· 4 4 "exports": { 5 5 "./*": "./src/*.ts" 6 6 }, 7 + "engineStrict": true, 8 + "engines": { 9 + "node": ">=22", 10 + "pnpm": ">=10", 11 + "npm": "pnpm", 12 + "yarn": "pnpm" 13 + }, 7 14 "dependencies": { 8 15 "@moonlight-mod/types": "workspace:*" 9 16 }
+57
packages/core/src/asar.ts
··· 1 + // https://github.com/electron/asar 2 + // http://formats.kaitai.io/python_pickle/ 3 + import { BinaryReader } from "./util/binary"; 4 + 5 + /* 6 + The asar format is kinda bad, especially because it uses multiple pickle 7 + entries. It spams sizes, expecting us to read small buffers and parse those, 8 + but we can just take it all through at once without having to create multiple 9 + BinaryReaders. This implementation might be wrong, though. 10 + 11 + This either has size/offset or files but I can't get the type to cooperate, 12 + so pretend this is a union. 13 + */ 14 + 15 + type AsarEntry = { 16 + size: number; 17 + offset: `${number}`; // who designed this 18 + 19 + files?: Record<string, AsarEntry>; 20 + }; 21 + 22 + export default function extractAsar(file: ArrayBuffer) { 23 + const array = new Uint8Array(file); 24 + const br = new BinaryReader(array); 25 + 26 + // two uints, one containing the number '4', to signify that the other uint takes up 4 bytes 27 + // bravo, electron, bravo 28 + const _payloadSize = br.readUInt32(); 29 + const _headerSize = br.readInt32(); 30 + 31 + const headerStringStart = br.position; 32 + const headerStringSize = br.readUInt32(); // How big the block is 33 + const actualStringSize = br.readUInt32(); // How big the string in that block is 34 + 35 + const base = headerStringStart + headerStringSize + 4; 36 + 37 + const string = br.readString(actualStringSize); 38 + const header: AsarEntry = JSON.parse(string); 39 + 40 + const ret: Record<string, Uint8Array> = {}; 41 + function addDirectory(dir: AsarEntry, path: string) { 42 + for (const [name, data] of Object.entries(dir.files!)) { 43 + const fullName = path + "/" + name; 44 + if (data.files != null) { 45 + addDirectory(data, fullName); 46 + } else { 47 + br.position = base + parseInt(data.offset); 48 + const file = br.read(data.size); 49 + ret[fullName] = file; 50 + } 51 + } 52 + } 53 + 54 + addDirectory(header, ""); 55 + 56 + return ret; 57 + }
+31 -31
packages/core/src/config.ts
··· 1 1 import { Config } from "@moonlight-mod/types"; 2 - import requireImport from "./util/import"; 3 2 import { getConfigPath } from "./util/data"; 3 + import * as constants from "@moonlight-mod/types/constants"; 4 + import Logger from "./util/logger"; 5 + 6 + const logger = new Logger("core/config"); 4 7 5 8 const defaultConfig: Config = { 9 + // If you're updating this, update `builtinExtensions` in constants as well 6 10 extensions: { 7 11 moonbase: true, 8 12 disableSentry: true, 9 13 noTrack: true, 10 14 noHideToken: true 11 15 }, 12 - repositories: ["https://moonlight-mod.github.io/extensions-dist/repo.json"] 16 + repositories: [constants.mainRepo] 13 17 }; 14 18 15 - export function writeConfig(config: Config) { 16 - const fs = requireImport("fs"); 17 - const configPath = getConfigPath(); 18 - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); 19 - } 20 - 21 - function readConfigNode(): Config { 22 - const fs = requireImport("fs"); 23 - const configPath = getConfigPath(); 24 - 25 - if (!fs.existsSync(configPath)) { 26 - writeConfig(defaultConfig); 27 - return defaultConfig; 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); 28 25 } 29 - 30 - let config: Config = JSON.parse(fs.readFileSync(configPath, "utf8")); 31 - 32 - // Assign the default values if they don't exist (newly added) 33 - config = { ...defaultConfig, ...config }; 34 - writeConfig(config); 35 - 36 - return config; 37 26 } 38 27 39 - export function readConfig(): Config { 28 + export async function readConfig(): Promise<Config> { 40 29 webPreload: { 41 30 return moonlightNode.config; 42 31 } 43 32 44 - nodePreload: { 45 - return readConfigNode(); 46 - } 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); 47 43 48 - injector: { 49 - return readConfigNode(); 44 + return config; 45 + } catch (e) { 46 + logger.error("Failed to read config, falling back to defaults", e); 47 + // We don't want to write the default config here - if a user is manually 48 + // editing their config and messes it up, we'll delete it all instead of 49 + // letting them fix it 50 + return defaultConfig; 51 + } 50 52 } 51 - 52 - throw new Error("Called readConfig() in an impossible environment"); 53 53 }
+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 + }
+111 -70
packages/core/src/extension/loader.ts
··· 2 2 ExtensionWebExports, 3 3 DetectedExtension, 4 4 ProcessedExtensions, 5 - WebpackModuleFunc 5 + WebpackModuleFunc, 6 + constants, 7 + ExtensionManifest, 8 + ExtensionEnvironment 6 9 } from "@moonlight-mod/types"; 7 10 import { readConfig } from "../config"; 8 11 import Logger from "../util/logger"; ··· 10 13 import calculateDependencies from "../util/dependency"; 11 14 import { createEventEmitter } from "../util/event"; 12 15 import { registerStyles } from "../styles"; 16 + import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event"; 13 17 14 18 const logger = new Logger("core/extension/loader"); 15 19 16 - async function loadExt(ext: DetectedExtension) { 17 - webPreload: { 18 - if (ext.scripts.web != null) { 19 - const source = 20 - ext.scripts.web + "\n//# sourceURL=file:///" + ext.scripts.webPath; 21 - const fn = new Function("require", "module", "exports", source); 20 + function evalIIFE(id: string, source: string): ExtensionWebExports { 21 + const fn = new Function("require", "module", "exports", source); 22 22 23 - const module = { id: ext.id, exports: {} }; 24 - fn.apply(window, [ 25 - () => { 26 - logger.warn("Attempted to require() from web"); 27 - }, 28 - module, 29 - module.exports 30 - ]); 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; 31 51 32 - const exports: ExtensionWebExports = module.exports; 33 - if (exports.patches != null) { 34 - let idx = 0; 35 - for (const patch of exports.patches) { 36 - if (Array.isArray(patch.replace)) { 37 - for (const replacement of patch.replace) { 38 - const newPatch = Object.assign({}, patch, { 39 - replace: replacement 40 - }); 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 + } 41 58 42 - registerPatch({ ...newPatch, ext: ext.id, id: idx }); 43 - idx++; 44 - } 45 - } else { 46 - registerPatch({ ...patch, ext: ext.id, id: idx }); 47 - idx++; 48 - } 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 }); 49 66 } 67 + idx++; 50 68 } 69 + } 51 70 52 - if (exports.webpackModules != null) { 53 - for (const [name, wp] of Object.entries(exports.webpackModules)) { 54 - if (wp.run == null && ext.scripts.webpackModules?.[name] != null) { 55 - const func = new Function( 56 - "module", 57 - "exports", 58 - "require", 59 - ext.scripts.webpackModules[name]! 60 - ) as WebpackModuleFunc; 61 - registerWebpackModule({ 62 - ...wp, 63 - ext: ext.id, 64 - id: name, 65 - run: func 66 - }); 67 - } else { 68 - registerWebpackModule({ ...wp, ext: ext.id, id: name }); 69 - } 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, 79 + id: name, 80 + run: func 81 + }); 82 + } else { 83 + registerWebpackModule({ ...wp, ext: ext.id, id: name }); 70 84 } 71 85 } 86 + } 72 87 73 - if (exports.styles != null) { 74 - registerStyles( 75 - exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`) 76 - ); 77 - } 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); 78 103 } 79 104 } 80 105 ··· 100 125 } 101 126 } 102 127 128 + export enum ExtensionCompat { 129 + Compatible, 130 + InvalidApiLevel, 131 + InvalidEnvironment 132 + } 133 + 134 + export function checkExtensionCompat(manifest: ExtensionManifest): ExtensionCompat { 135 + let environment; 136 + webTarget: { 137 + environment = ExtensionEnvironment.Web; 138 + } 139 + nodeTarget: { 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 + } 148 + 103 149 /* 104 150 This function resolves extensions and loads them, split into a few stages: 105 151 ··· 114 160 extensions fires an event on completion, which allows us to await the loading 115 161 of another extension, resolving dependencies & load order effectively. 116 162 */ 117 - export async function loadExtensions( 118 - exts: DetectedExtension[] 119 - ): Promise<ProcessedExtensions> { 120 - const config = readConfig(); 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(); 121 167 const items = exts 122 168 .map((ext) => { 123 169 return { ··· 155 201 }; 156 202 } 157 203 158 - export async function loadProcessedExtensions({ 159 - extensions, 160 - dependencyGraph 161 - }: ProcessedExtensions) { 162 - const eventEmitter = createEventEmitter(); 204 + export async function loadProcessedExtensions({ extensions, dependencyGraph }: ProcessedExtensions) { 205 + const eventEmitter = createEventEmitter<WebEventType, WebEventPayloads>(); 163 206 const finished: Set<string> = new Set(); 164 207 165 208 logger.trace( ··· 181 224 } 182 225 183 226 function done() { 184 - eventEmitter.removeEventListener("ext-ready", cb); 227 + eventEmitter.removeEventListener(WebEventType.ExtensionLoad, cb); 185 228 r(); 186 229 } 187 230 188 - eventEmitter.addEventListener("ext-ready", cb); 231 + eventEmitter.addEventListener(WebEventType.ExtensionLoad, cb); 189 232 if (finished.has(dep)) done(); 190 233 }) 191 234 ); 192 235 193 236 if (waitPromises.length > 0) { 194 - logger.debug( 195 - `Waiting on ${waitPromises.length} dependencies for "${ext.id}"` 196 - ); 237 + logger.debug(`Waiting on ${waitPromises.length} dependencies for "${ext.id}"`); 197 238 await Promise.all(waitPromises); 198 239 } 199 240 ··· 201 242 await loadExt(ext); 202 243 203 244 finished.add(ext.id); 204 - eventEmitter.dispatchEvent("ext-ready", ext.id); 245 + eventEmitter.dispatchEvent(WebEventType.ExtensionLoad, ext.id); 205 246 logger.debug(`Loaded "${ext.id}"`); 206 247 } 207 248 208 - webPreload: { 249 + webTarget: { 209 250 for (const ext of extensions) { 210 251 moonlight.enabledExtensions.add(ext.id); 211 252 }
+128 -85
packages/core/src/extension.ts
··· 1 - import { 2 - ExtensionManifest, 3 - DetectedExtension, 4 - ExtensionLoadSource, 5 - constants 6 - } from "@moonlight-mod/types"; 1 + import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, constants } from "@moonlight-mod/types"; 7 2 import { readConfig } from "./config"; 8 - import requireImport from "./util/import"; 9 3 import { getCoreExtensionsPath, getExtensionsPath } from "./util/data"; 4 + import Logger from "./util/logger"; 5 + 6 + const logger = new Logger("core/extension"); 10 7 11 - function findManifests(dir: string): string[] { 12 - const fs = requireImport("fs"); 13 - const path = requireImport("path"); 8 + async function findManifests(dir: string): Promise<string[]> { 14 9 const ret = []; 15 10 16 - if (fs.existsSync(dir)) { 17 - for (const file of fs.readdirSync(dir)) { 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); 18 14 if (file === "manifest.json") { 19 - ret.push(path.join(dir, file)); 15 + ret.push(path); 20 16 } 21 17 22 - if (fs.statSync(path.join(dir, file)).isDirectory()) { 23 - ret.push(...findManifests(path.join(dir, file))); 18 + if (!(await moonlightNodeSandboxed.fs.isFile(path))) { 19 + ret.push(...(await findManifests(path))); 24 20 } 25 21 } 26 22 } ··· 28 24 return ret; 29 25 } 30 26 31 - function loadDetectedExtensions( 27 + async function loadDetectedExtensions( 32 28 dir: string, 33 - type: ExtensionLoadSource 34 - ): DetectedExtension[] { 35 - const fs = requireImport("fs"); 36 - const path = requireImport("path"); 29 + type: ExtensionLoadSource, 30 + seen: Set<string> 31 + ): Promise<DetectedExtension[]> { 37 32 const ret: DetectedExtension[] = []; 38 33 39 - const manifests = findManifests(dir); 34 + const manifests = await findManifests(dir); 40 35 for (const manifestPath of manifests) { 41 - if (!fs.existsSync(manifestPath)) continue; 42 - const dir = path.dirname(manifestPath); 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); 43 74 44 - const manifest: ExtensionManifest = JSON.parse( 45 - fs.readFileSync(manifestPath, "utf8") 46 - ); 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 + } 47 83 48 - const webPath = path.join(dir, "index.js"); 49 - const nodePath = path.join(dir, "node.js"); 50 - const hostPath = path.join(dir, "host.js"); 84 + const stylePath = moonlightNodeSandboxed.fs.join(dir, "style.css"); 51 85 52 - // if none exist (empty manifest) don't give a shit 53 - if ( 54 - !fs.existsSync(webPath) && 55 - !fs.existsSync(nodePath) && 56 - !fs.existsSync(hostPath) 57 - ) { 58 - continue; 86 + ret.push({ 87 + id: manifest.id, 88 + manifest, 89 + source: { 90 + type, 91 + url 92 + }, 93 + scripts: { 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); 59 106 } 107 + } 108 + 109 + return ret; 110 + } 111 + 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 + } 60 122 61 - const web = fs.existsSync(webPath) 62 - ? fs.readFileSync(webPath, "utf8") 63 - : undefined; 123 + res.push(...(await loadDetectedExtensions(await getExtensionsPath(), ExtensionLoadSource.Normal, seen))); 64 124 65 - let url: string | undefined = undefined; 66 - const urlPath = path.join(dir, constants.repoUrlFile); 67 - if (type === ExtensionLoadSource.Normal && fs.existsSync(urlPath)) { 68 - url = fs.readFileSync(urlPath, "utf8"); 69 - } 125 + return res; 126 + } 70 127 71 - const wpModules: Record<string, string> = {}; 72 - const wpModulesPath = path.join(dir, "webpackModules"); 73 - if (fs.existsSync(wpModulesPath)) { 74 - const wpModulesFile = fs.readdirSync(wpModulesPath); 128 + async function getExtensionsBrowser(): Promise<DetectedExtension[]> { 129 + const ret: DetectedExtension[] = []; 130 + const seen = new Set<string>(); 75 131 76 - for (const wpModuleFile of wpModulesFile) { 77 - if (wpModuleFile.endsWith(".js")) { 78 - wpModules[wpModuleFile.replace(".js", "")] = fs.readFileSync( 79 - path.join(wpModulesPath, wpModuleFile), 80 - "utf8" 81 - ); 82 - } 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; 137 + const manifest = JSON.parse(coreExtensionsFs[`${ext}/manifest.json`]); 138 + const web = coreExtensionsFs[`${ext}/index.js`]; 139 + 140 + const wpModules: Record<string, string> = {}; 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]; 83 145 } 84 146 } 85 147 ··· 87 149 id: manifest.id, 88 150 manifest, 89 151 source: { 90 - type, 91 - url 152 + type: ExtensionLoadSource.Core 92 153 }, 93 154 scripts: { 94 155 web, 95 - webPath: web != null ? webPath : undefined, 96 156 webpackModules: wpModules, 97 - nodePath: fs.existsSync(nodePath) ? nodePath : undefined, 98 - hostPath: fs.existsSync(hostPath) ? hostPath : undefined 157 + style: coreExtensionsFs[`${ext}/style.css`] 99 158 } 100 159 }); 160 + seen.add(manifest.id); 101 161 } 102 162 103 - return ret; 104 - } 105 - 106 - function getExtensionsNative(): DetectedExtension[] { 107 - const config = readConfig(); 108 - const res = []; 109 - 110 - res.push( 111 - ...loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core) 112 - ); 113 - 114 - res.push( 115 - ...loadDetectedExtensions(getExtensionsPath(), ExtensionLoadSource.Normal) 116 - ); 117 - 118 - for (const devSearchPath of config.devSearchPaths ?? []) { 119 - res.push( 120 - ...loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer) 121 - ); 163 + if (await moonlightNodeSandboxed.fs.exists("/extensions")) { 164 + ret.push(...(await loadDetectedExtensions("/extensions", ExtensionLoadSource.Normal, seen))); 122 165 } 123 166 124 - return res; 167 + return ret; 125 168 } 126 169 127 - export function getExtensions(): DetectedExtension[] { 170 + export async function getExtensions(): Promise<DetectedExtension[]> { 128 171 webPreload: { 129 172 return moonlightNode.extensions; 130 173 } 131 174 132 - nodePreload: { 133 - return getExtensionsNative(); 175 + browser: { 176 + return await getExtensionsBrowser(); 134 177 } 135 178 136 - injector: { 137 - return getExtensionsNative(); 179 + nodeTarget: { 180 + return await getExtensionsNative(); 138 181 } 139 182 140 183 throw new Error("Called getExtensions() outside of node-preload/web-preload");
+56
packages/core/src/fs.ts
··· 1 + import type { MoonlightFS } from "@moonlight-mod/types"; 2 + import requireImport from "./util/import"; 3 + 4 + export default function createFS(): MoonlightFS { 5 + const fs = requireImport("fs"); 6 + const path = requireImport("path"); 7 + 8 + return { 9 + async readFile(path) { 10 + const file = fs.readFileSync(path); 11 + return new Uint8Array(file); 12 + }, 13 + async readFileString(path) { 14 + return fs.readFileSync(path, "utf8"); 15 + }, 16 + async writeFile(path, data) { 17 + fs.writeFileSync(path, Buffer.from(data)); 18 + }, 19 + async writeFileString(path, data) { 20 + fs.writeFileSync(path, data, "utf8"); 21 + }, 22 + async unlink(path) { 23 + fs.unlinkSync(path); 24 + }, 25 + 26 + async readdir(path) { 27 + return fs.readdirSync(path); 28 + }, 29 + async mkdir(path) { 30 + fs.mkdirSync(path, { recursive: true }); 31 + }, 32 + async rmdir(path) { 33 + fs.rmSync(path, { recursive: true }); 34 + }, 35 + 36 + async exists(path) { 37 + return fs.existsSync(path); 38 + }, 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); 48 + }, 49 + dirname(dir) { 50 + return path.dirname(dir); 51 + }, 52 + basename(dir) { 53 + return path.basename(dir); 54 + } 55 + }; 56 + }
+238 -106
packages/core/src/patch.ts
··· 6 6 IdentifiedWebpackModule, 7 7 WebpackJsonp, 8 8 WebpackJsonpEntry, 9 - WebpackModuleFunc 9 + WebpackModuleFunc, 10 + WebpackRequireType 10 11 } from "@moonlight-mod/types"; 11 12 import Logger from "./util/logger"; 12 13 import calculateDependencies, { Dependency } from "./util/dependency"; 13 - import WebpackRequire from "@moonlight-mod/types/discord/require"; 14 + import { WebEventType } from "@moonlight-mod/types/core/event"; 15 + import { processFind, processReplace, testFind } from "./util/patch"; 14 16 15 17 const logger = new Logger("core/patch"); 16 18 17 19 // Can't be Set because we need splice 18 20 const patches: IdentifiedPatch[] = []; 19 21 let webpackModules: Set<IdentifiedWebpackModule> = new Set(); 22 + let webpackRequire: WebpackRequireType | null = null; 23 + 24 + const moduleLoadSubscriptions: Map<string, ((moduleId: string) => void)[]> = new Map(); 20 25 21 26 export function registerPatch(patch: IdentifiedPatch) { 27 + patch.find = processFind(patch.find); 28 + processReplace(patch.replace); 29 + 22 30 patches.push(patch); 23 31 moonlight.unpatched.add(patch); 24 32 } ··· 30 38 } 31 39 } 32 40 41 + export function onModuleLoad(module: string | string[], callback: (moduleId: string) => void): void { 42 + let moduleIds = module; 43 + 44 + if (typeof module === "string") { 45 + moduleIds = [module]; 46 + } 47 + 48 + for (const moduleId of moduleIds) { 49 + if (moduleLoadSubscriptions.has(moduleId)) { 50 + moduleLoadSubscriptions.get(moduleId)?.push(callback); 51 + } else { 52 + moduleLoadSubscriptions.set(moduleId, [callback]); 53 + } 54 + } 55 + } 56 + 33 57 /* 34 58 The patching system functions by matching a string or regex against the 35 59 .toString()'d copy of a Webpack module. When a patch happens, we reconstruct ··· 42 66 const moduleCache: Record<string, string> = {}; 43 67 const patched: Record<string, Array<string>> = {}; 44 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 + 45 103 function patchModules(entry: WebpackJsonpEntry[1]) { 104 + // Populate the module cache 46 105 for (const [id, func] of Object.entries(entry)) { 47 - let moduleString = Object.prototype.hasOwnProperty.call(moduleCache, id) 48 - ? moduleCache[id] 49 - : func.toString().replace(/\n/g, ""); 106 + if (!Object.hasOwn(moduleCache, id) && func.__moonlight !== true) { 107 + moduleCache[id] = func.toString().replace(/\n/g, ""); 108 + moonlight.moonmap.parseScript(id, moduleCache[id]); 109 + } 110 + } 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>(); 50 124 51 125 for (let i = 0; i < patches.length; i++) { 52 126 const patch = patches[i]; 53 127 if (patch.prerequisite != null && !patch.prerequisite()) { 128 + moonlight.unpatched.delete(patch); 54 129 continue; 55 130 } 56 131 ··· 59 134 patch.find.lastIndex = 0; 60 135 } 61 136 62 - // indexOf is faster than includes by 0.25% lmao 63 - const match = 64 - typeof patch.find === "string" 65 - ? moduleString.indexOf(patch.find) !== -1 66 - : patch.find.test(moduleString); 137 + const match = testFind(origModuleString, patch.find) || patch.find === mappedName; 67 138 68 139 // Global regexes apply to all modules 69 - const shouldRemove = 70 - typeof patch.find === "string" ? true : !patch.find.global; 140 + const shouldRemove = typeof patch.find === "string" ? true : !patch.find.global; 71 141 142 + let replaced = moduleString; 143 + let hardFailed = false; 72 144 if (match) { 73 - moonlight.unpatched.delete(patch); 145 + // We ensured normal PatchReplace objects get turned into arrays on register 146 + const replaces = patch.replace as PatchReplace[]; 74 147 75 - // We ensured all arrays get turned into normal PatchReplace objects on register 76 - const replace = patch.replace as PatchReplace; 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); 77 154 78 - if ( 79 - replace.type === undefined || 80 - replace.type === PatchReplaceType.Normal 81 - ) { 82 - // Add support for \i to match rspack's minified names 83 - if (typeof replace.match !== "string") { 84 - replace.match = new RegExp( 85 - replace.match.source.replace(/\\i/g, "[A-Za-z_$][\\w$]*"), 86 - replace.match.flags 87 - ); 88 - } 89 - // tsc fails to detect the overloads for this, so I'll just do this 90 - // Verbose, but it works 91 - let replaced; 92 - if (typeof replace.replacement === "string") { 93 - replaced = moduleString.replace(replace.match, replace.replacement); 94 - } else { 95 - replaced = moduleString.replace(replace.match, replace.replacement); 96 - } 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 + } 97 163 98 - if (replaced === moduleString) { 99 - logger.warn("Patch replacement failed", id, patch); 100 - continue; 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; 101 181 } 182 + } 102 183 103 - // Store what extensions patched what modules for easier debugging 104 - patched[id] = patched[id] || []; 105 - patched[id].push(`${patch.ext}#${patch.id}`); 184 + if (!hardFailed) { 185 + moduleString = replaced; 186 + modified = true; 187 + exts.add(patch.ext); 188 + } 106 189 107 - // Webpack module arguments are minified, so we replace them with consistent names 108 - // We have to wrap it so things don't break, though 109 - const patchedStr = patched[id].sort().join(", "); 190 + if (isPatched) moonlight.unpatched.delete(patch); 191 + if (shouldRemove) patches.splice(i--, 1); 192 + } 193 + } 110 194 111 - const wrapped = 112 - `(${replaced}).apply(this, arguments)\n` + 113 - `// Patched by moonlight: ${patchedStr}\n` + 114 - `//# sourceURL=Webpack-Module-${id}`; 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 + } 115 201 116 - try { 117 - const func = new Function( 118 - "module", 119 - "exports", 120 - "require", 121 - wrapped 122 - ) as WebpackModuleFunc; 123 - entry[id] = func; 124 - entry[id].__moonlight = true; 125 - moduleString = replaced; 126 - } catch (e) { 127 - logger.warn("Error constructing function for patch", patch, e); 128 - patched[id].pop(); 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; 129 208 } 130 - } else if (replace.type === PatchReplaceType.Module) { 131 - // Directly replace the module with a new one 132 - const newModule = replace.replacement(moduleString); 133 - entry[id] = newModule; 134 - entry[id].__moonlight = true; 135 - moduleString = 136 - newModule.toString().replace(/\n/g, "") + 137 - `//# sourceURL=Webpack-Module-${id}`; 138 - } 139 - 140 - if (shouldRemove) { 141 - patches.splice(i--, 1); 142 209 } 143 210 } 211 + } catch (e) { 212 + logger.error("Failed to parse script for LunAST", id, e); 144 213 } 145 214 146 215 if (moonlightNode.config.patchAll === true) { 147 - if ( 148 - (typeof id !== "string" || !id.includes("_")) && 149 - !entry[id].__moonlight 150 - ) { 151 - const wrapped = 152 - `(${moduleString}).apply(this, arguments)\n` + 153 - `//# sourceURL=Webpack-Module-${id}`; 154 - entry[id] = new Function( 155 - "module", 156 - "exports", 157 - "require", 158 - wrapped 159 - ) as WebpackModuleFunc; 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; 160 219 entry[id].__moonlight = true; 161 220 } 162 221 } 163 222 223 + // Dispatch module load event subscription 224 + if (moduleLoadSubscriptions.has(id)) { 225 + const loadCallbacks = moduleLoadSubscriptions.get(id)!; 226 + for (const callback of loadCallbacks) { 227 + try { 228 + callback(id); 229 + } catch (e) { 230 + logger.error("Error in module load subscription: " + e); 231 + } 232 + } 233 + moduleLoadSubscriptions.delete(id); 234 + } 235 + 164 236 moduleCache[id] = moduleString; 165 237 } 166 238 } ··· 172 244 */ 173 245 let chunkId = Number.MAX_SAFE_INTEGER; 174 246 247 + function depToString(x: ExplicitExtensionDependency) { 248 + return x.ext != null ? `${x.ext}_${x.id}` : x.id; 249 + } 250 + 175 251 function handleModuleDependencies() { 176 252 const modules = Array.from(webpackModules.values()); 177 253 178 - const dependencies: Dependency<string, IdentifiedWebpackModule>[] = 179 - modules.map((wp) => { 180 - return { 181 - id: `${wp.ext}_${wp.id}`, 182 - data: wp 183 - }; 184 - }); 254 + const dependencies: Dependency<string, IdentifiedWebpackModule>[] = modules.map((wp) => { 255 + return { 256 + id: depToString(wp), 257 + data: wp 258 + }; 259 + }); 185 260 186 261 const [sorted, _] = calculateDependencies(dependencies, { 187 262 fetchDep: (id) => { 188 - return modules.find((x) => id === `${x.ext}_${x.id}`) ?? null; 263 + return modules.find((x) => id === depToString(x)) ?? null; 189 264 }, 190 265 191 266 getDeps: (item) => { 192 267 const deps = item.data?.dependencies ?? []; 193 268 return ( 194 269 deps.filter( 195 - (dep) => !(dep instanceof RegExp || typeof dep === "string") 270 + (dep) => !(dep instanceof RegExp || typeof dep === "string") && dep.ext != null 196 271 ) as ExplicitExtensionDependency[] 197 - ).map((x) => `${x.ext}_${x.id}`); 272 + ).map(depToString); 198 273 } 199 274 }); 200 275 ··· 210 285 for (const [_modId, mod] of Object.entries(entry)) { 211 286 const modStr = mod.toString(); 212 287 for (const wpModule of webpackModules) { 213 - const id = wpModule.ext + "_" + wpModule.id; 288 + const id = depToString(wpModule); 214 289 if (wpModule.dependencies) { 215 290 const deps = new Set(wpModule.dependencies); 216 291 ··· 223 298 } else if (dep instanceof RegExp) { 224 299 if (dep.test(modStr)) deps.delete(dep); 225 300 } else if ( 226 - injectedWpModules.find( 227 - (x) => x.ext === dep.ext && x.id === dep.id 228 - ) 301 + dep.ext != null 302 + ? injectedWpModules.find((x) => x.ext === dep.ext && x.id === dep.id) 303 + : injectedWpModules.find((x) => x.id === dep.id) 229 304 ) { 230 305 deps.delete(dep); 231 306 } 232 307 } 233 308 309 + wpModule.dependencies = Array.from(deps); 234 310 if (deps.size !== 0) { 235 - wpModule.dependencies = Array.from(deps); 236 311 continue; 237 312 } 238 - 239 - wpModule.dependencies = Array.from(deps); 240 313 } 241 314 } 242 315 ··· 249 322 if (wpModule.run) { 250 323 modules[id] = wpModule.run; 251 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); 252 334 } 253 - if (wpModule.entrypoint) entrypoints.push(id); 254 335 } 255 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; 345 + } 346 + 347 + if (webpackRequire != null) { 348 + for (const id of moonlight.moonmap.getLazyModules()) { 349 + webpackRequire.e(id); 350 + } 256 351 } 257 352 258 353 if (inject) { ··· 260 355 window.webpackChunkdiscord_app.push([ 261 356 [--chunkId], 262 357 modules, 263 - (require: typeof WebpackRequire) => entrypoints.map(require) 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 + }) 264 370 ]); 265 371 } 266 372 } ··· 269 375 interface Window { 270 376 webpackChunkdiscord_app: WebpackJsonp; 271 377 } 378 + } 379 + 380 + function moduleSourceGetter(id: string) { 381 + return moduleCache[id] ?? null; 272 382 } 273 383 274 384 /* ··· 282 392 export async function installWebpackPatcher() { 283 393 await handleModuleDependencies(); 284 394 395 + moonlight.lunast.setModuleSourceGetter(moduleSourceGetter); 396 + moonlight.moonmap.setModuleSourceGetter(moduleSourceGetter); 397 + 398 + const wpRequireFetcher: WebpackModuleFunc = (module, exports, require) => { 399 + webpackRequire = require; 400 + }; 401 + wpRequireFetcher.__moonlight = true; 402 + webpackModules.add({ 403 + id: "moonlight", 404 + entrypoint: true, 405 + run: wpRequireFetcher 406 + }); 407 + 285 408 let realWebpackJsonp: WebpackJsonp | null = null; 286 409 Object.defineProperty(window, "webpackChunkdiscord_app", { 287 410 set: (jsonp: WebpackJsonp) => { ··· 293 416 const realPush = jsonp.push; 294 417 if (jsonp.push.__moonlight !== true) { 295 418 jsonp.push = (items) => { 419 + moonlight.events.dispatchEvent(WebEventType.ChunkLoad, { 420 + chunkId: items[0], 421 + modules: items[1], 422 + require: items[2] 423 + }); 424 + 296 425 patchModules(items[1]); 297 426 298 427 try { ··· 335 464 set(modules: any) { 336 465 const { stack } = new Error(); 337 466 if (stack!.includes("/assets/") && !Array.isArray(modules)) { 467 + moonlight.events.dispatchEvent(WebEventType.ChunkLoad, { 468 + modules: modules 469 + }); 338 470 patchModules(modules); 339 - if (!window.webpackChunkdiscord_app) 340 - window.webpackChunkdiscord_app = []; 471 + 472 + if (!window.webpackChunkdiscord_app) window.webpackChunkdiscord_app = []; 341 473 injectModules(modules); 342 474 } 343 475
+49
packages/core/src/persist.ts
··· 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"); 6 + 7 + export default function persist(asarPath: string) { 8 + try { 9 + if (process.platform === "win32") { 10 + persistWin32(asarPath); 11 + } 12 + } catch (e) { 13 + logger.error(`Failed to persist moonlight: ${e}`); 14 + } 15 + } 16 + 17 + function persistWin32(asarPath: string) { 18 + const updaterModule = require(join(asarPath, "common", "updater")); 19 + const updater = updaterModule.Updater; 20 + 21 + const currentAppDir = join(dirname(asarPath), "app"); 22 + 23 + const realEmit = updater.prototype.emit; 24 + updater.prototype.emit = function (event: string, ...args: any[]) { 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"); 32 + 33 + // app.asar -> _app.asar 34 + const newAsar = join(newResources, "app.asar"); 35 + const newRenamedAsar = join(newResources, "_app.asar"); 36 + if (!existsSync(newRenamedAsar)) renameSync(newAsar, newRenamedAsar); 37 + 38 + // copy the already existing app dir so we don't have to figure out the moonlight dir 39 + const newAppDir = join(newResources, "app"); 40 + if (!existsSync(newAppDir)) mkdirSync(newAppDir); 41 + 42 + for (const file of readdirSync(currentAppDir)) { 43 + copyFileSync(join(currentAppDir, file), join(newAppDir, file)); 44 + } 45 + } 46 + 47 + return realEmit.call(this, event, ...args); 48 + }; 49 + }
+63
packages/core/src/util/binary.ts
··· 1 + // https://github.com/NotNite/brc-save-editor/blob/main/src/lib/binary.ts 2 + export interface BinaryInterface { 3 + data: Uint8Array; 4 + view: DataView; 5 + length: number; 6 + position: number; 7 + } 8 + 9 + export class BinaryReader implements BinaryInterface { 10 + data: Uint8Array; 11 + view: DataView; 12 + length: number; 13 + position: number; 14 + 15 + constructor(data: Uint8Array) { 16 + this.data = data; 17 + this.view = new DataView(data.buffer); 18 + 19 + this.length = data.length; 20 + this.position = 0; 21 + } 22 + 23 + readByte() { 24 + return this._read(this.view.getInt8, 1); 25 + } 26 + 27 + readBoolean() { 28 + return this.readByte() !== 0; 29 + } 30 + 31 + readInt32() { 32 + return this._read(this.view.getInt32, 4); 33 + } 34 + 35 + readUInt32() { 36 + return this._read(this.view.getUint32, 4); 37 + } 38 + 39 + readSingle() { 40 + return this._read(this.view.getFloat32, 4); 41 + } 42 + 43 + readInt64() { 44 + return this._read(this.view.getBigInt64, 8); 45 + } 46 + 47 + readString(length: number) { 48 + const result = this.read(length); 49 + return new TextDecoder().decode(result); 50 + } 51 + 52 + read(length: number) { 53 + const data = this.data.subarray(this.position, this.position + length); 54 + this.position += length; 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; 62 + } 63 + }
packages/core/src/util/clone.ts

This is a binary file and will not be displayed.

+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 + }
+25 -28
packages/core/src/util/data.ts
··· 1 1 import { constants } from "@moonlight-mod/types"; 2 - import requireImport from "./import"; 2 + 3 + export async function getMoonlightDir() { 4 + browser: { 5 + return "/"; 6 + } 3 7 4 - export function getMoonlightDir(): string { 5 8 const electron = require("electron"); 6 - const fs = requireImport("fs"); 7 - const path = requireImport("path"); 8 9 9 10 let appData = ""; 10 11 injector: { ··· 15 16 appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData); 16 17 } 17 18 18 - const dir = path.join(appData, "moonlight-mod"); 19 - if (!fs.existsSync(dir)) fs.mkdirSync(dir); 19 + const dir = moonlightNodeSandboxed.fs.join(appData, "moonlight-mod"); 20 + if (!(await moonlightNodeSandboxed.fs.exists(dir))) await moonlightNodeSandboxed.fs.mkdir(dir); 20 21 21 22 return dir; 22 23 } ··· 26 27 version: string; 27 28 }; 28 29 29 - export function getConfigPath(): string { 30 - const dir = getMoonlightDir(); 31 - const fs = requireImport("fs"); 32 - const path = requireImport("path"); 30 + export async function getConfigPath() { 31 + browser: { 32 + return "/config.json"; 33 + } 34 + 35 + const dir = await getMoonlightDir(); 33 36 34 37 let configPath = ""; 35 38 36 - const buildInfoPath = path.join(process.resourcesPath, "build_info.json"); 37 - if (!fs.existsSync(buildInfoPath)) { 38 - configPath = path.join(dir, "desktop.json"); 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"); 39 42 } else { 40 - const buildInfo: BuildInfo = JSON.parse( 41 - fs.readFileSync(buildInfoPath, "utf8") 42 - ); 43 - configPath = path.join(dir, buildInfo.releaseChannel + ".json"); 43 + const buildInfo: BuildInfo = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(buildInfoPath)); 44 + configPath = moonlightNodeSandboxed.fs.join(dir, buildInfo.releaseChannel + ".json"); 44 45 } 45 46 46 47 return configPath; 47 48 } 48 49 49 - function getPathFromMoonlight(...names: string[]): string { 50 - const dir = getMoonlightDir(); 51 - const fs = requireImport("fs"); 52 - const path = requireImport("path"); 50 + async function getPathFromMoonlight(...names: string[]) { 51 + const dir = await getMoonlightDir(); 53 52 54 - const target = path.join(dir, ...names); 55 - if (!fs.existsSync(target)) fs.mkdirSync(target); 53 + const target = moonlightNodeSandboxed.fs.join(dir, ...names); 54 + if (!(await moonlightNodeSandboxed.fs.exists(target))) await moonlightNodeSandboxed.fs.mkdir(target); 56 55 57 56 return target; 58 57 } 59 58 60 - export function getExtensionsPath(): string { 61 - return getPathFromMoonlight(constants.extensionsDir); 59 + export async function getExtensionsPath() { 60 + return await getPathFromMoonlight(constants.extensionsDir); 62 61 } 63 62 64 63 export function getCoreExtensionsPath(): string { 65 - const path = requireImport("path"); 66 - const a = path.join(__dirname, constants.coreExtensionsDir); 67 - return a; 64 + return moonlightNodeSandboxed.fs.join(__dirname, constants.coreExtensionsDir); 68 65 }
+4 -14
packages/core/src/util/dependency.ts
··· 35 35 const fullDeps: Set<T> = new Set(); 36 36 let failed = false; 37 37 38 - // eslint-disable-next-line no-inner-declarations 39 38 function resolveDeps(id: T, root: boolean) { 40 39 if (id === item.id && !root) { 41 40 logger.warn(`Circular dependency detected: "${item.id}"`); ··· 113 112 logger.trace("Enabled stage", itemsOrig); 114 113 const implicitlyEnabled: T[] = []; 115 114 116 - // eslint-disable-next-line no-inner-declarations 117 115 function validateDeps(dep: Dependency<T, D>) { 118 116 if (getEnabled!(dep)) { 119 117 const deps = dependencyGraphOrig.get(dep.id)!; ··· 122 120 validateDeps({ id, data }); 123 121 } 124 122 } else { 125 - const dependsOnMe = Array.from(dependencyGraphOrig.entries()).filter( 126 - ([, v]) => v?.has(dep.id) 127 - ); 123 + const dependsOnMe = Array.from(dependencyGraphOrig.entries()).filter(([, v]) => v?.has(dep.id)); 128 124 129 125 if (dependsOnMe.length > 0) { 130 126 logger.debug("Implicitly enabling dependency", dep.id); ··· 134 130 } 135 131 136 132 for (const dep of itemsOrig) validateDeps(dep); 137 - itemsOrig = itemsOrig.filter( 138 - (x) => getEnabled(x) || implicitlyEnabled.includes(x.id) 139 - ); 133 + itemsOrig = itemsOrig.filter((x) => getEnabled(x) || implicitlyEnabled.includes(x.id)); 140 134 } 141 135 142 136 if (getIncompatible != null) { ··· 176 170 dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id))); 177 171 } 178 172 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 - ); 173 + while (Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0) { 174 + const noDependents = items.filter((e) => dependencyGraph.get(e.id)?.size === 0); 185 175 186 176 if (noDependents.length === 0) { 187 177 logger.warn("Stuck dependency graph detected", dependencyGraph);
+48 -54
packages/core/src/util/event.ts
··· 1 - export type MoonlightEventCallback = (data: string) => void; 1 + import { MoonlightEventEmitter } from "@moonlight-mod/types/core/event"; 2 2 3 - export interface MoonlightEventEmitter { 4 - dispatchEvent: (id: string, data: string) => void; 5 - addEventListener: (id: string, cb: MoonlightEventCallback) => void; 6 - removeEventListener: (id: string, cb: MoonlightEventCallback) => void; 7 - } 3 + export function createEventEmitter< 4 + EventId extends string = string, 5 + EventData = Record<EventId, any> 6 + >(): MoonlightEventEmitter<EventId, EventData> { 7 + webTarget: { 8 + const eventEmitter = new EventTarget(); 9 + const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); 8 10 9 - function nodeMethod(): MoonlightEventEmitter { 10 - const EventEmitter = require("events"); 11 - const eventEmitter = new EventEmitter(); 12 - const listeners = new Map<MoonlightEventCallback, (...args: any[]) => void>(); 11 + return { 12 + dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => { 13 + eventEmitter.dispatchEvent(new CustomEvent(id as string, { detail: data })); 14 + }, 13 15 14 - return { 15 - dispatchEvent: (id: string, data: string) => { 16 - eventEmitter.emit(id, data); 17 - }, 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; 18 19 19 - addEventListener: (id: string, cb: (data: string) => void) => { 20 - if (listeners.has(cb)) return; 20 + function listener(e: Event) { 21 + const event = e as CustomEvent<string>; 22 + cb(event.detail as EventData[Id]); 23 + } 21 24 22 - function listener(data: string) { 23 - cb(data); 24 - } 25 + listeners.set(untyped, listener); 26 + eventEmitter.addEventListener(id as string, listener); 27 + }, 25 28 26 - listeners.set(cb, listener); 27 - eventEmitter.on(id, listener); 28 - }, 29 - 30 - removeEventListener: (id: string, cb: (data: string) => void) => { 31 - const listener = listeners.get(cb); 32 - if (listener == null) return; 33 - listeners.delete(cb); 34 - eventEmitter.off(id, listener); 35 - } 36 - }; 37 - } 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; 33 + listeners.delete(untyped); 34 + eventEmitter.removeEventListener(id as string, listener); 35 + } 36 + }; 37 + } 38 38 39 - export function createEventEmitter(): MoonlightEventEmitter { 40 - webPreload: { 41 - const eventEmitter = new EventTarget(); 42 - const listeners = new Map<MoonlightEventCallback, (e: Event) => void>(); 39 + nodeTarget: { 40 + const EventEmitter = require("events"); 41 + const eventEmitter = new EventEmitter(); 42 + const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); 43 43 44 44 return { 45 - dispatchEvent: (id: string, data: string) => { 46 - eventEmitter.dispatchEvent(new CustomEvent(id, { detail: data })); 45 + dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => { 46 + eventEmitter.emit(id as string, data); 47 47 }, 48 48 49 - addEventListener: (id: string, cb: (data: string) => void) => { 50 - if (listeners.has(cb)) return; 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; 51 52 52 53 function listener(e: Event) { 53 54 const event = e as CustomEvent<string>; 54 - cb(event.detail); 55 + cb(event as EventData[Id]); 55 56 } 56 57 57 - listeners.set(cb, listener); 58 - eventEmitter.addEventListener(id, listener); 58 + listeners.set(untyped, listener); 59 + eventEmitter.on(id as string, listener); 59 60 }, 60 61 61 - removeEventListener: (id: string, cb: (data: string) => void) => { 62 - const listener = listeners.get(cb); 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); 63 65 if (listener == null) return; 64 - listeners.delete(cb); 65 - eventEmitter.removeEventListener(id, listener); 66 + listeners.delete(untyped); 67 + eventEmitter.off(id as string, listener); 66 68 } 67 69 }; 68 - } 69 - 70 - nodePreload: { 71 - return nodeMethod(); 72 - } 73 - 74 - injector: { 75 - return nodeMethod(); 76 70 } 77 71 78 72 throw new Error("Called createEventEmitter() in an impossible environment");
+3 -5
packages/core/src/util/import.ts
··· 9 9 cemented if import is passed a string literal. 10 10 */ 11 11 12 - const canRequire = ["path", "fs"] as const; 13 - type CanRequire = (typeof canRequire)[number]; 12 + const _canRequire = ["path", "fs"] as const; 13 + type CanRequire = (typeof _canRequire)[number]; 14 14 15 15 type ImportTypes = { 16 16 path: typeof import("path"); 17 17 fs: typeof import("fs"); 18 18 }; 19 19 20 - export default function requireImport<T extends CanRequire>( 21 - type: T 22 - ): Awaited<ImportTypes[T]> { 20 + export default function requireImport<T extends CanRequire>(type: T): Awaited<ImportTypes[T]> { 23 21 return require(type); 24 22 }
+12 -16
packages/core/src/util/logger.ts
··· 1 1 /* eslint-disable no-console */ 2 2 import { LogLevel } from "@moonlight-mod/types/logger"; 3 - import { readConfig } from "../config"; 3 + import { Config } from "@moonlight-mod/types"; 4 4 5 5 const colors = { 6 6 [LogLevel.SILLY]: "#EDD3E9", ··· 11 11 [LogLevel.ERROR]: "#FF0000" 12 12 }; 13 13 14 - const config = readConfig(); 15 14 let maxLevel = LogLevel.INFO; 16 - if (config.loggerLevel != null) { 17 - const enumValue = 18 - LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel]; 19 - if (enumValue != null) { 20 - maxLevel = enumValue; 21 - } 22 - } 23 15 24 16 export default class Logger { 25 17 private name: string; ··· 57 49 const logLevel = LogLevel[level].toUpperCase(); 58 50 if (maxLevel > level) return; 59 51 60 - if (MOONLIGHT_WEB_PRELOAD) { 61 - args = [ 62 - `%c[${logLevel}]`, 63 - `background-color: ${colors[level]}; color: #FFFFFF;`, 64 - `[${this.name}]`, 65 - ...obj 66 - ]; 52 + if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) { 53 + args = [`%c[${logLevel}]`, `background-color: ${colors[level]}; color: #FFFFFF;`, `[${this.name}]`, ...obj]; 67 54 } else { 68 55 args = [`[${logLevel}]`, `[${this.name}]`, ...obj]; 69 56 } ··· 92 79 } 93 80 } 94 81 } 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 + } 89 + } 90 + }
+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 1 { 2 - "extends": "../../tsconfig.json" 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["ESNext", "DOM"] 5 + } 3 6 }
+11 -2
packages/core-extensions/package.json
··· 1 1 { 2 2 "name": "@moonlight-mod/core-extensions", 3 3 "private": true, 4 + "engineStrict": true, 5 + "engines": { 6 + "node": ">=22", 7 + "pnpm": ">=10", 8 + "npm": "pnpm", 9 + "yarn": "pnpm" 10 + }, 4 11 "dependencies": { 5 - "@electron/asar": "^3.2.5", 6 - "@moonlight-mod/types": "workspace:*" 12 + "@moonlight-mod/core": "workspace:*", 13 + "@moonlight-mod/types": "workspace:*", 14 + "microdiff": "catalog:prod", 15 + "nanotar": "catalog:prod" 7 16 } 8 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;
+6 -29
packages/core-extensions/src/common/index.ts
··· 1 1 import { ExtensionWebExports } from "@moonlight-mod/types"; 2 2 3 3 export const webpackModules: ExtensionWebExports["webpackModules"] = { 4 - components: { 5 - dependencies: [ 6 - { ext: "spacepack", id: "spacepack" }, 7 - "MasonryList:", 8 - ".flexGutterSmall," 9 - ] 4 + stores: { 5 + dependencies: [{ id: "discord/packages/flux" }] 10 6 }, 11 - 12 - flux: { 13 - dependencies: [{ ext: "spacepack", id: "spacepack" }, "connectStores:"] 7 + ErrorBoundary: { 8 + dependencies: [{ id: "react" }] 14 9 }, 15 - 16 - fluxDispatcher: { 17 - dependencies: [ 18 - { ext: "spacepack", id: "spacepack" }, 19 - "isDispatching", 20 - "dispatch" 21 - ] 22 - }, 23 - 24 - react: { 25 - dependencies: [ 26 - { ext: "spacepack", id: "spacepack" }, 27 - "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED", 28 - /\.?version(?:=|:)/, 29 - /\.?createElement(?:=|:)/ 30 - ] 31 - }, 32 - 33 - stores: { 34 - dependencies: [{ ext: "common", id: "flux" }] 10 + icons: { 11 + dependencies: [{ id: "react" }, { id: "discord/components/common/index" }] 35 12 } 36 13 };
+3 -1
packages/core-extensions/src/common/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "common", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "Common", 5 - "tagline": "A *lot* of common clientmodding utilities from the Discord client", 7 + "tagline": "Common client modding utilities for the Discord client", 6 8 "authors": ["Cynosphere", "NotNite"], 7 9 "tags": ["library"] 8 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;
-41
packages/core-extensions/src/common/webpackModules/components.ts
··· 1 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 - 3 - const Components = spacepack.findByCode("MasonryList:function")[0].exports; 4 - const MarkdownParser = spacepack.findByCode( 5 - "parseAutoModerationSystemMessage:" 6 - )[0].exports.Z; 7 - const LegacyText = spacepack.findByCode(".selectable", ".colorStandard")[0] 8 - .exports.default; 9 - const Flex = Object.values( 10 - spacepack.findByCode(".flex" + "GutterSmall,")[0].exports 11 - )[0]; 12 - 13 - const CardClasses = {}; 14 - spacepack 15 - .lazyLoad( 16 - "renderArtisanalHack", 17 - /\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"ChannelSettings"/, 18 - /webpackId:(\d+),name:"ChannelSettings"/ 19 - ) 20 - .then(() => 21 - Object.assign( 22 - CardClasses, 23 - spacepack.findByExports("card", "cardHeader", "inModal")[0].exports 24 - ) 25 - ); 26 - 27 - const ControlClasses = spacepack.findByCode( 28 - "title", 29 - "titleDefault", 30 - "dividerDefault" 31 - )[0].exports; 32 - 33 - // We use CJS export here because merging the exports from Components is annoying as shit 34 - module.exports = { 35 - ...Components, 36 - MarkdownParser, 37 - LegacyText, 38 - Flex, 39 - CardClasses, 40 - ControlClasses 41 - };
-28
packages/core-extensions/src/common/webpackModules/flux.ts
··· 1 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 - 3 - const mod = spacepack.findByCode("connectStores:")[0].exports; 4 - 5 - const useStateFromStores = spacepack.findFunctionByStrings( 6 - mod, 7 - '"useStateFromStores"' 8 - )!; 9 - 10 - module.exports = { 11 - BatchedStoreListener: spacepack.findFunctionByStrings( 12 - mod, 13 - " tried to load a non-existent store." 14 - ), 15 - Dispatcher: spacepack.findFunctionByStrings(mod, "_dispatchWithDevtools("), 16 - Store: spacepack.findFunctionByStrings(mod, "registerActionHandlers("), 17 - default: mod.ZP, 18 - statesWillNeverBeEqual: spacepack.findFunctionByStrings(mod, "return!1"), 19 - useStateFromStores, 20 - useStateFromStoresArray: spacepack.findFunctionByStrings( 21 - mod, 22 - new RegExp(`return ${useStateFromStores.name}\\(.+?\\.[^Z]\\)`) 23 - ), 24 - useStateFromStoresObject: spacepack.findFunctionByStrings( 25 - mod, 26 - new RegExp(`return ${useStateFromStores.name}\\(.+?\\.Z\\)`) 27 - ) 28 - };
-6
packages/core-extensions/src/common/webpackModules/fluxDispatcher.ts
··· 1 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 - 3 - module.exports = spacepack.findByExports( 4 - "isDispatching", 5 - "dispatch" 6 - )[0].exports.Z;
+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;
-7
packages/core-extensions/src/common/webpackModules/react.ts
··· 1 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 - 3 - module.exports = spacepack.findByCode( 4 - "__SECRET_INTERNALS_DO_NOT_USE" + "_OR_YOU_WILL_BE_FIRED", 5 - /\.?version(?:=|:)/, 6 - /\.?createElement(?:=|:)/ 7 - )[0].exports;
+2 -2
packages/core-extensions/src/common/webpackModules/stores.ts
··· 1 - import Flux from "@moonlight-mod/wp/common_flux"; 1 + import { Store } from "@moonlight-mod/wp/discord/packages/flux"; 2 2 3 3 module.exports = new Proxy( 4 4 {}, 5 5 { 6 6 get: function (target, key, receiver) { 7 - const allStores = Flux.Store.getAll(); 7 + const allStores = Store.getAll(); 8 8 9 9 let targetStore; 10 10 for (const store of allStores) {
+84
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: 31 + /(?<=\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[\(0,\i\.jsx\)\(\i,{user:\i}\),.+?onClickPremiumGuildIcon:\i}\)])/, 32 + replacement: (_, decorators) => 33 + `children:require("componentEditor_memberList").default._patchDecorators(${decorators},arguments[0])` 34 + }, 35 + { 36 + match: /name:null==\i\?\(0,\i\.jsx\)\("span"/, 37 + replacement: (orig: string) => 38 + `children:require("componentEditor_memberList").default._patchItems([],arguments[0]),${orig}` 39 + } 40 + ] 41 + }, 42 + 43 + // messages 44 + { 45 + find: '},"new-member")),', 46 + replace: [ 47 + { 48 + match: /(?<=\.BADGES](=|:))(\i)(;|})/, 49 + replacement: (_, leading, badges, trailing) => 50 + `require("componentEditor_messages").default._patchUsernameBadges(${badges},arguments[0])${trailing}` 51 + }, 52 + { 53 + match: /(?<=className:\i,)badges:(\i)/, 54 + replacement: (_, badges) => 55 + `badges:require("componentEditor_messages").default._patchBadges(${badges},arguments[0])` 56 + }, 57 + { 58 + match: /(?<=username:\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[.+?])}\),usernameSpanId:/, 59 + replacement: (_, elements) => 60 + `children:require("componentEditor_messages").default._patchUsername(${elements},arguments[0])}),usernameSpanId:` 61 + } 62 + ] 63 + }, 64 + { 65 + find: '.provider&&"Discord"===', 66 + replace: { 67 + match: /(?<=\.container\),)children:(\[.+?this\.renderSuppressConfirmModal\(\),.+?\])}\)/, 68 + replacement: (_, elements) => 69 + `children:require("componentEditor_messages").default._patchAccessories(${elements},this.props)})` 70 + } 71 + } 72 + ]; 73 + 74 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 75 + dmList: { 76 + dependencies: [{ id: "react" }] 77 + }, 78 + memberList: { 79 + dependencies: [{ id: "react" }] 80 + }, 81 + messages: { 82 + dependencies: [{ id: "react" }] 83 + } 84 + };
+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 -13
packages/core-extensions/src/contextMenu/index.tsx
··· 5 5 find: "Menu API only allows Items and groups of Items as children.", 6 6 replace: [ 7 7 { 8 - match: /(?<=let{navId[^}]+?}=(.),(.)=.\(.\))/, 9 - replacement: (_, props, items) => 10 - `,__contextMenu=!${props}.__contextMenu_evilMenu&&require("contextMenu_contextMenu")._patchMenu(${props}, ${items})` 8 + match: /(?<=let{navId[^}]+?}=(.),.=).+?(?=,)/, 9 + replacement: (items, props) => `require("contextMenu_contextMenu")._patchMenu(${props},${items})` 11 10 } 12 11 ] 13 12 }, ··· 16 15 replace: [ 17 16 { 18 17 match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/, 19 - replacement: (render) => 20 - `require("contextMenu_contextMenu")._saveProps(this,${render})` 18 + replacement: (render) => `require("contextMenu_contextMenu")._saveProps(this,${render})` 21 19 } 22 20 ] 23 21 } ··· 25 23 26 24 export const webpackModules: Record<string, ExtensionWebpackModule> = { 27 25 contextMenu: { 28 - dependencies: [ 29 - { ext: "spacepack", id: "spacepack" }, 30 - "Menu API only allows Items and groups of Items as children." 31 - ] 26 + dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."] 32 27 }, 33 28 evilMenu: { 34 - dependencies: [ 35 - { ext: "spacepack", id: "spacepack" }, 36 - "Menu API only allows Items and groups of Items as children." 37 - ] 29 + dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."] 38 30 } 39 31 };
+2
packages/core-extensions/src/contextMenu/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "contextMenu", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "Context Menu", 5 7 "tagline": "A library for patching and creating context menus",
+25 -30
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
··· 1 - import { 2 - InternalItem, 3 - MenuElement, 4 - MenuProps 5 - } from "@moonlight-mod/types/coreExtensions/contextMenu"; 1 + import { InternalItem, Menu, MenuElement } from "@moonlight-mod/types/coreExtensions/contextMenu"; 6 2 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 7 3 import parser from "@moonlight-mod/wp/contextMenu_evilMenu"; 8 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 + 9 11 type Patch = { 10 12 navId: string; 11 - item: ( 12 - props: any 13 - ) => 14 - | React.ReactComponentElement<MenuElement> 15 - | React.ReactComponentElement<MenuElement>[]; 16 - anchorId: string; 13 + item: React.FC<any>; 14 + anchor: string | RegExp; 17 15 before: boolean; 18 16 }; 19 17 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 }); 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 }); 31 22 } 32 23 33 - export const patches: Patch[] = []; 34 - function _patchMenu(props: MenuProps, items: InternalItem[]) { 24 + const patches: Patch[] = []; 25 + function _patchMenu(props: React.ComponentProps<Menu>, items: InternalItem[]) { 35 26 const matches = patches.filter((p) => p.navId === props.navId); 36 - if (!matches.length) return; 27 + if (!matches.length) return items; 37 28 38 29 for (const patch of matches) { 39 - const idx = items.findIndex((i) => i.key === patch.anchorId); 30 + const idx = items.findIndex((i) => 31 + typeof patch.anchor === "string" ? i.key === patch.anchor : patch.anchor.test(i.key!) 32 + ); 40 33 if (idx === -1) continue; 41 - items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps))); 34 + items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps) as ReturnType)); 42 35 } 36 + 37 + return items; 43 38 } 44 39 45 40 let menuProps: any; ··· 56 51 } 57 52 58 53 module.exports = { 54 + patches, 59 55 addItem, 60 56 _patchMenu, 61 57 _saveProps 62 58 }; 63 59 64 60 // Unmangle Menu elements 61 + // spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString(); 65 62 const code = 66 63 spacepack.require.m[ 67 - spacepack.findByCode( 68 - "Menu API only allows Items and groups of Items as children." 69 - )[0].id 64 + spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id 70 65 ].toString(); 71 66 72 67 let MangledMenu;
+11 -20
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
··· 1 1 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 2 3 + // spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString(); 3 4 let code = 4 5 spacepack.require.m[ 5 - spacepack.findByCode( 6 - "Menu API only allows Items and groups of Items as children." 7 - )[0].id 6 + spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id 8 7 ].toString(); 9 - code = code.replace( 10 - /onSelect:(.)}=(.),.=(.\(.\)),/, 11 - `onSelect:$1}=$2;return $3;let ` 12 - ); 13 - const mod = new Function( 14 - "module", 15 - "exports", 16 - "require", 17 - `(${code}).apply(this, arguments)` 18 - ); 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 + 19 14 const exp: any = {}; 20 15 mod({}, exp, require); 21 - const Menu = spacepack.findFunctionByStrings(exp, "isUsingKeyboardNavigation")!; 22 - module.exports = (el: any) => { 23 - return Menu({ 24 - children: el, 25 - __contextMenu_evilMenu: true 26 - }); 27 - }; 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 -25
packages/core-extensions/src/disableSentry/host.ts
··· 1 1 import { join } from "node:path"; 2 2 import { Module } from "node:module"; 3 - import { BrowserWindow } from "electron"; 4 3 5 4 const logger = moonlightHost.getLogger("disableSentry"); 6 5 7 6 if (moonlightHost.asarPath !== "moonlightDesktop") { 8 7 try { 9 - const hostSentryPath = require.resolve( 10 - join(moonlightHost.asarPath, "node_modules", "@sentry", "electron") 11 - ); 12 - require.cache[hostSentryPath] = new Module( 13 - hostSentryPath, 14 - require.cache[require.resolve(moonlightHost.asarPath)] 15 - ); 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)]); 16 10 require.cache[hostSentryPath]!.exports = { 17 11 init: () => {}, 18 12 captureException: () => {}, 19 13 setTag: () => {}, 20 - setUser: () => {} 14 + setUser: () => {}, 15 + captureMessage: () => {} 21 16 }; 22 17 logger.debug("Stubbed Sentry host side!"); 23 18 } catch (err) { 24 19 logger.error("Failed to stub Sentry host side:", err); 25 20 } 26 21 } 27 - 28 - moonlightHost.events.on("window-created", (window: BrowserWindow) => { 29 - window.webContents.session.webRequest.onBeforeRequest( 30 - { 31 - urls: [ 32 - "https://*.sentry.io/*", 33 - "https://*.discord.com/error-reporting-proxy/*", 34 - "https://discord.com/assets/sentry.*.js", 35 - "https://*.discord.com/assets/sentry.*.js" 36 - ] 37 - }, 38 - function (details, callback) { 39 - callback({ cancel: true }); 40 - } 41 - ); 42 - });
+5 -6
packages/core-extensions/src/disableSentry/index.ts
··· 6 6 find: "profiledRootComponent:", 7 7 replace: { 8 8 type: PatchReplaceType.Normal, 9 - match: /(?<=\.Z=){.+?}}/, 10 - replacement: 'require("disableSentry_stub").proxy()' 9 + match: /Z:\(\)=>\i/, 10 + replacement: 'Z:()=>require("disableSentry_stub").proxy()' 11 11 } 12 12 }, 13 13 { 14 - find: "window.DiscordSentry.addBreadcrumb", 14 + find: "this._sentryUtils=", 15 15 replace: { 16 16 type: PatchReplaceType.Normal, 17 - match: /Z:function\(\){return .}/, 18 - replacement: 19 - 'default:function(){return (...args)=>{moonlight.getLogger("disableSentry").debug("Sentry calling addBreadcrumb passthrough:", ...args);}}' 17 + match: /(?<=this._sentryUtils=)./, 18 + replacement: "undefined" 20 19 } 21 20 }, 22 21 {
+12 -1
packages/core-extensions/src/disableSentry/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "disableSentry", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "Disable Sentry", 5 7 "tagline": "Turns off Discord's error reporting systems", 6 8 "authors": ["Cynosphere", "NotNite"], 7 9 "tags": ["privacy"] 8 - } 10 + }, 11 + "blocked": [ 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 + ] 9 20 }
+13 -19
packages/core-extensions/src/disableSentry/node.ts
··· 5 5 6 6 const logger = moonlightNode.getLogger("disableSentry"); 7 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 - } 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); 27 21 }
+1 -2
packages/core-extensions/src/disableSentry/webpackModules/stub.ts
··· 23 23 throw Error("crash"); 24 24 }; 25 25 } else if (keys.includes(prop.toString())) { 26 - return (...args: any[]) => 27 - logger.debug(`Sentry calling "${prop.toString()}":`, ...args); 26 + return (...args: any[]) => logger.debug(`Sentry calling "${prop.toString()}":`, ...args); 28 27 } else { 29 28 return undefined; 30 29 }
+39 -2
packages/core-extensions/src/experiments/index.ts
··· 11 11 { 12 12 find: '"scientist:triggered"', // Scientist? Triggered. 13 13 replace: { 14 - match: /(?<=personal_connection_id\|\|)!1/, 15 - replacement: "!0" 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: /&&\((\i)\?\(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:(\i)(,|})/, 51 + replacement: (_, isStaff, trail) => 52 + `.useReducedMotion,isStaff:(moonlight.getConfigOption("experiments","staffSettings")??false)?true:${isStaff}${trail}` 16 53 } 17 54 } 18 55 ];
+12 -2
packages/core-extensions/src/experiments/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "experiments", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "Experiments", 5 7 "tagline": "Allows you to configure Discord's internal A/B testing features", ··· 7 9 "tags": ["dangerZone"] 8 10 }, 9 11 "settings": { 10 - "sections": { 12 + "devtools": { 13 + "advice": "reload", 14 + "displayName": "Enable staff help menu (DevTools)", 15 + "type": "boolean", 16 + "default": false 17 + }, 18 + "staffSettings": { 19 + "advice": "reload", 11 20 "displayName": "Allow access to other staff settings elsewhere", 12 - "type": "boolean" 21 + "type": "boolean", 22 + "default": false 13 23 } 14 24 } 15 25 }
+7 -19
packages/core-extensions/src/markdown/index.ts
··· 6 6 replace: [ 7 7 { 8 8 match: /={newline:(.+?)},(.{1,2})=\(0,/, 9 - replacement: (_, rules, RULES) => 10 - `=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,` 9 + replacement: (_, rules, RULES) => `=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,` 11 10 }, 12 11 { 13 - match: /(?<=var (.{1,2})={RULES:.+?})/, 14 - replacement: (_, rulesets) => 15 - `;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});` 12 + match: /(?<=;(.{1,2}\.Z)={RULES:.+?})/, 13 + replacement: (_, rulesets) => `;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});` 16 14 } 17 15 ] 18 16 }, ··· 25 23 `__slateRules,${rulesDef}=__slateRules=require("markdown_markdown")._addSlateRules({link:{${rules}}),${syntaxBefore}=new Set` 26 24 }, 27 25 { 28 - match: 29 - /(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"link":{(.+?)}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:` 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:` 40 29 } 41 30 ] 42 31 }, ··· 44 33 find: '"Slate: Unknown decoration attribute: "', 45 34 replace: { 46 35 match: /=({strong:.+?});/, 47 - replacement: (_, rules) => 48 - `=require("markdown_markdown")._addSlateDecorators(${rules});` 36 + replacement: (_, rules) => `=require("markdown_markdown")._addSlateDecorators(${rules});` 49 37 } 50 38 } 51 39 ];
+2
packages/core-extensions/src/markdown/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "markdown", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "Markdown", 5 7 "tagline": "A library for adding new markdown rules",
+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"; 1 + import { MarkdownRule, Ruleset, SlateRule } from "@moonlight-mod/types/coreExtensions/markdown"; 7 2 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 - > = {}; 3 + export const rules: Record<string, (rules: Record<string, MarkdownRule>) => MarkdownRule> = {}; 4 + export const slateRules: Record<string, (rules: Record<string, SlateRule>) => SlateRule> = {}; 16 5 export const slateDecorators: Record<string, string> = {}; 17 6 export const ruleBlacklists: Record<Ruleset, Record<string, boolean>> = { 18 7 RULES: {}, ··· 67 56 return originalRules; 68 57 } 69 58 70 - export function _applyRulesetBlacklist( 71 - rulesets: Record<Ruleset, Record<string, MarkdownRule>> 72 - ) { 59 + export function _applyRulesetBlacklist(rulesets: Record<Ruleset, Record<string, MarkdownRule>>) { 73 60 for (const ruleset of Object.keys(rulesets) as Ruleset[]) { 74 61 if (ruleset === "RULES") continue; 75 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 + });
+80 -17
packages/core-extensions/src/moonbase/index.tsx
··· 1 - import { ExtensionWebExports } from "@moonlight-mod/types"; 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 + ]; 2 33 3 - export const webpackModules: ExtensionWebExports["webpackModules"] = { 34 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 4 35 stores: { 5 - dependencies: [ 6 - { ext: "common", id: "flux" }, 7 - { ext: "common", id: "fluxDispatcher" } 8 - ] 36 + dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }] 9 37 }, 10 38 11 39 ui: { 12 40 dependencies: [ 13 41 { ext: "spacepack", id: "spacepack" }, 14 - { ext: "common", id: "react" }, 15 - { ext: "common", id: "components" }, 42 + { id: "react" }, 43 + { id: "discord/components/common/index" }, 16 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" }, 17 49 "Masks.PANEL_BUTTON", 18 - "renderArtisanalHack(){", 19 50 '"Missing channel in Channel.openChannelContextMenu"', 20 51 ".forumOrHome]:" 21 52 ] 22 53 }, 23 54 24 - moonbase: { 55 + ThemeDarkIcon: { 56 + dependencies: [{ ext: "common", id: "icons" }, { id: "react" }] 57 + }, 58 + 59 + settings: { 25 60 dependencies: [ 26 61 { ext: "spacepack", id: "spacepack" }, 27 62 { ext: "settings", id: "settings" }, 28 - { ext: "common", id: "react" }, 29 - { ext: "moonbase", id: "ui" } 63 + { id: "react" }, 64 + { ext: "moonbase", id: "ui" }, 65 + { ext: "contextMenu", id: "contextMenu" }, 66 + ':"USER_SETTINGS_MODAL_SET_SECTION"' 30 67 ], 31 68 entrypoint: true 69 + }, 70 + 71 + updates: { 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 + ] 32 100 } 33 101 }; 34 - 35 - export const styles = [ 36 - ".moonbase-settings > :first-child { margin-top: 0px; }", 37 - "textarea.moonbase-resizeable { resize: vertical }" 38 - ];
+35 -5
packages/core-extensions/src/moonbase/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "moonbase", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "Moonbase", 5 7 "tagline": "The official settings UI for moonlight", 6 - "authors": ["Cynosphere", "NotNite"] 8 + "authors": ["Cynosphere", "NotNite", "redstonekasi"] 7 9 }, 8 - "dependencies": ["spacepack", "settings", "common"], 10 + "dependencies": ["spacepack", "settings", "common", "notices", "contextMenu"], 9 11 "settings": { 10 12 "sections": { 13 + "advice": "reload", 11 14 "displayName": "Split into sections", 12 15 "description": "Show the Moonbase tabs as separate sections", 13 - "type": "boolean" 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 14 24 }, 15 25 "saveFilter": { 26 + "advice": "none", 16 27 "displayName": "Persist filter", 17 28 "description": "Save extension filter in config", 18 - "type": "boolean" 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 19 45 } 20 - } 46 + }, 47 + "cors": [ 48 + "https://github.com/moonlight-mod/moonlight/releases/download/", 49 + "https://objects.githubusercontent.com/github-production-release-asset-" 50 + ] 21 51 }
+178
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 { 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`; 11 + const artifactName = "dist.tar.gz"; 12 + 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; 25 + assets: { 26 + name: string; 27 + browser_download_url: string; 28 + }[]; 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; 56 + } catch (e) { 57 + logger.error("Error checking for moonlight update", e); 58 + 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]> { 68 + const json = await getStableRelease(); 69 + const asset = json.assets.find((a) => a.name === artifactName); 70 + if (!asset) throw new Error("Artifact not found"); 71 + 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]; 79 + } 80 + 81 + async function downloadNightly(): Promise<[ArrayBuffer, string]> { 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 + 94 + return [await zipReq.arrayBuffer(), ref]; 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); 112 + for (const file of files) { 113 + if (!file.data) continue; 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 + }, 129 + 130 + async fetchRepositories(repos) { 131 + const ret: Record<string, RepositoryManifest[]> = {}; 132 + 133 + for (const repo of repos) { 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; 141 + } catch (e) { 142 + logger.error(`Error fetching repository ${repo}`, e); 143 + } 144 + } 145 + 146 + return ret; 147 + }, 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 + }
+2 -69
packages/core-extensions/src/moonbase/node.ts
··· 1 - import { MoonbaseNatives, RepositoryManifest } from "./types"; 2 - import asar from "@electron/asar"; 3 - import fs from "fs"; 4 - import path from "path"; 5 - import os from "os"; 6 - import { repoUrlFile } from "types/src/constants"; 7 - 8 - const logger = moonlightNode.getLogger("moonbase"); 9 - 10 - async function fetchRepositories(repos: string[]) { 11 - const ret: Record<string, RepositoryManifest[]> = {}; 12 - 13 - for (const repo of repos) { 14 - try { 15 - const req = await fetch(repo); 16 - const json = await req.json(); 17 - ret[repo] = json; 18 - } catch (e) { 19 - logger.error(`Error fetching repository ${repo}`, e); 20 - } 21 - } 22 - 23 - return ret; 24 - } 25 - 26 - async function installExtension( 27 - manifest: RepositoryManifest, 28 - url: string, 29 - repo: string 30 - ) { 31 - const req = await fetch(url); 32 - 33 - const dir = moonlightNode.getExtensionDir(manifest.id); 34 - // remake it in case of updates 35 - if (fs.existsSync(dir)) fs.rmdirSync(dir, { recursive: true }); 36 - fs.mkdirSync(dir, { recursive: true }); 37 - 38 - // for some reason i just can't .writeFileSync() a file that ends in .asar??? 39 - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "moonlight-")); 40 - const tempFile = path.join(tempDir, "extension"); 41 - const buffer = await req.arrayBuffer(); 42 - fs.writeFileSync(tempFile, Buffer.from(buffer)); 43 - 44 - asar.extractAll(tempFile, dir); 45 - fs.writeFileSync(path.join(dir, repoUrlFile), repo); 46 - } 47 - 48 - async function deleteExtension(id: string) { 49 - const dir = moonlightNode.getExtensionDir(id); 50 - fs.rmdirSync(dir, { recursive: true }); 51 - } 52 - 53 - function getExtensionConfig(id: string, key: string): any { 54 - const config = moonlightNode.config.extensions[id]; 55 - if (typeof config === "object") { 56 - return config.config?.[key]; 57 - } 58 - 59 - return undefined; 60 - } 61 - 62 - const exports: MoonbaseNatives = { 63 - fetchRepositories, 64 - installExtension, 65 - deleteExtension, 66 - getExtensionConfig 67 - }; 68 - 69 - module.exports = exports; 1 + import getNatives from "./native"; 2 + module.exports = getNatives();
+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 + }
+26 -10
packages/core-extensions/src/moonbase/types.ts
··· 1 - import { DetectedExtension, ExtensionManifest } from "types/src"; 1 + import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 2 + import { DetectedExtension, ExtensionManifest, MoonlightBranch } from "@moonlight-mod/types"; 2 3 3 4 export type MoonbaseNatives = { 4 - fetchRepositories( 5 - repos: string[] 6 - ): Promise<Record<string, RepositoryManifest[]>>; 7 - installExtension( 8 - manifest: RepositoryManifest, 9 - url: string, 10 - repo: string 11 - ): Promise<void>; 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>; 12 10 deleteExtension(id: string): Promise<void>; 13 - getExtensionConfig(id: string, key: string): any; 14 11 }; 15 12 16 13 export type RepositoryManifest = ExtensionManifest & { ··· 29 26 manifest: ExtensionManifest | RepositoryManifest; 30 27 source: DetectedExtension["source"]; 31 28 state: ExtensionState; 29 + compat: ExtensionCompat; 30 + hasUpdate: boolean; 31 + changelog?: string; 32 + settingsOverride?: ExtensionManifest["settings"]; 32 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 + async 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 + await 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 + }
+10
packages/core-extensions/src/moonbase/webpackModules/moonbase.ts
··· 1 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 2 + import type { Moonbase } from "@moonlight-mod/types/coreExtensions/moonbase"; 3 + 4 + export const moonbase: Moonbase = { 5 + registerConfigComponent(ext, option, component) { 6 + MoonbaseSettingsStore.registerConfigComponent(ext, option, component); 7 + } 8 + }; 9 + 10 + export default moonbase;
-44
packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx
··· 1 - import settings from "@moonlight-mod/wp/settings_settings"; 2 - import React from "@moonlight-mod/wp/common_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 { MenuItem } from "@moonlight-mod/wp/common_components"; 8 - 9 - const { open } = spacepack.findByExports("setSection", "clearSubsection")[0] 10 - .exports.Z; 11 - 12 - settings.addSection("moonbase", "Moonbase", Moonbase, null, -2, { 13 - stores: [MoonbaseSettingsStore], 14 - element: () => { 15 - // Require it here because lazy loading SUX 16 - const SettingsNotice = spacepack.findByCode( 17 - "onSaveButtonColor", 18 - "FocusRingScope" 19 - )[0].exports.Z; 20 - return ( 21 - <SettingsNotice 22 - submitting={MoonbaseSettingsStore.submitting} 23 - onReset={() => { 24 - MoonbaseSettingsStore.reset(); 25 - }} 26 - onSave={() => { 27 - MoonbaseSettingsStore.writeConfig(); 28 - }} 29 - /> 30 - ); 31 - } 32 - }); 33 - 34 - settings.addSectionMenuItems( 35 - "moonbase", 36 - ...pages.map((page, i) => ( 37 - <MenuItem 38 - key={page.id} 39 - id={`moonbase-${page.id}`} 40 - label={page.name} 41 - action={() => open("moonbase", i)} 42 - /> 43 - )) 44 - );
+100
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, 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} 19 + onReset={() => { 20 + MoonbaseSettingsStore.reset(); 21 + }} 22 + onSave={async () => { 23 + await MoonbaseSettingsStore.writeConfig(); 24 + }} 25 + /> 26 + ); 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 38 + type Breadcrumb = { 39 + id: string; 40 + label: string; 41 + }; 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" }, 63 + { id: page.id, label: page.name } 64 + ]; 65 + return ( 66 + <> 67 + <Breadcrumbs 68 + className={Margins.marginBottom20} 69 + renderCustomBreadcrumb={renderBreadcrumb} 70 + breadcrumbs={breadcrumbs} 71 + activeId={page.id} 72 + > 73 + {page.name} 74 + </Breadcrumbs> 75 + 76 + <RestartAdviceMessage /> 77 + <Update /> 78 + 79 + <page.element /> 80 + </> 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", 91 + ...pages.map((page, i) => ( 92 + <MenuItem 93 + key={page.id} 94 + id={`moonbase-${page.id}`} 95 + label={page.name} 96 + action={() => UserSettingsModalActionCreators.open("moonbase", i.toString())} 97 + /> 98 + )) 99 + ); 100 + }
+362 -91
packages/core-extensions/src/moonbase/webpackModules/stores.ts
··· 1 - import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; 2 - import { ExtensionState, MoonbaseExtension, MoonbaseNatives } from "../types"; 3 - import Flux from "@moonlight-mod/wp/common_flux"; 4 - import Dispatcher from "@moonlight-mod/wp/common_fluxDispatcher"; 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 { NodeEventType } from "@moonlight-mod/types/core/event"; 17 + import { getConfigOption, setConfigOption } from "@moonlight-mod/core/util/config"; 18 + import diff from "microdiff"; 5 19 6 - const natives: MoonbaseNatives = moonlight.getNatives("moonbase"); 7 20 const logger = moonlight.getLogger("moonbase"); 8 21 9 - class MoonbaseSettingsStore extends Flux.Store<any> { 10 - private origConfig: Config; 22 + let natives: MoonbaseNatives = moonlight.getNatives("moonbase"); 23 + if (moonlightNode.isBrowser) natives = getNatives(); 24 + 25 + class MoonbaseSettingsStore extends Store<any> { 26 + private initialConfig: Config; 27 + private savedConfig: Config; 11 28 private config: Config; 12 29 private extensionIndex: number; 30 + private configComponents: Record<string, Record<string, CustomComponent>> = {}; 13 31 14 32 modified: boolean; 15 33 submitting: boolean; 16 34 installing: boolean; 17 35 36 + #updateState = UpdateState.Ready; 37 + get updateState() { 38 + return this.#updateState; 39 + } 40 + newVersion: string | null; 41 + shouldShowNotice: boolean; 42 + 43 + restartAdvice = RestartAdvice.NotNeeded; 44 + 18 45 extensions: { [id: number]: MoonbaseExtension }; 19 - updates: { [id: number]: { version: string; download: string } }; 46 + updates: { 47 + [id: number]: { 48 + version: string; 49 + download: string; 50 + updateManifest: RepositoryManifest; 51 + }; 52 + }; 20 53 21 54 constructor() { 22 55 super(Dispatcher); 23 56 24 - this.origConfig = moonlightNode.config; 25 - this.config = this.clone(this.origConfig); 57 + this.initialConfig = moonlightNode.config; 58 + this.savedConfig = moonlightNode.config; 59 + this.config = this.clone(this.savedConfig); 26 60 this.extensionIndex = 0; 27 61 28 62 this.modified = false; 29 63 this.submitting = false; 30 64 this.installing = false; 65 + 66 + this.newVersion = null; 67 + this.shouldShowNotice = false; 31 68 32 69 this.extensions = {}; 33 70 this.updates = {}; ··· 36 73 this.extensions[uniqueId] = { 37 74 ...ext, 38 75 uniqueId, 39 - state: moonlight.enabledExtensions.has(ext.id) 40 - ? ExtensionState.Enabled 41 - : ExtensionState.Disabled 76 + state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled : ExtensionState.Disabled, 77 + compat: checkExtensionCompat(ext.manifest), 78 + hasUpdate: false 42 79 }; 43 80 } 44 81 45 - natives.fetchRepositories(this.config.repositories).then((ret) => { 46 - for (const [repo, exts] of Object.entries(ret)) { 47 - try { 48 - for (const ext of exts) { 49 - const uniqueId = this.extensionIndex++; 50 - const extensionData = { 51 - id: ext.id, 52 - uniqueId, 53 - manifest: ext, 54 - source: { type: ExtensionLoadSource.Normal, url: repo }, 55 - state: ExtensionState.NotDownloaded 56 - }; 82 + // This is async but we're calling it without 83 + this.checkUpdates(); 84 + 85 + // Update our state if another extension edited the config programatically 86 + moonlightNode.events.addEventListener(NodeEventType.ConfigSaved, (config) => { 87 + if (!this.submitting) { 88 + this.config = this.clone(config); 89 + // NOTE: This is also async but we're calling it without 90 + this.processConfigChanged(); 91 + } 92 + }); 93 + } 94 + 95 + async checkUpdates() { 96 + await Promise.all([this.checkExtensionUpdates(), this.checkMoonlightUpdates()]); 97 + this.shouldShowNotice = this.newVersion != null || Object.keys(this.updates).length > 0; 98 + this.emitChange(); 99 + } 100 + 101 + private async checkExtensionUpdates() { 102 + const repositories = await natives!.fetchRepositories(this.savedConfig.repositories); 103 + 104 + // Reset update state 105 + for (const id in this.extensions) { 106 + const ext = this.extensions[id]; 107 + ext.hasUpdate = false; 108 + ext.changelog = undefined; 109 + } 110 + this.updates = {}; 111 + 112 + for (const [repo, exts] of Object.entries(repositories)) { 113 + for (const ext of exts) { 114 + const uniqueId = this.extensionIndex++; 115 + const extensionData = { 116 + id: ext.id, 117 + uniqueId, 118 + manifest: ext, 119 + source: { type: ExtensionLoadSource.Normal, url: repo }, 120 + state: ExtensionState.NotDownloaded, 121 + compat: ExtensionCompat.Compatible, 122 + hasUpdate: false 123 + }; 57 124 58 - if (this.alreadyExists(extensionData)) { 59 - if (this.hasUpdate(extensionData)) { 60 - this.updates[uniqueId] = { 61 - version: ext.version!, 62 - download: ext.download 63 - }; 64 - } 125 + // Don't present incompatible updates 126 + if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue; 65 127 66 - continue; 67 - } 128 + const existing = this.getExisting(extensionData); 129 + if (existing != null) { 130 + // Make sure the download URL is properly updated 131 + existing.manifest = { 132 + ...existing.manifest, 133 + download: ext.download 134 + }; 68 135 69 - this.extensions[uniqueId] = extensionData; 136 + if (this.hasUpdate(extensionData)) { 137 + this.updates[existing.uniqueId] = { 138 + version: ext.version!, 139 + download: ext.download, 140 + updateManifest: ext 141 + }; 142 + existing.hasUpdate = true; 143 + existing.changelog = ext.meta?.changelog; 70 144 } 71 - } catch (e) { 72 - logger.error(`Error processing repository ${repo}`, e); 145 + } else { 146 + this.extensions[uniqueId] = extensionData; 73 147 } 74 148 } 149 + } 150 + } 75 151 76 - this.emitChange(); 77 - }); 152 + private async checkMoonlightUpdates() { 153 + this.newVersion = this.getExtensionConfigRaw("moonbase", "updateChecking", true) 154 + ? await natives!.checkForMoonlightUpdate() 155 + : null; 78 156 } 79 157 80 - private alreadyExists(ext: MoonbaseExtension) { 81 - return Object.values(this.extensions).some( 82 - (e) => e.id === ext.id && e.source.url === ext.source.url 83 - ); 158 + private getExisting(ext: MoonbaseExtension) { 159 + return Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 84 160 } 85 161 86 162 private hasUpdate(ext: MoonbaseExtension) { 87 - const existing = Object.values(this.extensions).find( 88 - (e) => e.id === ext.id && e.source.url === ext.source.url 89 - ); 163 + const existing = Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 90 164 if (existing == null) return false; 91 165 92 - return ( 93 - existing.manifest.version !== ext.manifest.version && 94 - existing.state !== ExtensionState.NotDownloaded 95 - ); 166 + return existing.manifest.version !== ext.manifest.version && existing.state !== ExtensionState.NotDownloaded; 96 167 } 97 168 98 169 // Jank 99 170 private isModified() { 100 - const orig = JSON.stringify(this.origConfig); 171 + const orig = JSON.stringify(this.savedConfig); 101 172 const curr = JSON.stringify(this.config); 102 173 return orig !== curr; 103 174 } ··· 106 177 return this.submitting || this.installing; 107 178 } 108 179 180 + // Required for the settings store contract 109 181 showNotice() { 110 182 return this.modified; 111 183 } ··· 115 187 } 116 188 117 189 getExtensionUniqueId(id: string) { 118 - return Object.values(this.extensions).find((ext) => ext.id === id) 119 - ?.uniqueId; 190 + return Object.values(this.extensions).find((ext) => ext.id === id)?.uniqueId; 120 191 } 121 192 122 193 getExtensionConflicting(uniqueId: number) { 123 194 const ext = this.getExtension(uniqueId); 124 195 if (ext.state !== ExtensionState.NotDownloaded) return false; 125 196 return Object.values(this.extensions).some( 126 - (e) => 127 - e.id === ext.id && 128 - e.uniqueId !== uniqueId && 129 - e.state !== ExtensionState.NotDownloaded 197 + (e) => e.id === ext.id && e.uniqueId !== uniqueId && e.state !== ExtensionState.NotDownloaded 130 198 ); 131 199 } 132 200 ··· 149 217 150 218 getExtensionConfig<T>(uniqueId: number, key: string): T | undefined { 151 219 const ext = this.getExtension(uniqueId); 152 - const defaultValue = ext.manifest.settings?.[key]?.default; 153 - const clonedDefaultValue = this.clone(defaultValue); 154 - const cfg = this.config.extensions[ext.id]; 220 + const settings = ext.settingsOverride ?? ext.manifest.settings; 221 + return getConfigOption(ext.id, key, this.config, settings); 222 + } 155 223 156 - if (cfg == null || typeof cfg === "boolean") return clonedDefaultValue; 157 - return cfg.config?.[key] ?? clonedDefaultValue; 224 + getExtensionConfigRaw<T>(id: string, key: string, defaultValue: T | undefined): T | undefined { 225 + const cfg = this.config.extensions[id]; 226 + if (cfg == null || typeof cfg === "boolean") return defaultValue; 227 + return cfg.config?.[key] ?? defaultValue; 158 228 } 159 229 160 230 getExtensionConfigName(uniqueId: number, key: string) { 161 231 const ext = this.getExtension(uniqueId); 162 - return ext.manifest.settings?.[key]?.displayName ?? key; 232 + const settings = ext.settingsOverride ?? ext.manifest.settings; 233 + return settings?.[key]?.displayName ?? key; 163 234 } 164 235 165 236 getExtensionConfigDescription(uniqueId: number, key: string) { 166 237 const ext = this.getExtension(uniqueId); 167 - return ext.manifest.settings?.[key]?.description; 238 + const settings = ext.settingsOverride ?? ext.manifest.settings; 239 + return settings?.[key]?.description; 168 240 } 169 241 170 - setExtensionConfig(uniqueId: number, key: string, value: any) { 171 - const ext = this.getExtension(uniqueId); 172 - const oldConfig = this.config.extensions[ext.id]; 173 - const newConfig = 174 - typeof oldConfig === "boolean" 175 - ? { 176 - enabled: oldConfig, 177 - config: { [key]: value } 178 - } 179 - : { 180 - ...oldConfig, 181 - config: { ...(oldConfig?.config ?? {}), [key]: value } 182 - }; 183 - 184 - this.config.extensions[ext.id] = newConfig; 242 + setExtensionConfig(id: string, key: string, value: any) { 243 + setConfigOption(this.config, id, key, value); 185 244 this.modified = this.isModified(); 186 245 this.emitChange(); 187 246 } ··· 191 250 let val = this.config.extensions[ext.id]; 192 251 193 252 if (val == null) { 194 - this.config.extensions[ext.id] = { enabled }; 253 + this.config.extensions[ext.id] = enabled; 195 254 this.modified = this.isModified(); 196 255 this.emitChange(); 197 256 return; ··· 208 267 this.emitChange(); 209 268 } 210 269 270 + dismissAllExtensionUpdates() { 271 + for (const id in this.extensions) { 272 + this.extensions[id].hasUpdate = false; 273 + } 274 + this.emitChange(); 275 + } 276 + 277 + async updateAllExtensions() { 278 + for (const id of Object.keys(this.updates)) { 279 + try { 280 + await this.installExtension(parseInt(id)); 281 + } catch (e) { 282 + logger.error("Error bulk updating extension", id, e); 283 + } 284 + } 285 + } 286 + 211 287 async installExtension(uniqueId: number) { 212 288 const ext = this.getExtension(uniqueId); 213 289 if (!("download" in ext.manifest)) { ··· 216 292 217 293 this.installing = true; 218 294 try { 219 - const url = this.updates[uniqueId]?.download ?? ext.manifest.download; 220 - await natives.installExtension(ext.manifest, url, ext.source.url!); 295 + const update = this.updates[uniqueId]; 296 + const url = update?.download ?? ext.manifest.download; 297 + await natives!.installExtension(ext.manifest, url, ext.source.url!); 221 298 if (ext.state === ExtensionState.NotDownloaded) { 222 299 this.extensions[uniqueId].state = ExtensionState.Disabled; 223 300 } 224 301 302 + if (update != null) { 303 + const existing = this.extensions[uniqueId]; 304 + existing.settingsOverride = update.updateManifest.settings; 305 + existing.compat = checkExtensionCompat(update.updateManifest); 306 + existing.manifest = update.updateManifest; 307 + existing.changelog = update.updateManifest.meta?.changelog; 308 + } 309 + 225 310 delete this.updates[uniqueId]; 226 311 } catch (e) { 227 312 logger.error("Error installing extension:", e); 228 313 } 229 314 230 315 this.installing = false; 316 + this.restartAdvice = this.#computeRestartAdvice(); 231 317 this.emitChange(); 232 318 } 233 319 320 + private getRank(ext: MoonbaseExtension) { 321 + if (ext.source.type === ExtensionLoadSource.Developer) return 3; 322 + if (ext.source.type === ExtensionLoadSource.Core) return 2; 323 + if (ext.source.url === mainRepo) return 1; 324 + return 0; 325 + } 326 + 327 + async getDependencies(uniqueId: number) { 328 + const ext = this.getExtension(uniqueId); 329 + 330 + const missingDeps = []; 331 + for (const dep of ext.manifest.dependencies ?? []) { 332 + const anyInstalled = Object.values(this.extensions).some( 333 + (e) => e.id === dep && e.state !== ExtensionState.NotDownloaded 334 + ); 335 + if (!anyInstalled) missingDeps.push(dep); 336 + } 337 + 338 + if (missingDeps.length === 0) return null; 339 + 340 + const deps: Record<string, MoonbaseExtension[]> = {}; 341 + for (const dep of missingDeps) { 342 + const candidates = Object.values(this.extensions).filter((e) => e.id === dep); 343 + 344 + deps[dep] = candidates.sort((a, b) => { 345 + const aRank = this.getRank(a); 346 + const bRank = this.getRank(b); 347 + if (aRank === bRank) { 348 + const repoIndex = this.savedConfig.repositories.indexOf(a.source.url!); 349 + const otherRepoIndex = this.savedConfig.repositories.indexOf(b.source.url!); 350 + return repoIndex - otherRepoIndex; 351 + } else { 352 + return bRank - aRank; 353 + } 354 + }); 355 + } 356 + 357 + return deps; 358 + } 359 + 234 360 async deleteExtension(uniqueId: number) { 235 361 const ext = this.getExtension(uniqueId); 236 362 if (ext == null) return; 237 363 238 364 this.installing = true; 239 365 try { 240 - await natives.deleteExtension(ext.id); 366 + await natives!.deleteExtension(ext.id); 241 367 this.extensions[uniqueId].state = ExtensionState.NotDownloaded; 242 368 } catch (e) { 243 369 logger.error("Error deleting extension:", e); 244 370 } 245 371 246 372 this.installing = false; 373 + this.restartAdvice = this.#computeRestartAdvice(); 374 + this.emitChange(); 375 + } 376 + 377 + async updateMoonlight() { 378 + this.#updateState = UpdateState.Working; 379 + this.emitChange(); 380 + 381 + await natives 382 + .updateMoonlight() 383 + .then(() => (this.#updateState = UpdateState.Installed)) 384 + .catch((e) => { 385 + logger.error(e); 386 + this.#updateState = UpdateState.Failed; 387 + }); 388 + 247 389 this.emitChange(); 248 390 } 249 391 ··· 257 399 this.emitChange(); 258 400 } 259 401 260 - writeConfig() { 261 - this.submitting = true; 402 + tryGetExtensionName(id: string) { 403 + const uniqueId = this.getExtensionUniqueId(id); 404 + return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id; 405 + } 262 406 407 + registerConfigComponent(ext: string, name: string, component: CustomComponent) { 408 + if (!(ext in this.configComponents)) this.configComponents[ext] = {}; 409 + this.configComponents[ext][name] = component; 410 + } 411 + 412 + getExtensionConfigComponent(ext: string, name: string) { 413 + return this.configComponents[ext]?.[name]; 414 + } 415 + 416 + #computeRestartAdvice() { 417 + // If moonlight update needs a restart, always hide advice. 418 + if (this.#updateState === UpdateState.Installed) return RestartAdvice.NotNeeded; 419 + 420 + const i = this.initialConfig; // Initial config, from startup 421 + const n = this.config; // New config about to be saved 422 + 423 + let returnedAdvice = RestartAdvice.NotNeeded; 424 + const updateAdvice = (r: RestartAdvice) => (returnedAdvice < r ? (returnedAdvice = r) : returnedAdvice); 425 + 426 + // Top-level keys, repositories is not needed here because Moonbase handles it. 427 + if (i.patchAll !== n.patchAll) updateAdvice(RestartAdvice.ReloadNeeded); 428 + if (i.loggerLevel !== n.loggerLevel) updateAdvice(RestartAdvice.ReloadNeeded); 429 + if (diff(i.devSearchPaths ?? [], n.devSearchPaths ?? [], { cyclesFix: false }).length !== 0) 430 + return updateAdvice(RestartAdvice.RestartNeeded); 431 + 432 + // Extension specific logic 433 + for (const id in n.extensions) { 434 + // Installed extension (might not be detected yet) 435 + const ext = Object.values(this.extensions).find((e) => e.id === id && e.state !== ExtensionState.NotDownloaded); 436 + // Installed and detected extension 437 + const detected = moonlightNode.extensions.find((e) => e.id === id); 438 + 439 + // If it's not installed at all, we don't care 440 + if (!ext) continue; 441 + 442 + const initState = i.extensions[id]; 443 + const newState = n.extensions[id]; 444 + 445 + const newEnabled = typeof newState === "boolean" ? newState : newState.enabled; 446 + // If it's enabled but not detected yet, restart. 447 + if (newEnabled && !detected) { 448 + return updateAdvice(RestartAdvice.RestartNeeded); 449 + } 450 + 451 + // Toggling extensions specifically wants to rely on the initial state, 452 + // that's what was considered when loading extensions. 453 + const initEnabled = initState && (typeof initState === "boolean" ? initState : initState.enabled); 454 + if (initEnabled !== newEnabled || detected?.manifest.version !== ext.manifest.version) { 455 + // If we have the extension locally, we confidently know if it has host/preload scripts. 456 + // If not, we have to respect the environment specified in the manifest. 457 + // If that is the default, we can't know what's needed. 458 + 459 + if (detected?.scripts.hostPath || detected?.scripts.nodePath) { 460 + return updateAdvice(RestartAdvice.RestartNeeded); 461 + } 462 + 463 + switch (ext.manifest.environment) { 464 + case ExtensionEnvironment.Both: 465 + case ExtensionEnvironment.Web: 466 + updateAdvice(RestartAdvice.ReloadNeeded); 467 + continue; 468 + case ExtensionEnvironment.Desktop: 469 + return updateAdvice(RestartAdvice.RestartNeeded); 470 + default: 471 + updateAdvice(RestartAdvice.ReloadNeeded); 472 + continue; 473 + } 474 + } 475 + 476 + const initConfig = typeof initState === "boolean" ? {} : { ...initState?.config }; 477 + const newConfig = typeof newState === "boolean" ? {} : { ...newState?.config }; 478 + 479 + const def = ext.manifest.settings; 480 + if (!def) continue; 481 + 482 + for (const key in def) { 483 + const defaultValue = def[key].default; 484 + 485 + initConfig[key] ??= defaultValue; 486 + newConfig[key] ??= defaultValue; 487 + } 488 + 489 + const changedKeys = diff(initConfig, newConfig, { cyclesFix: false }).map((c) => c.path[0]); 490 + for (const key in def) { 491 + if (!changedKeys.includes(key)) continue; 492 + 493 + const advice = def[key].advice; 494 + switch (advice) { 495 + case ExtensionSettingsAdvice.None: 496 + updateAdvice(RestartAdvice.NotNeeded); 497 + continue; 498 + case ExtensionSettingsAdvice.Reload: 499 + updateAdvice(RestartAdvice.ReloadNeeded); 500 + continue; 501 + case ExtensionSettingsAdvice.Restart: 502 + updateAdvice(RestartAdvice.RestartNeeded); 503 + continue; 504 + default: 505 + updateAdvice(RestartAdvice.ReloadSuggested); 506 + } 507 + } 508 + } 509 + 510 + return returnedAdvice; 511 + } 512 + 513 + async writeConfig() { 263 514 try { 264 - moonlightNode.writeConfig(this.config); 265 - this.origConfig = this.clone(this.config); 266 - } catch (e) { 267 - logger.error("Error writing config", e); 515 + this.submitting = true; 516 + this.emitChange(); 517 + 518 + await moonlightNode.writeConfig(this.config); 519 + await this.processConfigChanged(); 520 + } finally { 521 + this.submitting = false; 522 + this.emitChange(); 268 523 } 524 + } 269 525 270 - this.submitting = false; 526 + private async processConfigChanged() { 527 + this.savedConfig = this.clone(this.config); 528 + this.restartAdvice = this.#computeRestartAdvice(); 271 529 this.modified = false; 530 + 531 + const modifiedRepos = diff(this.savedConfig.repositories, this.config.repositories); 532 + if (modifiedRepos.length !== 0) await this.checkUpdates(); 533 + 272 534 this.emitChange(); 273 535 } 274 536 275 537 reset() { 276 538 this.submitting = false; 277 539 this.modified = false; 278 - this.config = this.clone(this.origConfig); 540 + this.config = this.clone(this.savedConfig); 279 541 this.emitChange(); 542 + } 543 + 544 + restartDiscord() { 545 + if (moonlightNode.isBrowser) { 546 + window.location.reload(); 547 + } else { 548 + // @ts-expect-error TODO: DiscordNative 549 + window.DiscordNative.app.relaunch(); 550 + } 280 551 } 281 552 282 553 // 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 + }
+29 -34
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
··· 1 1 import { LogLevel } from "@moonlight-mod/types"; 2 2 3 - const logLevels = Object.values(LogLevel).filter( 4 - (v) => typeof v === "string" 5 - ) as string[]; 3 + const logLevels = Object.values(LogLevel).filter((v) => typeof v === "string") as string[]; 6 4 7 - import React from "@moonlight-mod/wp/common_react"; 5 + import React from "@moonlight-mod/wp/react"; 8 6 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 9 7 import { 10 8 FormDivider, ··· 12 10 FormText, 13 11 FormSwitch, 14 12 TextInput, 15 - Flex, 16 13 Button, 17 14 SingleSelect, 18 15 Tooltip, 19 16 Clickable 20 - } from "@moonlight-mod/wp/common_components"; 21 - import CommonComponents from "@moonlight-mod/wp/common_components"; 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 22 23 23 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 24 24 25 - const FormClasses = spacepack.findByCode("dividerDefault:")[0].exports; 26 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 27 - 28 - let RemoveButtonClasses: any; 25 + let GuildSettingsRoleEditClasses: any; 29 26 spacepack 30 27 .lazyLoad( 31 28 "renderArtisanalHack", ··· 34 31 ) 35 32 .then( 36 33 () => 37 - (RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0] 38 - .exports) 34 + (GuildSettingsRoleEditClasses = spacepack.require( 35 + "discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css" 36 + )) 39 37 ); 40 38 41 - const { CircleXIcon } = CommonComponents; 42 39 function RemoveEntryButton({ onClick }: { onClick: () => void }) { 43 40 return ( 44 - <div className={RemoveButtonClasses.removeButtonContainer}> 41 + <div className={GuildSettingsRoleEditClasses.removeButtonContainer}> 45 42 <Tooltip text="Remove entry" position="top"> 46 43 {(props: any) => ( 47 - <Clickable 48 - {...props} 49 - className={RemoveButtonClasses.removeButton} 50 - onClick={onClick} 51 - > 44 + <Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}> 52 45 <CircleXIcon width={24} height={24} /> 53 46 </Clickable> 54 47 )} ··· 57 50 ); 58 51 } 59 52 60 - function ArrayFormItem({ 61 - config 62 - }: { 63 - config: "repositories" | "devSearchPaths"; 64 - }) { 53 + function ArrayFormItem({ config }: { config: "repositories" | "devSearchPaths" }) { 65 54 const items = MoonbaseSettingsStore.getConfigOption(config) ?? []; 66 55 return ( 67 56 <Flex ··· 119 108 export default function ConfigPage() { 120 109 return ( 121 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> 122 121 <FormItem title="Repositories"> 123 - <FormText className={Margins.marginBottom4}> 124 - A list of remote repositories to display extensions from 125 - </FormText> 122 + <FormText className={Margins.marginBottom4}>A list of remote repositories to display extensions from</FormText> 126 123 <ArrayFormItem config="repositories" /> 127 124 </FormItem> 128 - <FormDivider className={FormClasses.dividerDefault} /> 125 + <FormDivider className={FormSwitchClasses.dividerDefault} /> 129 126 <FormItem title="Extension search paths" className={Margins.marginTop20}> 130 127 <FormText className={Margins.marginBottom4}> 131 128 A list of local directories to search for built extensions 132 129 </FormText> 133 130 <ArrayFormItem config="devSearchPaths" /> 134 131 </FormItem> 135 - <FormDivider className={FormClasses.dividerDefault} /> 132 + <FormDivider className={FormSwitchClasses.dividerDefault} /> 136 133 <FormSwitch 137 134 className={Margins.marginTop20} 138 - value={MoonbaseSettingsStore.getConfigOption("patchAll")} 135 + value={MoonbaseSettingsStore.getConfigOption("patchAll") ?? false} 139 136 onChange={(value: boolean) => { 140 137 MoonbaseSettingsStore.setConfigOption("patchAll", value); 141 138 }} ··· 152 149 value: o.toLowerCase(), 153 150 label: o[0] + o.slice(1).toLowerCase() 154 151 }))} 155 - onChange={(v) => 156 - MoonbaseSettingsStore.setConfigOption("loggerLevel", v) 157 - } 152 + onChange={(v) => MoonbaseSettingsStore.setConfigOption("loggerLevel", v)} 158 153 /> 159 154 </FormItem> 160 155 </>
+285 -156
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
··· 1 1 import { ExtensionState } from "../../../types"; 2 - import { ExtensionLoadSource } from "@moonlight-mod/types"; 3 - 4 - import React from "@moonlight-mod/wp/common_react"; 5 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 6 - import CommonComponents from "@moonlight-mod/wp/common_components"; 7 - import * as Flux from "@moonlight-mod/wp/common_flux"; 2 + import { constants, ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types"; 8 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"; 9 32 import ExtensionInfo from "./info"; 10 33 import Settings from "./settings"; 34 + import { doGenericExtensionPopup, doMissingExtensionPopup } from "./popup"; 11 35 12 36 export enum ExtensionPage { 13 37 Info, 14 38 Description, 39 + Changelog, 15 40 Settings 16 41 } 17 42 18 - import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 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 + }); 19 72 20 - const { DownloadIcon, TrashIcon, CircleWarningIcon } = CommonComponents; 73 + const [tab, setTab] = React.useState( 74 + update != null && ext?.changelog != null ? ExtensionPage.Changelog : ExtensionPage.Info 75 + ); 21 76 22 - const PanelButton = spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.Z; 23 - const TabBarClasses = spacepack.findByExports( 24 - "tabBar", 25 - "tabBarItem", 26 - "headerContentWrapper" 27 - )[0].exports; 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); 28 90 29 - export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { 30 - const [tab, setTab] = React.useState(ExtensionPage.Info); 31 - const [restartNeeded, setRestartNeeded] = React.useState(false); 91 + const enabledDependants = useStateFromStores([MoonbaseSettingsStore], () => 92 + Object.keys(MoonbaseSettingsStore.extensions) 93 + .filter((uniqueId) => { 94 + const potentialDependant = MoonbaseSettingsStore.getExtension(parseInt(uniqueId)); 32 95 33 - const { ext, enabled, busy, update, conflicting } = Flux.useStateFromStores( 34 - [MoonbaseSettingsStore], 35 - () => { 36 - return { 37 - ext: MoonbaseSettingsStore.getExtension(uniqueId), 38 - enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId), 39 - busy: MoonbaseSettingsStore.busy, 40 - update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId), 41 - conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId) 42 - }; 43 - } 96 + return ( 97 + potentialDependant.manifest.dependencies?.includes(ext?.id) && 98 + MoonbaseSettingsStore.getExtensionEnabled(parseInt(uniqueId)) 99 + ); 100 + }) 101 + .map((a) => MoonbaseSettingsStore.getExtension(parseInt(a))) 44 102 ); 103 + const implicitlyEnabled = enabledDependants.length > 0; 45 104 46 - // Why it work like that :sob: 47 - if (ext == null) return <></>; 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 + ); 48 111 49 - const { 50 - Card, 51 - CardClasses, 52 - Flex, 53 - Text, 54 - MarkdownParser, 55 - Switch, 56 - TabBar, 57 - Button 58 - } = CommonComponents; 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 + )} 59 125 60 - const tagline = ext.manifest?.meta?.tagline; 61 - const settings = ext.manifest?.settings; 62 - const description = ext.manifest?.meta?.description; 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 + )} 63 131 64 - return ( 65 - <Card editable={true} className={CardClasses.card}> 66 - <div className={CardClasses.cardHeader}> 67 - <Flex direction={Flex.Direction.VERTICAL}> 68 - <Flex direction={Flex.Direction.HORIZONTAL}> 69 - <Text variant="text-md/semibold"> 70 - {ext.manifest?.meta?.name ?? ext.id} 71 - </Text> 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 + )} 72 137 </Flex> 73 138 74 - {tagline != null && ( 75 - <Text variant="text-sm/normal"> 76 - {MarkdownParser.parse(tagline)} 77 - </Text> 78 - )} 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> 79 248 </Flex> 249 + </div> 80 250 81 - <Flex 82 - direction={Flex.Direction.HORIZONTAL} 83 - align={Flex.Align.END} 84 - justify={Flex.Justify.END} 85 - > 86 - {ext.state === ExtensionState.NotDownloaded ? ( 87 - <Button 88 - color={Button.Colors.BRAND} 89 - submitting={busy} 90 - disabled={conflicting} 91 - onClick={() => { 92 - MoonbaseSettingsStore.installExtension(uniqueId); 93 - }} 94 - > 95 - Install 96 - </Button> 97 - ) : ( 98 - <div 99 - // too lazy to learn how <Flex /> works lmao 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} 100 259 style={{ 101 - display: "flex", 102 - alignItems: "center", 103 - gap: "1rem" 260 + padding: "0 20px" 104 261 }} 105 262 > 106 - {ext.source.type === ExtensionLoadSource.Normal && ( 107 - <PanelButton 108 - icon={TrashIcon} 109 - tooltipText="Delete" 110 - onClick={() => { 111 - MoonbaseSettingsStore.deleteExtension(uniqueId); 112 - }} 113 - /> 114 - )} 263 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Info}> 264 + Info 265 + </TabBar.Item> 115 266 116 - {update != null && ( 117 - <PanelButton 118 - icon={DownloadIcon} 119 - tooltipText="Update" 120 - onClick={() => { 121 - MoonbaseSettingsStore.installExtension(uniqueId); 122 - }} 123 - /> 267 + {description != null && ( 268 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Description}> 269 + Description 270 + </TabBar.Item> 124 271 )} 125 272 126 - {restartNeeded && ( 127 - <PanelButton 128 - icon={() => ( 129 - <CircleWarningIcon 130 - color={CommonComponents.tokens.colors.STATUS_DANGER} 131 - /> 132 - )} 133 - onClick={() => window.location.reload()} 134 - tooltipText="You will need to reload/restart your client for this extension to work properly." 135 - /> 273 + {changelog != null && ( 274 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Changelog}> 275 + Changelog 276 + </TabBar.Item> 136 277 )} 137 278 138 - <Switch 139 - checked={enabled} 140 - onChange={() => { 141 - setRestartNeeded(true); 142 - MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled); 143 - }} 144 - /> 145 - </div> 146 - )} 147 - </Flex> 148 - </div> 279 + {settings != null && ( 280 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Settings}> 281 + Settings 282 + </TabBar.Item> 283 + )} 284 + </TabBar> 149 285 150 - <div> 151 - {(description != null || settings != null) && ( 152 - <TabBar 153 - selectedItem={tab} 154 - type="top" 155 - onItemSelect={setTab} 156 - className={TabBarClasses.tabBar} 157 - style={{ 158 - padding: "0 20px" 159 - }} 160 - > 161 - <TabBar.Item 162 - className={TabBarClasses.tabBarItem} 163 - id={ExtensionPage.Info} 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" 164 292 > 165 - Info 166 - </TabBar.Item> 167 - 168 - {description != null && ( 169 - <TabBar.Item 170 - className={TabBarClasses.tabBarItem} 171 - id={ExtensionPage.Description} 172 - > 173 - Description 174 - </TabBar.Item> 175 - )} 176 - 177 - {settings != null && ( 178 - <TabBar.Item 179 - className={TabBarClasses.tabBarItem} 180 - id={ExtensionPage.Settings} 181 - > 182 - Settings 183 - </TabBar.Item> 184 - )} 185 - </TabBar> 293 + {linkButtons.length > 0 && linkButtons} 294 + </Flex> 295 + </Flex> 186 296 )} 187 297 188 298 <Flex 189 299 justify={Flex.Justify.START} 190 300 wrap={Flex.Wrap.WRAP} 191 301 style={{ 192 - padding: "16px 16px" 302 + padding: "16px 16px", 303 + // This looks wonky in the settings tab 304 + rowGap: tab === ExtensionPage.Info ? "16px" : undefined 193 305 }} 194 306 > 195 - {tab === ExtensionPage.Info && <ExtensionInfo ext={ext} />} 307 + {tab === ExtensionPage.Info && <ExtensionInfo ext={ext} selectTag={selectTag} />} 196 308 {tab === ExtensionPage.Description && ( 197 - <Text variant="text-md/normal"> 198 - {MarkdownParser.parse(description ?? "*No 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, 313 + allowList: 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 + })} 199 324 </Text> 200 325 )} 201 - {tab === ExtensionPage.Settings && <Settings ext={ext} />} 326 + {tab === ExtensionPage.Settings && ( 327 + <ErrorBoundary> 328 + <Settings ext={ext} /> 329 + </ErrorBoundary> 330 + )} 202 331 </Flex> 203 332 </div> 204 333 </Card>
+119 -119
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
··· 1 1 import { tagNames } from "./info"; 2 - 3 2 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 4 - import React from "@moonlight-mod/wp/common_react"; 5 - import * as Flux from "@moonlight-mod/wp/common_flux"; 3 + import * as React from "@moonlight-mod/wp/react"; 4 + import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 6 5 import { WindowStore } from "@moonlight-mod/wp/common_stores"; 7 6 import { 8 7 Button, ··· 11 10 Popout, 12 11 Dialog, 13 12 Menu, 14 - MenuGroup, 15 - MenuCheckboxItem, 16 - MenuItem 17 - } from "@moonlight-mod/wp/common_components"; 18 - import CommonComponents from "@moonlight-mod/wp/common_components"; 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"; 19 23 20 24 export enum Filter { 21 25 Core = 1 << 0, ··· 24 28 Enabled = 1 << 3, 25 29 Disabled = 1 << 4, 26 30 Installed = 1 << 5, 27 - Repository = 1 << 6 31 + Repository = 1 << 6, 32 + Incompatible = 1 << 7, 33 + Deprecated = 1 << 8 28 34 } 29 - export const defaultFilter = ~(~0 << 7); 30 - 31 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 32 - const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0] 33 - .exports; 35 + export const defaultFilter = 127 as Filter; 34 36 35 - let FilterDialogClasses: any; 36 - let FilterBarClasses: any; 37 + let HeaderClasses: any; 38 + let ForumsClasses: any; 39 + let SortMenuClasses: any; 37 40 spacepack 38 - .lazyLoad( 39 - '"Missing channel in Channel.openChannelContextMenu"', 40 - /e\("(\d+)"\)/g, 41 - /webpackId:(\d+?),/ 42 - ) 41 + .lazyLoad('"Missing channel in Channel.openChannelContextMenu"', /e\("(\d+)"\)/g, /webpackId:(\d+?),/) 43 42 .then(() => { 44 - FilterBarClasses = spacepack.findByCode("tagsButtonWithCount:")[0].exports; 45 - FilterDialogClasses = spacepack.findByCode( 46 - "countContainer:", 47 - "tagContainer:" 48 - )[0].exports; 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"); 49 46 }); 50 47 51 - const TagItem = spacepack.findByCode(".FORUM_TAG_A11Y_FILTER_BY_TAG")[0].exports 52 - .Z; 53 - 54 - const { ChevronSmallDownIcon, ChevronSmallUpIcon, ArrowsUpDownIcon } = 55 - CommonComponents; 56 - 57 - function toggleTag( 58 - selectedTags: Set<string>, 59 - setSelectedTags: (tags: Set<string>) => void, 60 - tag: string 61 - ) { 48 + function toggleTag(selectedTags: Set<string>, setSelectedTags: (tags: Set<string>) => void, tag: string) { 62 49 const newState = new Set(selectedTags); 63 50 if (newState.has(tag)) newState.delete(tag); 64 51 else newState.add(tag); ··· 74 61 setFilter: (filter: Filter) => void; 75 62 closePopout: () => void; 76 63 }) { 77 - const toggleFilter = (set: Filter) => 78 - setFilter(filter & set ? filter & ~set : filter | set); 64 + const toggleFilter = (set: Filter) => setFilter(filter & set ? filter & ~set : filter | set); 79 65 80 66 return ( 81 67 <div className={SortMenuClasses.container}> 82 - <Menu navId="sort-filter" hideScrollbar={true} onClose={closePopout}> 68 + <Menu navId="sort-filter" hideScroller={true} onClose={closePopout}> 83 69 <MenuGroup label="Type"> 84 70 <MenuCheckboxItem 85 71 id="t-core" 86 72 label="Core" 87 - checked={filter & Filter.Core} 73 + checked={(filter & Filter.Core) === Filter.Core} 88 74 action={() => toggleFilter(Filter.Core)} 89 75 /> 90 76 <MenuCheckboxItem 91 77 id="t-normal" 92 78 label="Normal" 93 - checked={filter & Filter.Normal} 79 + checked={(filter & Filter.Normal) === Filter.Normal} 94 80 action={() => toggleFilter(Filter.Normal)} 95 81 /> 96 82 <MenuCheckboxItem 97 83 id="t-developer" 98 84 label="Developer" 99 - checked={filter & Filter.Developer} 85 + checked={(filter & Filter.Developer) === Filter.Developer} 100 86 action={() => toggleFilter(Filter.Developer)} 101 87 /> 102 88 </MenuGroup> ··· 104 90 <MenuCheckboxItem 105 91 id="s-enabled" 106 92 label="Enabled" 107 - checked={filter & Filter.Enabled} 93 + checked={(filter & Filter.Enabled) === Filter.Enabled} 108 94 action={() => toggleFilter(Filter.Enabled)} 109 95 /> 110 96 <MenuCheckboxItem 111 97 id="s-disabled" 112 98 label="Disabled" 113 - checked={filter & Filter.Disabled} 99 + checked={(filter & Filter.Disabled) === Filter.Disabled} 114 100 action={() => toggleFilter(Filter.Disabled)} 115 101 /> 116 102 </MenuGroup> ··· 118 104 <MenuCheckboxItem 119 105 id="l-installed" 120 106 label="Installed" 121 - checked={filter & Filter.Installed} 107 + checked={(filter & Filter.Installed) === Filter.Installed} 122 108 action={() => toggleFilter(Filter.Installed)} 123 109 /> 124 110 <MenuCheckboxItem 125 111 id="l-repository" 126 112 label="Repository" 127 - checked={filter & Filter.Repository} 113 + checked={(filter & Filter.Repository) === Filter.Repository} 128 114 action={() => toggleFilter(Filter.Repository)} 129 115 /> 130 116 </MenuGroup> 131 117 <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 + /> 132 130 <MenuItem 133 131 id="reset-all" 134 132 className={SortMenuClasses.clearText} 135 - label={ 136 - <Text variant="text-sm/medium" color="none"> 137 - Reset to default 138 - </Text> 139 - } 133 + label="Reset to default" 140 134 action={() => { 141 135 setFilter(defaultFilter); 142 136 closePopout(); ··· 148 142 ); 149 143 } 150 144 151 - function TagButtonPopout({ 152 - selectedTags, 153 - setSelectedTags, 154 - setPopoutRef, 155 - closePopout 156 - }: any) { 145 + function TagButtonPopout({ selectedTags, setSelectedTags, setPopoutRef, closePopout }: any) { 157 146 return ( 158 - <Dialog ref={setPopoutRef} className={FilterDialogClasses.container}> 159 - <div className={FilterDialogClasses.header}> 160 - <div className={FilterDialogClasses.headerLeft}> 161 - <Heading 162 - color="interactive-normal" 163 - variant="text-xs/bold" 164 - className={FilterDialogClasses.headerText} 165 - > 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}> 166 151 Select tags 167 152 </Heading> 168 - <div className={FilterDialogClasses.countContainer}> 169 - <Text 170 - className={FilterDialogClasses.countText} 171 - color="none" 172 - variant="text-xs/medium" 173 - > 153 + <div className={HeaderClasses.countContainer}> 154 + <Text className={HeaderClasses.countText} color="none" variant="text-xs/medium"> 174 155 {selectedTags.size} 175 156 </Text> 176 157 </div> 177 158 </div> 178 159 </div> 179 - <div className={FilterDialogClasses.tagContainer}> 160 + <div className={HeaderClasses.tagContainer}> 180 161 {Object.keys(tagNames).map((tag) => ( 181 162 <TagItem 182 163 key={tag} 183 - className={FilterDialogClasses.tag} 184 - tag={{ name: tagNames[tag as keyof typeof tagNames] }} 164 + className={HeaderClasses.tag} 165 + tag={{ name: tagNames[tag as keyof typeof tagNames], id: tagNames[tag as keyof typeof tagNames] }} 185 166 onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 186 167 selected={selectedTags.has(tag)} 187 168 /> 188 169 ))} 189 170 </div> 190 - <div className={FilterDialogClasses.separator} /> 171 + <div className={HeaderClasses.separator} /> 191 172 <Button 192 173 look={Button.Looks.LINK} 193 174 size={Button.Sizes.MIN} 194 175 color={Button.Colors.CUSTOM} 195 - className={FilterDialogClasses.clear} 176 + className={HeaderClasses.clear} 196 177 onClick={() => { 197 178 setSelectedTags(new Set()); 198 179 closePopout(); ··· 217 198 selectedTags: Set<string>; 218 199 setSelectedTags: (tags: Set<string>) => void; 219 200 }) { 220 - const windowSize = Flux.useStateFromStores([WindowStore], () => 221 - WindowStore.windowSize() 222 - ); 201 + const windowSize = useStateFromStores([WindowStore], () => WindowStore.windowSize()); 223 202 224 203 const tagsContainer = React.useRef<HTMLDivElement>(null); 225 204 const tagListInner = React.useRef<HTMLDivElement>(null); 226 205 const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0); 206 + const [checkingUpdates, setCheckingUpdates] = React.useState(false); 207 + 227 208 React.useLayoutEffect(() => { 228 209 if (tagsContainer.current === null || tagListInner.current === null) return; 229 - const { left: containerX, top: containerY } = 230 - tagsContainer.current.getBoundingClientRect(); 210 + const { left: containerX, top: containerY } = tagsContainer.current.getBoundingClientRect(); 231 211 let offset = 0; 232 212 for (const child of tagListInner.current.children) { 233 - const { 234 - right: childX, 235 - top: childY, 236 - height 237 - } = child.getBoundingClientRect(); 213 + const { right: childX, top: childY, height } = child.getBoundingClientRect(); 238 214 if (childY - containerY > height) break; 239 215 const newOffset = childX - containerX; 240 216 if (newOffset > offset) { ··· 242 218 } 243 219 } 244 220 setTagsButtonOffset(offset); 245 - }, [windowSize]); 221 + }, [windowSize, tagsContainer.current, tagListInner.current, tagListInner.current?.getBoundingClientRect()?.width]); 246 222 247 223 return ( 248 224 <div ··· 250 226 style={{ 251 227 paddingTop: "12px" 252 228 }} 253 - className={`${FilterBarClasses.tagsContainer} ${Margins.marginBottom8}`} 229 + className={`${ForumsClasses.tagsContainer} ${Margins.marginBottom8}`} 254 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> 255 256 <Popout 256 257 renderPopout={({ closePopout }: any) => ( 257 - <FilterButtonPopout 258 - filter={filter} 259 - setFilter={setFilter} 260 - closePopout={closePopout} 261 - /> 258 + <FilterButtonPopout filter={filter} setFilter={setFilter} closePopout={closePopout} /> 262 259 )} 263 260 position="bottom" 264 261 align="left" ··· 268 265 {...props} 269 266 size={Button.Sizes.MIN} 270 267 color={Button.Colors.CUSTOM} 271 - className={FilterBarClasses.sortDropdown} 272 - innerClassName={FilterBarClasses.sortDropdownInner} 268 + className={ForumsClasses.sortDropdown} 269 + innerClassName={ForumsClasses.sortDropdownInner} 273 270 > 274 271 <ArrowsUpDownIcon size="xs" /> 275 - <Text 276 - className={FilterBarClasses.sortDropdownText} 277 - variant="text-sm/medium" 278 - color="interactive-normal" 279 - > 272 + <Text className={ForumsClasses.sortDropdownText} variant="text-sm/medium" color="interactive-normal"> 280 273 Sort & filter 281 274 </Text> 282 275 {isShown ? ( ··· 287 280 </Button> 288 281 )} 289 282 </Popout> 290 - <div className={FilterBarClasses.divider} /> 291 - <div className={FilterBarClasses.tagList}> 292 - <div ref={tagListInner} className={FilterBarClasses.tagListInner}> 283 + <div className={ForumsClasses.divider} /> 284 + <div className={ForumsClasses.tagList}> 285 + <div ref={tagListInner} className={ForumsClasses.tagListInner}> 293 286 {Object.keys(tagNames).map((tag) => ( 294 287 <TagItem 295 288 key={tag} 296 - className={FilterBarClasses.tag} 297 - tag={{ name: tagNames[tag as keyof typeof tagNames] }} 289 + className={ForumsClasses.tag} 290 + tag={{ name: tagNames[tag as keyof typeof tagNames], id: tag }} 298 291 onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 299 292 selected={selectedTags.has(tag)} 300 293 /> ··· 322 315 left: tagsButtonOffset 323 316 }} 324 317 // TODO: Use Discord's class name utility 325 - className={`${FilterBarClasses.tagsButton} ${ 326 - selectedTags.size > 0 ? FilterBarClasses.tagsButtonWithCount : "" 327 - }`} 328 - innerClassName={FilterBarClasses.tagsButtonInner} 318 + className={`${ForumsClasses.tagsButton} ${selectedTags.size > 0 ? ForumsClasses.tagsButtonWithCount : ""}`} 319 + innerClassName={ForumsClasses.tagsButtonInner} 329 320 > 330 321 {selectedTags.size > 0 ? ( 331 - <div 332 - style={{ boxSizing: "content-box" }} 333 - className={FilterBarClasses.countContainer} 334 - > 335 - <Text 336 - className={FilterBarClasses.countText} 337 - color="none" 338 - variant="text-xs/medium" 339 - > 322 + <div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}> 323 + <Text className={ForumsClasses.countText} color="none" variant="text-xs/medium"> 340 324 {selectedTags.size} 341 325 </Text> 342 326 </div> ··· 351 335 </Button> 352 336 )} 353 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 354 </div> 355 355 ); 356 356 }
+110 -51
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
··· 3 3 import FilterBar, { Filter, defaultFilter } from "./filterBar"; 4 4 import ExtensionCard from "./card"; 5 5 6 - import React from "@moonlight-mod/wp/common_react"; 6 + import React from "@moonlight-mod/wp/react"; 7 7 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 8 - import * as Flux from "@moonlight-mod/wp/common_flux"; 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"; 9 16 10 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"; 11 21 12 - const SearchBar: any = Object.values( 13 - spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0].exports 14 - )[0]; 22 + const SearchBar = spacepack.require("discord/uikit/search/SearchBar").default; 23 + 24 + const validTags: string[] = Object.values(ExtensionTag); 15 25 16 26 export default function ExtensionsPage() { 17 - const moonbaseId = MoonbaseSettingsStore.getExtensionUniqueId("moonbase")!; 18 - const { extensions, savedFilter } = Flux.useStateFromStoresObject( 19 - [MoonbaseSettingsStore], 20 - () => { 21 - return { 22 - extensions: MoonbaseSettingsStore.extensions, 23 - savedFilter: MoonbaseSettingsStore.getExtensionConfig( 24 - moonbaseId, 25 - "filter" 26 - ) 27 - }; 28 - } 29 - ); 27 + const { extensions, savedFilter } = useStateFromStoresObject([MoonbaseSettingsStore], () => { 28 + return { 29 + extensions: MoonbaseSettingsStore.extensions, 30 + savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw<number>("moonbase", "filter", defaultFilter) 31 + }; 32 + }); 30 33 31 34 const [query, setQuery] = React.useState(""); 35 + const [hitUpdateAll, setHitUpdateAll] = React.useState(false); 36 + 37 + const filterState = React.useState(defaultFilter); 32 38 33 39 let filter: Filter, setFilter: (filter: Filter) => void; 34 - if (moonlight.getConfigOption<boolean>("moonbase", "saveFilter")) { 40 + if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "saveFilter", false)) { 35 41 filter = savedFilter ?? defaultFilter; 36 - setFilter = (filter) => 37 - MoonbaseSettingsStore.setExtensionConfig(moonbaseId, "filter", filter); 42 + setFilter = (filter) => MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter); 38 43 } else { 39 - const state = React.useState(defaultFilter); 40 - filter = state[0]; 41 - setFilter = state[1]; 44 + filter = filterState[0]; 45 + setFilter = filterState[1]; 42 46 } 47 + 43 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 + 44 58 const sorted = Object.values(extensions).sort((a, b) => { 45 59 const aName = a.manifest.meta?.name ?? a.id; 46 60 const bName = b.manifest.meta?.name ?? b.id; ··· 49 63 50 64 const filtered = sorted.filter( 51 65 (ext) => 52 - (ext.manifest.meta?.name?.toLowerCase().includes(query) || 66 + (query === "" || 67 + ext.manifest.id?.toLowerCase().includes(query) || 68 + ext.manifest.meta?.name?.toLowerCase().includes(query) || 53 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 + )) || 54 78 ext.manifest.meta?.description?.toLowerCase().includes(query)) && 55 - [...selectedTags.values()].every( 56 - (tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag) 57 - ) && 79 + [...selectedTags.values()].every((tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)) && 58 80 // This seems very bad, sorry 59 81 !( 60 - (!(filter & Filter.Core) && 61 - ext.source.type === ExtensionLoadSource.Core) || 62 - (!(filter & Filter.Normal) && 63 - ext.source.type === ExtensionLoadSource.Normal) || 64 - (!(filter & Filter.Developer) && 65 - ext.source.type === ExtensionLoadSource.Developer) || 66 - (!(filter & Filter.Enabled) && 67 - MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 68 - (!(filter & Filter.Disabled) && 69 - !MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 70 - (!(filter & Filter.Installed) && 71 - ext.state !== ExtensionState.NotDownloaded) || 72 - (!(filter & Filter.Repository) && 73 - ext.state === ExtensionState.NotDownloaded) 74 - ) 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) 75 96 ); 97 + 98 + // Prioritize extensions with updates 99 + const filteredWithUpdates = filtered.filter((ext) => ext!.hasUpdate); 100 + const filteredWithoutUpdates = filtered.filter((ext) => !ext!.hasUpdate); 76 101 77 102 return ( 78 103 <> ··· 89 114 spellCheck: "false" 90 115 }} 91 116 /> 92 - <FilterBar 93 - filter={filter} 94 - setFilter={setFilter} 95 - selectedTags={selectedTags} 96 - setSelectedTags={setSelectedTags} 97 - /> 98 - {filtered.map((ext) => ( 99 - <ExtensionCard uniqueId={ext.uniqueId} key={ext.id} /> 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> 100 159 ))} 101 160 </> 102 161 );
+55 -54
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx
··· 1 1 import { ExtensionTag } from "@moonlight-mod/types"; 2 2 import { MoonbaseExtension } from "../../../types"; 3 3 4 - import React from "@moonlight-mod/wp/common_react"; 5 - import CommonComponents from "@moonlight-mod/wp/common_components"; 6 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 4 + import React from "@moonlight-mod/wp/react"; 5 + import { Text } from "@moonlight-mod/wp/discord/components/common/index"; 7 6 8 7 type Dependency = { 9 8 id: string; ··· 34 33 [ExtensionTag.Library]: "Library" 35 34 }; 36 35 37 - const UserInfoClasses = spacepack.findByCode( 38 - "infoScroller", 39 - "userInfoSection", 40 - "userInfoSectionHeader" 41 - )[0].exports; 42 - 43 36 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 44 37 45 - function InfoSection({ 46 - title, 47 - children 48 - }: { 49 - title: string; 50 - children: React.ReactNode; 51 - }) { 38 + function InfoSection({ title, children }: { title: string; children: React.ReactNode }) { 52 39 return ( 53 40 <div 54 41 style={{ 55 42 marginRight: "1em" 56 43 }} 57 44 > 58 - <CommonComponents.Text 59 - variant="eyebrow" 60 - className={UserInfoClasses.userInfoSectionHeader} 61 - > 45 + <Text variant="eyebrow" className="moonlight-card-info-header"> 62 46 {title} 63 - </CommonComponents.Text> 47 + </Text> 64 48 65 - <CommonComponents.Text variant="text-sm/normal"> 66 - {children} 67 - </CommonComponents.Text> 49 + <Text variant="text-sm/normal">{children}</Text> 68 50 </div> 69 51 ); 70 52 } 71 53 72 54 function Badge({ 73 55 color, 74 - children 56 + children, 57 + style = {}, 58 + onClick 75 59 }: { 76 60 color: string; 77 61 children: React.ReactNode; 62 + style?: React.CSSProperties; 63 + onClick?: () => void; 78 64 }) { 65 + if (onClick) style.cursor ??= "pointer"; 79 66 return ( 80 67 <span 81 - style={{ 82 - borderRadius: ".1875rem", 83 - padding: "0 0.275rem", 84 - marginRight: "0.4em", 85 - backgroundColor: color, 86 - color: "#fff" 87 - }} 68 + className="moonlight-card-badge" 69 + style={ 70 + { 71 + "--badge-color": color, 72 + ...style 73 + } as React.CSSProperties 74 + } 75 + onClick={onClick} 88 76 > 89 77 {children} 90 78 </span> 91 79 ); 92 80 } 93 81 94 - export default function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) { 82 + export default function ExtensionInfo({ 83 + ext, 84 + selectTag 85 + }: { 86 + ext: MoonbaseExtension; 87 + selectTag: (tag: string) => void; 88 + }) { 95 89 const authors = ext.manifest?.meta?.authors; 96 90 const tags = ext.manifest?.meta?.tags; 97 91 const version = ext.manifest?.version; 98 92 99 93 const dependencies: Dependency[] = []; 94 + const incompatible: Dependency[] = []; 95 + 100 96 if (ext.manifest.dependencies != null) { 101 97 dependencies.push( 102 98 ...ext.manifest.dependencies.map((dep) => ({ ··· 116 112 } 117 113 118 114 if (ext.manifest.incompatible != null) { 119 - dependencies.push( 115 + incompatible.push( 120 116 ...ext.manifest.incompatible.map((dep) => ({ 121 117 id: dep, 122 118 type: DependencyType.Incompatible ··· 154 150 <InfoSection title="Tags"> 155 151 {tags.map((tag, i) => { 156 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 + } 157 159 158 160 return ( 159 - <Badge 160 - key={i} 161 - color={ 162 - tag === ExtensionTag.DangerZone 163 - ? "var(--red-400)" 164 - : "var(--brand-500)" 165 - } 166 - > 161 + <Badge key={i} color={color} style={style} onClick={() => selectTag(tag)}> 167 162 {name} 168 163 </Badge> 169 164 ); ··· 174 169 {dependencies.length > 0 && ( 175 170 <InfoSection title="Dependencies"> 176 171 {dependencies.map((dep) => { 177 - const colors = { 178 - [DependencyType.Dependency]: "var(--brand-500)", 179 - [DependencyType.Optional]: "var(--orange-400)", 180 - [DependencyType.Incompatible]: "var(--red-400)" 181 - }; 182 - const color = colors[dep.type]; 183 - const id = MoonbaseSettingsStore.getExtensionUniqueId(dep.id); 184 - const name = 185 - (id !== null 186 - ? MoonbaseSettingsStore.getExtensionName(id!) 187 - : null) ?? dep.id; 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 + 188 189 return ( 189 - <Badge color={color} key={dep.id}> 190 + <Badge color="var(--bg-mod-strong)" key={dep.id}> 190 191 {name} 191 192 </Badge> 192 193 );
+211
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx
··· 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 27 + [ExtensionLoadSource.Core]: "Core extension", 28 + [ExtensionLoadSource.Normal]: "Extension repository" 29 + }; 30 + 31 + function ExtensionSelect({ 32 + id, 33 + candidates, 34 + option, 35 + setOption 36 + }: { 37 + id: string; 38 + candidates: MoonbaseExtension[]; 39 + option: string | undefined; 40 + setOption: (pick: string | undefined) => void; 41 + }) { 42 + return ( 43 + <SingleSelect 44 + key={id} 45 + autofocus={false} 46 + value={option} 47 + options={candidates.map((candidate) => { 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, 76 + candidates.length > 0 ? candidates[0].uniqueId.toString() : undefined 77 + ]) 78 + ) 79 + ); 80 + 81 + return ( 82 + <ConfirmModal 83 + body={ 84 + <Flex 85 + style={{ 86 + gap: "20px" 87 + }} 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 + 101 + <div 102 + style={{ 103 + display: "grid", 104 + gridTemplateColumns: "1fr 2fr", 105 + gap: "10px" 106 + }} 107 + > 108 + {Object.entries(deps).map(([id, candidates], i) => ( 109 + <> 110 + <Text 111 + variant="text-md/normal" 112 + style={{ 113 + alignSelf: "center", 114 + wordBreak: "break-word" 115 + }} 116 + > 117 + {MoonbaseSettingsStore.tryGetExtensionName(id)} 118 + </Text> 119 + 120 + <ExtensionSelect 121 + id={id} 122 + candidates={candidates} 123 + option={options[id]} 124 + setOption={(pick) => 125 + setOptions((prev) => ({ 126 + ...prev, 127 + [id]: pick 128 + })) 129 + } 130 + /> 131 + </> 132 + ))} 133 + </div> 134 + </Flex> 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) { 144 + MoonbaseSettingsStore.installExtension(parseInt(pick)); 145 + } 146 + } 147 + }} 148 + title="Extension dependencies" 149 + transitionState={transitionState} 150 + /> 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 + }
+133 -138
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
··· 9 9 10 10 import { ExtensionState, MoonbaseExtension } from "../../../types"; 11 11 12 - import React from "@moonlight-mod/wp/common_react"; 13 - import CommonComponents from "@moonlight-mod/wp/common_components"; 14 - import * as Flux from "@moonlight-mod/wp/common_flux"; 15 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 + ); 16 51 17 52 type SettingsProps = { 18 53 ext: MoonbaseExtension; ··· 20 55 setting: ExtensionSettingsManifest; 21 56 disabled: boolean; 22 57 }; 23 - 24 58 type SettingsComponent = React.ComponentType<SettingsProps>; 25 59 26 - import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 60 + const Margins = spacepack.require("discord/styles/shared/Margins.css"); 27 61 28 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 62 + function markdownify(str: string) { 63 + return MarkupUtils.parse(str, true, { 64 + hideSimpleEmbedContent: true, 65 + allowLinks: true 66 + }); 67 + } 29 68 30 69 function useConfigEntry<T>(uniqueId: number, name: string) { 31 - return Flux.useStateFromStores( 70 + return useStateFromStores( 32 71 [MoonbaseSettingsStore], 33 72 () => { 34 73 return { 35 74 value: MoonbaseSettingsStore.getExtensionConfig<T>(uniqueId, name), 36 - displayName: MoonbaseSettingsStore.getExtensionConfigName( 37 - uniqueId, 38 - name 39 - ), 40 - description: MoonbaseSettingsStore.getExtensionConfigDescription( 41 - uniqueId, 42 - name 43 - ) 75 + displayName: MoonbaseSettingsStore.getExtensionConfigName(uniqueId, name), 76 + description: MoonbaseSettingsStore.getExtensionConfigDescription(uniqueId, name) 44 77 }; 45 78 }, 46 79 [uniqueId, name] ··· 48 81 } 49 82 50 83 function Boolean({ ext, name, setting, disabled }: SettingsProps) { 51 - const { FormSwitch } = CommonComponents; 52 - const { value, displayName, description } = useConfigEntry<boolean>( 53 - ext.uniqueId, 54 - name 55 - ); 84 + const { value, displayName, description } = useConfigEntry<boolean>(ext.uniqueId, name); 56 85 57 86 return ( 58 87 <FormSwitch ··· 60 89 hideBorder={true} 61 90 disabled={disabled} 62 91 onChange={(value: boolean) => { 63 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); 92 + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 64 93 }} 65 - note={description} 94 + note={description != null ? markdownify(description) : undefined} 66 95 className={`${Margins.marginReset} ${Margins.marginTop20}`} 67 96 > 68 97 {displayName} ··· 71 100 } 72 101 73 102 function Number({ ext, name, setting, disabled }: SettingsProps) { 74 - const { FormItem, FormText, Slider } = CommonComponents; 75 - const { value, displayName, description } = useConfigEntry<number>( 76 - ext.uniqueId, 77 - name 78 - ); 103 + const { value, displayName, description } = useConfigEntry<number>(ext.uniqueId, name); 79 104 80 105 const castedSetting = setting as NumberSettingType; 81 - const min = castedSetting.min ?? 0; 82 - const max = castedSetting.max ?? 100; 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 + }; 83 113 84 114 return ( 85 115 <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.uniqueId, name, rounded); 95 - }} 96 - /> 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 + )} 97 134 </FormItem> 98 135 ); 99 136 } 100 137 101 138 function String({ ext, name, setting, disabled }: SettingsProps) { 102 - const { FormItem, FormText, TextInput } = CommonComponents; 103 - const { value, displayName, description } = useConfigEntry<string>( 104 - ext.uniqueId, 105 - name 106 - ); 139 + const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name); 107 140 108 141 return ( 109 142 <FormItem className={Margins.marginTop20} title={displayName}> 110 - {description && ( 111 - <FormText className={Margins.marginBottom8}>{description}</FormText> 112 - )} 143 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 113 144 <TextInput 114 145 value={value ?? ""} 115 - onChange={(value: string) => { 116 - if (disabled) return; 117 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); 118 - }} 146 + disabled={disabled} 147 + onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} 119 148 /> 120 149 </FormItem> 121 150 ); 122 151 } 123 152 124 153 function MultilineString({ ext, name, setting, disabled }: SettingsProps) { 125 - const { FormItem, FormText, TextArea } = CommonComponents; 126 - const { value, displayName, description } = useConfigEntry<string>( 127 - ext.uniqueId, 128 - name 129 - ); 154 + const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name); 130 155 131 156 return ( 132 157 <FormItem className={Margins.marginTop20} title={displayName}> 133 - {description && ( 134 - <FormText className={Margins.marginBottom8}>{description}</FormText> 135 - )} 158 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 136 159 <TextArea 137 160 rows={5} 138 161 value={value ?? ""} 162 + disabled={disabled} 139 163 className={"moonbase-resizeable"} 140 - onChange={(value: string) => { 141 - if (disabled) return; 142 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); 143 - }} 164 + onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} 144 165 /> 145 166 </FormItem> 146 167 ); 147 168 } 148 169 149 170 function Select({ ext, name, setting, disabled }: SettingsProps) { 150 - const { FormItem, FormText, SingleSelect } = CommonComponents; 151 - const { value, displayName, description } = useConfigEntry<string>( 152 - ext.uniqueId, 153 - name 154 - ); 171 + const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name); 155 172 156 173 const castedSetting = setting as SelectSettingType; 157 174 const options = castedSetting.options; 158 175 159 176 return ( 160 177 <FormItem className={Margins.marginTop20} title={displayName}> 161 - {description && ( 162 - <FormText className={Margins.marginBottom8}>{description}</FormText> 163 - )} 178 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 164 179 <SingleSelect 165 180 autofocus={false} 166 181 clearable={false} 167 182 value={value ?? ""} 168 - options={options.map((o: SelectOption) => 169 - typeof o === "string" ? { value: o, label: o } : o 170 - )} 183 + options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))} 171 184 onChange={(value: string) => { 172 185 if (disabled) return; 173 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); 186 + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 174 187 }} 175 188 /> 176 189 </FormItem> ··· 178 191 } 179 192 180 193 function MultiSelect({ ext, name, setting, disabled }: SettingsProps) { 181 - const { FormItem, FormText, Select, useVariableSelect, multiSelect } = 182 - CommonComponents; 183 - const { value, displayName, description } = useConfigEntry<string | string[]>( 184 - ext.uniqueId, 185 - name 186 - ); 194 + const { value, displayName, description } = useConfigEntry<string | string[]>(ext.uniqueId, name); 187 195 188 196 const castedSetting = setting as MultiSelectSettingType; 189 197 const options = castedSetting.options; 190 198 191 199 return ( 192 200 <FormItem className={Margins.marginTop20} title={displayName}> 193 - {description && ( 194 - <FormText className={Margins.marginBottom8}>{description}</FormText> 195 - )} 196 - <Select 201 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 202 + <DiscordSelect 197 203 autofocus={false} 198 204 clearable={false} 199 205 closeOnSelect={false} 200 - options={options.map((o: SelectOption) => 201 - typeof o === "string" ? { value: o, label: o } : o 202 - )} 206 + options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))} 203 207 {...useVariableSelect({ 204 208 onSelectInteraction: multiSelect, 205 - value: new Set(Array.isArray(value) ? value : [value]), 209 + value: value == null ? new Set() : new Set(Array.isArray(value) ? value : [value]), 206 210 onChange: (value: string) => { 207 211 if (disabled) return; 208 - MoonbaseSettingsStore.setExtensionConfig( 209 - ext.uniqueId, 210 - name, 211 - Array.from(value) 212 - ); 212 + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Array.from(value)); 213 213 } 214 214 })} 215 215 /> ··· 217 217 ); 218 218 } 219 219 220 - const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0] 221 - .exports; 222 - const CircleXIcon = CommonComponents.CircleXIcon; 223 - function RemoveEntryButton({ 224 - onClick, 225 - disabled 226 - }: { 227 - onClick: () => void; 228 - disabled: boolean; 229 - }) { 230 - const { Tooltip, Clickable } = CommonComponents; 220 + function RemoveEntryButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) { 231 221 return ( 232 - <div className={RemoveButtonClasses.removeButtonContainer}> 222 + <div className={GuildSettingsRoleEditClasses.removeButtonContainer}> 233 223 <Tooltip text="Remove entry" position="top"> 234 224 {(props: any) => ( 235 - <Clickable 236 - {...props} 237 - className={RemoveButtonClasses.removeButton} 238 - onClick={onClick} 239 - > 225 + <Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}> 240 226 <CircleXIcon width={16} height={16} /> 241 227 </Clickable> 242 228 )} ··· 246 232 } 247 233 248 234 function List({ ext, name, setting, disabled }: SettingsProps) { 249 - const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents; 250 - const { value, displayName, description } = useConfigEntry<string[]>( 251 - ext.uniqueId, 252 - name 253 - ); 235 + const { value, displayName, description } = useConfigEntry<string[]>(ext.uniqueId, name); 254 236 255 237 const entries = value ?? []; 256 - const updateConfig = () => 257 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, entries); 238 + const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries); 258 239 259 240 return ( 260 241 <FormItem className={Margins.marginTop20} title={displayName}> 261 - {description && ( 262 - <FormText className={Margins.marginBottom4}>{description}</FormText> 263 - )} 242 + {description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>} 264 243 <Flex direction={Flex.Direction.VERTICAL}> 265 244 {entries.map((val, i) => ( 266 245 // FIXME: stylesheets ··· 312 291 } 313 292 314 293 function Dictionary({ ext, name, setting, disabled }: SettingsProps) { 315 - const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents; 316 - const { value, displayName, description } = useConfigEntry< 317 - Record<string, string> 318 - >(ext.uniqueId, name); 294 + const { value, displayName, description } = useConfigEntry<Record<string, string>>(ext.uniqueId, name); 319 295 320 296 const entries = Object.entries(value ?? {}); 321 - const updateConfig = () => 322 - MoonbaseSettingsStore.setExtensionConfig( 323 - ext.uniqueId, 324 - name, 325 - Object.fromEntries(entries) 326 - ); 297 + const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Object.fromEntries(entries)); 327 298 328 299 return ( 329 300 <FormItem className={Margins.marginTop20} title={displayName}> 330 - {description && ( 331 - <FormText className={Margins.marginBottom4}>{description}</FormText> 332 - )} 301 + {description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>} 333 302 <Flex direction={Flex.Direction.VERTICAL}> 334 303 {entries.map(([key, val], i) => ( 335 304 // FIXME: stylesheets ··· 389 358 ); 390 359 } 391 360 361 + function Custom({ ext, name, setting, disabled }: SettingsProps) { 362 + const { value, displayName } = useConfigEntry<any>(ext.uniqueId, name); 363 + 364 + const { component: Component } = useStateFromStores( 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 + 392 387 function Setting({ ext, name, setting, disabled }: SettingsProps) { 393 388 const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = { 394 389 [ExtensionSettingType.Boolean]: Boolean, ··· 398 393 [ExtensionSettingType.Select]: Select, 399 394 [ExtensionSettingType.MultiSelect]: MultiSelect, 400 395 [ExtensionSettingType.List]: List, 401 - [ExtensionSettingType.Dictionary]: Dictionary 396 + [ExtensionSettingType.Dictionary]: Dictionary, 397 + [ExtensionSettingType.Custom]: Custom 402 398 }; 403 399 const element = elements[setting.type]; 404 400 if (element == null) return <></>; ··· 406 402 } 407 403 408 404 export default function Settings({ ext }: { ext: MoonbaseExtension }) { 409 - const { Flex } = CommonComponents; 410 405 return ( 411 406 <Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}> 412 - {Object.entries(ext.manifest.settings!).map(([name, setting]) => ( 407 + {Object.entries(ext.settingsOverride ?? ext.manifest.settings!).map(([name, setting]) => ( 413 408 <Setting 414 409 ext={ext} 415 410 key={name}
+28 -29
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
··· 1 - import React from "@moonlight-mod/wp/common_react"; 2 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 3 - import { Text, TabBar } from "@moonlight-mod/wp/common_components"; 4 - import * as Flux from "@moonlight-mod/wp/common_flux"; 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"; 5 4 import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores"; 6 5 7 6 import ExtensionsPage from "./extensions"; 8 7 import ConfigPage from "./config"; 9 - 10 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 11 - 12 - const { Divider } = spacepack.findByCode(".forumOrHome]:")[0].exports.Z; 13 - const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0] 14 - .exports; 15 - const TabBarClasses = spacepack.findByCode("nowPlayingColumn:")[0].exports; 16 - const { setSection, clearSubsection } = spacepack.findByExports( 17 - "setSection", 18 - "clearSubsection" 19 - )[0].exports.Z; 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"; 20 16 21 17 export const pages: { 22 18 id: string; ··· 32 28 id: "config", 33 29 name: "Config", 34 30 element: ConfigPage 31 + }, 32 + { 33 + id: "about", 34 + name: "About", 35 + element: AboutPage 35 36 } 36 37 ]; 37 38 38 39 export function Moonbase(props: { initialTab?: number } = {}) { 39 - const subsection = Flux.useStateFromStores( 40 - [UserSettingsModalStore], 41 - () => UserSettingsModalStore.getSubsection() ?? 0 42 - ); 40 + const subsection = useStateFromStores([UserSettingsModalStore], () => UserSettingsModalStore.getSubsection() ?? 0); 43 41 const setSubsection = React.useCallback( 44 42 (to: string) => { 45 - if (subsection !== to) setSection("moonbase", to); 43 + if (subsection !== to) UserSettingsModalActionCreators.setSection("moonbase", to); 46 44 }, 47 45 [subsection] 48 46 ); ··· 50 48 React.useEffect( 51 49 () => () => { 52 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 53 - clearSubsection("moonbase"); 51 + UserSettingsModalActionCreators.clearSubsection("moonbase"); 54 52 }, 55 53 [] 56 54 ); 57 55 58 56 return ( 59 57 <> 60 - <div className={`${TitleBarClasses.children} ${Margins.marginBottom20}`}> 61 - <Text 62 - className={TitleBarClasses.titleWrapper} 63 - variant="heading-lg/semibold" 64 - tag="h2" 65 - > 58 + <div className={`${HeaderBarClasses.children} ${Margins.marginBottom20}`}> 59 + <Text className={HeaderBarClasses.titleWrapper} variant="heading-lg/semibold" tag="h2"> 66 60 Moonbase 67 61 </Text> 68 62 <Divider /> ··· 70 64 selectedItem={subsection} 71 65 onItemSelect={setSubsection} 72 66 type="top-pill" 73 - className={TabBarClasses.tabBar} 67 + className={PeoplePageClasses.tabBar} 74 68 > 75 69 {pages.map((page, i) => ( 76 - <TabBar.Item key={page.id} id={i} className={TabBarClasses.item}> 70 + <TabBar.Item key={page.id} id={i} className={PeoplePageClasses.item}> 77 71 {page.name} 78 72 </TabBar.Item> 79 73 ))} 80 74 </TabBar> 81 75 </div> 82 76 77 + <RestartAdviceMessage /> 78 + <Update /> 79 + 83 80 {React.createElement(pages[subsection].element)} 84 81 </> 85 82 ); 86 83 } 84 + 85 + export { RestartAdviceMessage, Update };
+124
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 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 + }
+74
packages/core-extensions/src/moonbase/webpackModules/updates.tsx
··· 1 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 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" : ""}`; 10 + } 11 + 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; 24 + 25 + if (version != null) { 26 + message = 27 + moonlightNode.branch === MoonlightBranch.NIGHTLY 28 + ? `A new version of moonlight is available` 29 + : `moonlight ${version} is available`; 30 + } 31 + 32 + if (hasExtensionUpdates) { 33 + let concat = false; 34 + if (message == null) { 35 + message = ""; 36 + } else { 37 + concat = true; 38 + message += ", and "; 39 + } 40 + message += `${extensionUpdateCount} ${concat ? "" : "moonlight "}${plural( 41 + "extension", 42 + extensionUpdateCount 43 + )} can be updated`; 44 + } 45 + 46 + if (message != null) message += "."; 47 + 48 + Notices.addNotice({ 49 + element: ( 50 + <div className="moonbase-updates-notice_text-wrapper"> 51 + <ThemeDarkIcon size="sm" color="currentColor" /> 52 + {message} 53 + </div> 54 + ), 55 + color: "moonbase-updates-notice", 56 + buttons: [ 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 + } 69 + ] 70 + }); 71 + } 72 + } 73 + 74 + MoonbaseSettingsStore.addChangeListener(listener);
+5
packages/core-extensions/src/moonbase/wp.d.ts
··· 5 5 declare module "@moonlight-mod/wp/moonbase_stores" { 6 6 export * from "core-extensions/src/moonbase/webpackModules/stores"; 7 7 } 8 + 9 + declare module "@moonlight-mod/wp/moonbase_ThemeDarkIcon" { 10 + import ThemeDarkIcon from "core-extensions/src/moonbase/webpackModules/ThemeDarkIcon"; 11 + export = ThemeDarkIcon; 12 + }
+186
packages/core-extensions/src/nativeFixes/host.ts
··· 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"; 15 + setTimeout(() => { 16 + nativeTheme.themeSource = "dark"; 17 + }, 100); 18 + }); 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"); 26 + app.commandLine.appendSwitch("disable-backgrounding-occluded-windows"); 27 + 28 + // already added on Windows, but not on other operating systems 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 + }
+77
packages/core-extensions/src/nativeFixes/manifest.json
··· 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 + } 75 + }, 76 + "apiLevel": 2 77 + }
+4 -4
packages/core-extensions/src/noHideToken/index.ts
··· 1 - import { Patch } from "types/src"; 1 + import { Patch } from "@moonlight-mod/types"; 2 2 3 3 export const patches: Patch[] = [ 4 4 { 5 - find: "hideToken:function", 5 + find: "hideToken:()=>", 6 6 replace: { 7 - match: /(?<=hideToken:function\(\){)/, 8 - replacement: `return()=>{};` 7 + match: /hideToken:\(\)=>.+?,/, 8 + replacement: `hideToken:()=>{},` 9 9 } 10 10 } 11 11 ];
+4 -1
packages/core-extensions/src/noHideToken/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "noHideToken", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "No Hide Token", 5 - "tagline": "Disables removal of token from localStorage when opening dev tools", 7 + "tagline": "Prevents you from being logged-out on hard-crash", 8 + "description": "Prevents you from being logged-out on hard-crash by disabling removal of token from localStorage when opening dev tools", 6 9 "authors": ["adryd"], 7 10 "tags": ["dangerZone", "development"] 8 11 }
-15
packages/core-extensions/src/noTrack/host.ts
··· 1 - import { BrowserWindow } from "electron"; 2 - 3 - moonlightHost.events.on("window-created", (window: BrowserWindow) => { 4 - window.webContents.session.webRequest.onBeforeRequest( 5 - { 6 - urls: [ 7 - "https://*.discord.com/api/v*/science", 8 - "https://*.discord.com/api/v*/metrics" 9 - ] 10 - }, 11 - function (details, callback) { 12 - callback({ cancel: true }); 13 - } 14 - ); 15 - });
+3 -3
packages/core-extensions/src/noTrack/index.ts
··· 2 2 3 3 export const patches: Patch[] = [ 4 4 { 5 - find: "analyticsTrackingStoreMaker:function", 5 + find: "analyticsTrackingStoreMaker:()=>", 6 6 replace: { 7 - match: /analyticsTrackingStoreMaker:function\(\){return .+?}/, 8 - replacement: "analyticsTrackingStoreMaker:function(){return ()=>{}}" 7 + match: /analyticsTrackingStoreMaker:\(\)=>.+?,/, 8 + replacement: "analyticsTrackingStoreMaker:()=>()=>{}," 9 9 } 10 10 }, 11 11 {
+9 -1
packages/core-extensions/src/noTrack/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "noTrack", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "No Track", 5 7 "tagline": "Disables /api/science and analytics", 6 8 "authors": ["Cynosphere", "NotNite"], 7 9 "tags": ["privacy"] 8 - } 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 + ] 9 17 }
+42
packages/core-extensions/src/notices/index.ts
··· 1 + import type { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; 2 + 3 + export const patches: Patch[] = [ 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 + } 11 + }, 12 + { 13 + find: '"NoticeStore"', 14 + replace: [ 15 + { 16 + match: /\[.{1,2}\..{1,3}\.CONNECT_SPOTIFY\]:{/, 17 + replacement: (orig: string) => 18 + `__moonlight_notice:{predicate:()=>require("notices_notices").default.shouldShowNotice()},${orig}` 19 + }, 20 + { 21 + match: /=\[(.{1,2}\..{1,3}\.QUARANTINED,)/g, 22 + replacement: (_, orig) => `=["__moonlight_notice",${orig}` 23 + } 24 + ] 25 + } 26 + ]; 27 + 28 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 29 + notices: { 30 + dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }] 31 + }, 32 + 33 + component: { 34 + dependencies: [ 35 + { id: "react" }, 36 + { id: "discord/Dispatcher" }, 37 + { id: "discord/components/common/index" }, 38 + { id: "discord/packages/flux" }, 39 + { ext: "notices", id: "notices" } 40 + ] 41 + } 42 + };
+11
packages/core-extensions/src/notices/manifest.json
··· 1 + { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 + "id": "notices", 4 + "apiLevel": 2, 5 + "meta": { 6 + "name": "Notices", 7 + "tagline": "An API for adding notices at the top of the page", 8 + "authors": ["Cynosphere", "NotNite"], 9 + "tags": ["library"] 10 + } 11 + }
+50
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 { 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(); 12 + } 13 + if (!NoticesStore.shouldShowNotice()) { 14 + Dispatcher.dispatch({ 15 + type: "NOTICE_DISMISS" 16 + }); 17 + } 18 + } 19 + 20 + export default function UpdateNotice() { 21 + const { notice } = useStateFromStoresObject([NoticesStore], () => ({ 22 + notice: NoticesStore.getCurrentNotice() 23 + })); 24 + 25 + if (notice == null) return <></>; 26 + 27 + return ( 28 + <Notice color={notice.color}> 29 + {notice.element} 30 + 31 + {(notice.showClose ?? true) && ( 32 + <NoticeCloseButton onClick={() => popAndDismiss(notice)} noticeType="__moonlight_notice" /> 33 + )} 34 + 35 + {(notice.buttons ?? []).map((button) => ( 36 + <PrimaryCTANoticeButton 37 + key={button.name} 38 + onClick={() => { 39 + if (button.onClick()) { 40 + popAndDismiss(notice); 41 + } 42 + }} 43 + noticeType="__moonlight_notice" 44 + > 45 + {button.name} 46 + </PrimaryCTANoticeButton> 47 + ))} 48 + </Notice> 49 + ); 50 + }
+55
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 { Notice, Notices } from "@moonlight-mod/types/coreExtensions/notices"; 4 + 5 + // very lazy way of doing this, FIXME 6 + let open = false; 7 + 8 + class NoticesStore extends Store<any> { 9 + private notices: Notice[] = []; 10 + 11 + constructor() { 12 + super(Dispatcher); 13 + } 14 + 15 + addNotice(notice: Notice) { 16 + this.notices.push(notice); 17 + if (open && this.notices.length !== 0) { 18 + Dispatcher.dispatch({ 19 + type: "NOTICE_SHOW", 20 + notice: { type: "__moonlight_notice" } 21 + }); 22 + } 23 + this.emitChange(); 24 + } 25 + 26 + popNotice() { 27 + this.notices.shift(); 28 + this.emitChange(); 29 + } 30 + 31 + getCurrentNotice() { 32 + return this.notices.length > 0 ? this.notices[0] : null; 33 + } 34 + 35 + shouldShowNotice() { 36 + return this.notices.length > 0; 37 + } 38 + } 39 + 40 + const store: Notices = new NoticesStore(); 41 + 42 + function showNotice() { 43 + open = true; 44 + if (store.shouldShowNotice()) { 45 + Dispatcher.dispatch({ 46 + type: "NOTICE_SHOW", 47 + notice: { type: "__moonlight_notice" } 48 + }); 49 + } 50 + } 51 + 52 + Dispatcher.subscribe("CONNECTION_OPEN", showNotice); 53 + Dispatcher.subscribe("CONNECTION_OPEN_SUPPLEMENTAL", showNotice); 54 + 55 + export default store;
+98 -40
packages/core-extensions/src/quietLoggers/index.ts
··· 1 1 import { Patch } from "@moonlight-mod/types"; 2 2 3 3 const notXssDefensesOnly = () => 4 - (moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ?? 5 - false) === false; 4 + (moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ?? false) === false; 5 + 6 + const silenceDiscordLogger = moonlight.getConfigOption<boolean>("quietLoggers", "silenceDiscordLogger") ?? false; 6 7 7 8 // These patches MUST run before the simple patches, these are to remove loggers 8 9 // that end up causing syntax errors by the normal patch 9 10 const loggerFixes: Patch[] = [ 10 11 { 11 - find: '"./ggsans-800-extrabolditalic.woff2":', 12 + find: '"./gg-sans/ggsans-800-extrabolditalic.woff2":', 12 13 replace: { 13 - match: /throw .+?,./, 14 - replacement: "return{}" 14 + match: /var .=Error.+?;throw .+?,./, 15 + replacement: "" 15 16 } 16 17 }, 17 18 { 18 19 find: '("GatewaySocket")', 19 20 replace: { 20 - match: /.\.(info|log)(\(.+?\))(;|,)/g, 21 - replacement: (_, type, body, trail) => `(()=>{})${body}${trail}` 21 + match: /\i\.(log|info)\(/g, 22 + replacement: "(()=>{})(" 23 + } 24 + }, 25 + { 26 + find: '"_connect called with already existing websocket"', 27 + replace: { 28 + match: /\i\.(log|info|verbose)\(/g, 29 + replacement: "(()=>{})(" 22 30 } 23 31 } 24 32 ]; ··· 29 37 // Patches to simply remove a logger call 30 38 const stubPatches = [ 31 39 // "sh" is not a valid locale. 40 + ["is not a valid locale", /void \i\.error\(""\.concat\(\i," is not a valid locale\."\)\)/g], 41 + ['"[BUILD INFO] Release Channel: "', /new \i\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?\)\),/], 42 + ['.APP_NATIVE_CRASH,"Storage"', /console\.log\("AppCrashedFatalReport lastCrash:",\i,\i\);/], 43 + ['.APP_NATIVE_CRASH,"Storage"', 'void console.log("AppCrashedFatalReport: getLastCrash not supported.")'], 44 + ['"[NATIVE INFO] ', /new \i\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/], 45 + ['"Spellchecker"', /\i\.info\("Switching to ".+?"\(unavailable\)"\);?/g], 46 + ['throw Error("Messages are still loading.");', /console\.warn\("Unsupported Locale",\i\),/], 47 + ["}_dispatchWithDevtools(", /\i\.totalTime>\i&&\i\.verbose\(.+?\);/], 48 + ['"NativeDispatchUtils"', /null==\i&&\i\.warn\("Tried getting Dispatch instance before instantiated"\),/], 32 49 [ 33 - "is not a valid locale", 34 - /(.)\.error\(""\.concat\((.)," is not a valid locale\."\)\)/g 50 + '"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "', 51 + /\i\.has\(\i\.type\)&&\i\.log\(.+?\.type\)\),/ 35 52 ], 36 - ['="RunningGameStore"', /.\.info\("games",{.+?}\),/], 53 + ['console.warn("Window state not initialized"', /console\.warn\("Window state not initialized",\i\),/], 54 + ['.name="MaxListenersExceededWarning",', /(?<=\.length),\i\(\i\)/], 37 55 [ 38 - '"[BUILD INFO] Release Channel: "', 39 - /new .{1,2}\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?"\)\),/ 56 + '"The answer for life the universe and everything is:"', 57 + /\i\.info\("The answer for life the universe and everything is:",\i\),/ 40 58 ], 41 59 [ 42 - '.APP_NATIVE_CRASH,"Storage"', 43 - /console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/ 60 + '"isLibdiscoreBlockedDomainsEnabled called but libdiscore is not loaded"', 61 + /,\i\.verbose\("isLibdiscoreBlockedDomainsEnabledThisSession: ".concat\(\i\)\)/ 44 62 ], 45 63 [ 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",.\),/ 64 + '"Unable to determine render window for element"', 65 + /console\.warn\("Unable to determine render window for element",\i\),/ 54 66 ], 55 - ["}_dispatchWithDevtools(", /.\.totalTime>100&&.\.verbose\(.+?\);/], 56 67 [ 57 - '"NativeDispatchUtils"', 58 - /null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/ 68 + '"Unable to determine render window for element"', 69 + /console\.warn\('Unable to find element constructor "'\.concat\(\i,'" in'\),\i\),/ 59 70 ], 60 - ['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/], 61 71 [ 62 - '"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "', 63 - /.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/ 72 + '"[PostMessageTransport] Protocol error: event data should be an Array!"', 73 + /void console\.warn\("\[PostMessageTransport] Protocol error: event data should be an Array!"\)/ 64 74 ], 65 75 [ 66 - 'console.warn("Window state not initialized"', 67 - /console\.warn\("Window state not initialized",.\),/ 76 + '("ComponentDispatchUtils")', 77 + /new \i\.Z\("ComponentDispatchUtils"\)\.warn\("ComponentDispatch\.resubscribe: Resubscribe without existing subscription",\i\),/ 68 78 ] 69 79 ]; 70 80 81 + const stripLoggers = [ 82 + '("OverlayRenderStore")', 83 + '("FetchBlockedDomain")', 84 + '="UserSettingsProtoLastWriteTimes",', 85 + '("MessageActionCreators")', 86 + '("Routing/Utils")', 87 + '("DatabaseManager")', 88 + '("KeyboardLayoutMapUtils")', 89 + '("ChannelMessages")', 90 + '("MessageQueue")', 91 + '("RTCLatencyTestManager")', 92 + '("OverlayStoreV3")', 93 + '("OverlayBridgeStore")', 94 + '("AuthenticationStore")', 95 + '("ConnectionStore")', 96 + '"Dispatched INITIAL_GUILD "', 97 + '"handleIdentify called"', 98 + '("Spotify")' 99 + ]; 100 + 71 101 const simplePatches = [ 72 102 // 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"] 103 + ["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"] 81 104 ] as { [0]: string | RegExp; [1]: string }[]; 82 105 83 106 export const patches: Patch[] = [ 84 107 { 85 - find: ".Messages.XSSDefenses", 108 + find: ".Messages.SELF_XSS_HEADER", 86 109 replace: { 87 - match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/, 110 + match: /\(null!=\i&&"0\.0\.0"===\i\.remoteApp\.getVersion\(\)\)/, 88 111 replacement: "(true)" 89 112 } 90 113 }, 114 + { 115 + find: '("ComponentDispatchUtils")', 116 + replace: { 117 + match: 118 + /new \i\.Z\("ComponentDispatchUtils"\)\.warn\("ComponentDispatch\.subscribe: Attempting to add a duplicate listener",\i\)/, 119 + replacement: "void 0" 120 + }, 121 + prerequisite: notXssDefensesOnly 122 + }, 123 + // Highlight.js deprecation warnings 124 + { 125 + find: "Deprecated as of", 126 + replace: { 127 + match: /console\./g, 128 + replacement: "false&&console." 129 + }, 130 + prerequisite: notXssDefensesOnly 131 + }, 132 + // Discord's logger 133 + { 134 + find: "ฮฃ:", 135 + replace: { 136 + match: "for", 137 + replacement: "return;for" 138 + }, 139 + prerequisite: () => silenceDiscordLogger && notXssDefensesOnly() 140 + }, 91 141 ...loggerFixes, 92 142 ...stubPatches.map((patch) => ({ 93 143 find: patch[0], ··· 102 152 replace: { 103 153 match: patch[0], 104 154 replacement: patch[1] 155 + }, 156 + prerequisite: notXssDefensesOnly 157 + })), 158 + ...stripLoggers.map((find) => ({ 159 + find, 160 + replace: { 161 + match: /(\i|this\.logger)\.(log|warn|error|info|verbose)\(/g, 162 + replacement: "(()=>{})(" 105 163 }, 106 164 prerequisite: notXssDefensesOnly 107 165 }))
+10
packages/core-extensions/src/quietLoggers/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "quietLoggers", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "Quiet Loggers", 5 7 "tagline": "Quiet errors on startup, and disable unnecesary loggers", ··· 8 10 }, 9 11 "settings": { 10 12 "xssDefensesOnly": { 13 + "advice": "reload", 11 14 "displayName": "Only hide self-XSS", 12 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)", 13 23 "type": "boolean", 14 24 "default": false 15 25 }
+75
packages/core-extensions/src/rocketship/host/permissions.ts
··· 1 + import type { BrowserWindow } from "electron"; 2 + 3 + type PermissionRequestHandler = ( 4 + webContents: Electron.WebContents, 5 + permission: string, 6 + callback: (permissionGranted: boolean) => void, 7 + details: Electron.PermissionRequestHandlerHandlerDetails 8 + ) => void; 9 + 10 + type PermissionCheckHandler = ( 11 + webContents: Electron.WebContents | null, 12 + permission: string, 13 + requestingOrigin: string, 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 + });
+27
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> = 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[]; 12 + exclude: Node[]; 13 + 14 + ignore_devices?: boolean; 15 + 16 + only_speakers?: boolean; 17 + only_default_speakers?: boolean; 18 + 19 + workaround?: Node[]; 20 + } 21 + 22 + export interface PatchBay { 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 + }
+69
packages/core-extensions/src/rocketship/host/venmic.ts
··· 1 + import type { BrowserWindow } from "electron"; 2 + import { app, desktopCapturer } from "electron"; 3 + import path from "node:path"; 4 + import { type PatchBay } from "./types"; 5 + 6 + const logger = moonlightHost.getLogger("rocketship"); 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) { 16 + logger.error("Failed to load venmic.node:", error); 17 + return null; 18 + } 19 + } 20 + 21 + const patchbay = getPatchbay(); 22 + 23 + // TODO: figure out how to map source to window with venmic 24 + function linkVenmic() { 25 + if (patchbay == null) return false; 26 + 27 + try { 28 + const pid = 29 + app 30 + .getAppMetrics() 31 + .find((proc) => proc.name === "Audio Service") 32 + ?.pid?.toString() ?? ""; 33 + 34 + logger.info("Audio Service PID:", pid); 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 42 + }); 43 + } catch (error) { 44 + logger.error("Failed to link venmic:", error); 45 + return false; 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 + });
+2
packages/core-extensions/src/rocketship/host.ts
··· 1 + import "./host/permissions"; 2 + import "./host/venmic";
+124
packages/core-extensions/src/rocketship/index.ts
··· 1 + import { Patch } from "@moonlight-mod/types"; 2 + 3 + const logger = moonlight.getLogger("rocketship"); 4 + const getDisplayMediaOrig = navigator.mediaDevices.getDisplayMedia; 5 + 6 + async function getVenmicStream() { 7 + try { 8 + const devices = await navigator.mediaDevices.enumerateDevices(); 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 + 16 + const stream = await navigator.mediaDevices.getUserMedia({ 17 + audio: { 18 + deviceId: { 19 + exact: id 20 + }, 21 + autoGainControl: false, 22 + echoCancellation: false, 23 + noiseSuppression: false 24 + } 25 + }); 26 + 27 + return stream.getAudioTracks(); 28 + } catch (error) { 29 + logger.warn("Failed to get venmic stream:", error); 30 + return null; 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(); 38 + logger.debug("venmic", venmic); 39 + if (venmic != null) { 40 + // venmic will be proxying all audio, so we need to remove the original 41 + // tracks to not cause overlap 42 + for (const track of orig.getAudioTracks()) { 43 + orig.removeTrack(track); 44 + } 45 + 46 + for (const track of venmic) { 47 + orig.addTrack(track); 48 + } 49 + } 50 + 51 + return orig; 52 + }; 53 + 54 + export const patches: Patch[] = [ 55 + // "Ensure discord_voice is happy" 56 + { 57 + find: "RustAudioDeviceModule", 58 + replace: [ 59 + { 60 + match: /static supported\(\)\{.+?\}/, 61 + replacement: "static supported(){return true}" 62 + }, 63 + { 64 + match: "supported(){return!0}", 65 + replacement: "supported(){return true}" 66 + } 67 + ] 68 + }, 69 + // Remove Native media engine from list of choices 70 + { 71 + find: '.CAMERA_BACKGROUND_LIVE="cameraBackgroundLive"', 72 + replace: { 73 + match: /.\..{1,2}\.NATIVE,/, 74 + replacement: "" 75 + } 76 + }, 77 + // Stub out browser checks to allow us to use WebRTC voice on Embedded 78 + { 79 + find: "Using Unified Plan (", 80 + replace: { 81 + match: /return .\..{1,2}\?\((.)\.info/, 82 + replacement: (_, logger) => `return true?(${logger}.info` 83 + } 84 + }, 85 + { 86 + find: '"UnifiedConnection("', 87 + replace: { 88 + match: /this\.videoSupported=.\..{1,2};/, 89 + replacement: "this.videoSupported=true;" 90 + } 91 + }, 92 + { 93 + find: "OculusBrowser", 94 + replace: [ 95 + { 96 + match: /"Firefox"===(.)\(\)\.name/g, 97 + replacement: (orig, info) => `true||${orig}` 98 + } 99 + ] 100 + }, 101 + { 102 + find: ".getMediaEngine().getDesktopSource", 103 + replace: { 104 + match: /.\.isPlatformEmbedded/, 105 + replacement: "false" 106 + } 107 + }, 108 + { 109 + // Matching MediaEngineStore 110 + find: '"displayName","MediaEngineStore")', 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 + } 124 + ];
+13
packages/core-extensions/src/rocketship/manifest.json
··· 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 + }
+4 -6
packages/core-extensions/src/settings/index.ts
··· 5 5 { 6 6 find: '"useGenerateUserSettingsSections"', 7 7 replace: { 8 - match: /(?<=\.push\(.+?\)}\)\)}\),)./, 9 - replacement: (sections: string) => 10 - `require("settings_settings").Settings._mutateSections(${sections})` 8 + match: /(?<=\.push\(.+?\)}\)\)}\),)(.+?)}/, 9 + replacement: (_, sections: string) => `require("settings_settings").Settings._mutateSections(${sections})}` 11 10 } 12 11 }, 13 12 { 14 13 find: 'navId:"user-settings-cog",', 15 14 replace: { 16 - match: /children:\[(.)\.map\(.+?\),children:.\((.)\)/, 15 + match: /children:\[(\i)\.map\(.+?\),.*?children:\i\((\i)\)/, 17 16 replacement: (orig, sections, section) => 18 17 `${orig.replace( 19 18 /Object\.values\(.\..+?\)/, 20 - (orig) => 21 - `[...require("settings_settings").Settings.sectionNames,...${orig}]` 19 + (orig) => `[...require("settings_settings").Settings.sectionNames,...${orig}]` 22 20 )}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()` 23 21 } 24 22 }
+2
packages/core-extensions/src/settings/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "settings", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "Settings", 5 7 "tagline": "An API for adding to Discord's settings menu",
+11 -13
packages/core-extensions/src/settings/webpackModules/settings.ts
··· 1 - import { 2 - SettingsSection, 3 - Settings as SettingsType 4 - } from "@moonlight-mod/types/coreExtensions"; 1 + import { SettingsSection, Settings as SettingsType } from "@moonlight-mod/types/coreExtensions/settings"; 2 + import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators"; 5 3 6 4 export const Settings: SettingsType = { 7 5 ourSections: [], 8 6 sectionNames: [], 9 7 sectionMenuItems: {}, 10 8 11 - addSection: (section, label, element, color = null, pos, notice) => { 9 + addSection: (section, label, element, color = null, pos, notice, onClick) => { 12 10 const data: SettingsSection = { 13 11 section, 14 12 label, 15 13 color, 16 14 element, 17 15 pos: pos ?? -4, 18 - notice: notice 16 + notice: notice, 17 + onClick: onClick ?? (() => UserSettingsModalActionCreators.open(section)) 19 18 }; 20 19 21 20 Settings.ourSections.push(data); ··· 24 23 }, 25 24 addSectionMenuItems(section, ...newItems) { 26 25 const data = Settings.ourSections.find((x) => x.section === section); 27 - if (!data || !("element" in data)) 28 - throw new Error(`Could not find section "${section}"`); 26 + if (!data || !("element" in data)) throw new Error(`Could not find section "${section}"`); 29 27 (Settings.sectionMenuItems[section] ??= []).push(...newItems); 30 28 data._moonlight_submenu ??= () => Settings.sectionMenuItems[section]; 31 29 }, ··· 47 45 48 46 _mutateSections: (sections) => { 49 47 for (const section of Settings.ourSections) { 50 - sections.splice( 51 - section.pos < 0 ? sections.length + section.pos : section.pos, 52 - 0, 53 - section 54 - ); 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); 55 53 } 56 54 57 55 return sections;
+1 -1
packages/core-extensions/src/spacepack/index.ts
··· 1 1 import { ExtensionWebExports } from "@moonlight-mod/types"; 2 - import { Spacepack } from "@moonlight-mod/types/coreExtensions"; 2 + import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack"; 3 3 4 4 declare global { 5 5 interface Window {
+3
packages/core-extensions/src/spacepack/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "spacepack", 4 + "apiLevel": 2, 3 5 "meta": { 4 6 "name": "Spacepack", 5 7 "tagline": "Search utilities across all Webpack modules", ··· 8 10 }, 9 11 "settings": { 10 12 "addToGlobalScope": { 13 + "advice": "reload", 11 14 "displayName": "Add to global scope", 12 15 "description": "Populates window.spacepack for easier usage in DevTools", 13 16 "type": "boolean",
+89 -68
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"; 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"; 7 4 8 5 const webpackRequire = require as unknown as WebpackRequireType; 9 6 const cache = webpackRequire.c; ··· 21 18 module = module.toString(); 22 19 } 23 20 21 + if (module in moonlight.moonmap.modules) { 22 + module = moonlight.moonmap.modules[module]; 23 + } 24 + 24 25 if (!(module in modules)) { 25 26 return null; 26 27 } ··· 36 37 "module", 37 38 "exports", 38 39 "require", 39 - `(${funcStr}).apply(this, arguments)\n` + 40 - `//# sourceURL=Webpack-Module-${module}` 40 + `(${funcStr}).apply(this, arguments)\n` + `//# sourceURL=Webpack-Module/${module.slice(0, 3)}/${module}` 41 41 ) as WebpackModuleFunc; 42 42 }, 43 43 44 44 findByCode: (...args: (string | RegExp)[]) => { 45 - return Object.entries(modules) 46 - .filter( 47 - ([id, mod]) => 48 - !args.some( 49 - (item) => 50 - !(item instanceof RegExp 51 - ? item.test(mod.toString()) 52 - : mod.toString().indexOf(item) !== -1) 53 - ) 54 - ) 45 + const ret = Object.entries(modules) 46 + .filter(([id, mod]) => !args.some((item) => !testFind(mod.toString(), processFind(item)))) 55 47 .map(([id]) => { 56 48 //if (!(id in cache)) require(id); 57 49 //return cache[id]; ··· 60 52 try { 61 53 exports = require(id); 62 54 } catch (e) { 63 - logger.error(`Error requiring module "${id}": `, e); 55 + logger.error(`findByCode: Error requiring module "${id}": `, args, e); 64 56 } 65 57 66 58 return { ··· 69 61 }; 70 62 }) 71 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; 72 70 }, 73 71 74 72 findByExports: (...args: string[]) => { ··· 80 78 !( 81 79 exports !== undefined && 82 80 exports !== window && 83 - (exports?.[item] || 84 - exports?.default?.[item] || 85 - exports?.Z?.[item] || 86 - exports?.ZP?.[item]) 81 + (exports?.[item] || exports?.default?.[item] || exports?.Z?.[item] || exports?.ZP?.[item]) 87 82 ) 88 83 ) 89 84 ) ··· 95 90 }, 96 91 97 92 findObjectFromKey: (exports: Record<string, any>, key: string) => { 93 + let ret = null; 98 94 let subKey; 99 95 if (key.indexOf(".") > -1) { 100 96 const splitKey = key.split("."); ··· 105 101 const obj = exports[exportKey]; 106 102 if (obj && obj[key] !== undefined) { 107 103 if (subKey) { 108 - if (obj[key][subKey]) return obj; 104 + if (obj[key][subKey]) { 105 + ret = obj; 106 + break; 107 + } 109 108 } else { 110 - return obj; 109 + ret = obj; 110 + break; 111 111 } 112 112 } 113 113 } 114 - return null; 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; 115 120 }, 116 121 117 122 findObjectFromValue: (exports: Record<string, any>, value: any) => { 123 + let ret = null; 118 124 for (const exportKey in exports) { 119 125 const obj = exports[exportKey]; 120 126 // eslint-disable-next-line eqeqeq 121 - if (obj == value) return obj; 127 + if (obj == value) { 128 + ret = obj; 129 + break; 130 + } 122 131 for (const subKey in obj) { 123 132 // eslint-disable-next-line eqeqeq 124 133 if (obj && obj[subKey] == value) { 125 - return obj; 134 + ret = obj; 135 + break; 126 136 } 127 137 } 128 138 } 129 - return null; 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; 130 145 }, 131 146 132 - findObjectFromKeyValuePair: ( 133 - exports: Record<string, any>, 134 - key: string, 135 - value: any 136 - ) => { 147 + findObjectFromKeyValuePair: (exports: Record<string, any>, key: string, value: any) => { 148 + let ret = null; 137 149 for (const exportKey in exports) { 138 150 const obj = exports[exportKey]; 139 151 // eslint-disable-next-line eqeqeq 140 152 if (obj && obj[key] == value) { 141 - return obj; 153 + ret = obj; 154 + break; 142 155 } 143 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 + 144 169 return null; 145 170 }, 146 171 147 - findFunctionByStrings: ( 148 - exports: Record<string, any>, 149 - ...strings: (string | RegExp)[] 150 - ) => { 151 - return ( 172 + findFunctionByStrings: (exports: Record<string, any>, ...strings: (string | RegExp)[]) => { 173 + const ret = 152 174 Object.entries(exports).filter( 153 175 ([index, func]) => 154 - typeof func === "function" && 155 - !strings.some( 156 - (query) => 157 - !(query instanceof RegExp 158 - ? func.toString().match(query) 159 - : func.toString().includes(query)) 160 - ) 161 - )?.[0]?.[1] ?? null 162 - ); 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; 163 184 }, 164 185 165 - lazyLoad: ( 166 - find: string | RegExp | (string | RegExp)[], 167 - chunk: RegExp, 168 - module: RegExp 169 - ) => { 170 - const mod = Array.isArray(find) 171 - ? spacepack.findByCode(...find) 172 - : spacepack.findByCode(find); 173 - if (mod.length < 1) return Promise.reject("Module find failed"); 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 + } 174 195 175 196 const findId = mod[0].id; 176 197 const findCode = webpackRequire.m[findId].toString().replace(/\n/g, ""); ··· 180 201 chunkIds = [...findCode.matchAll(chunk)].map(([, id]) => id); 181 202 } else { 182 203 const match = findCode.match(chunk); 183 - if (match) 184 - chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id); 204 + if (match) chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id); 185 205 } 186 206 187 - if (!chunkIds || chunkIds.length === 0) 207 + if (!chunkIds || chunkIds.length === 0) { 208 + logger.warn("lazyLoad: Chunk ID match failed", find, chunk, module, new Error().stack!.substring(5)); 188 209 return Promise.reject("Chunk ID match failed"); 210 + } 189 211 190 212 const moduleId = findCode.match(module)?.[1]; 191 - if (!moduleId) return Promise.reject("Module ID match failed"); 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 + } 192 217 193 - return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() => 194 - webpackRequire(moduleId) 195 - ); 218 + return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() => webpackRequire(moduleId)); 196 219 }, 197 220 198 221 filterReal: (modules: WebpackModule[]) => { ··· 200 223 } 201 224 }; 202 225 203 - if ( 204 - moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true 205 - ) { 226 + if (moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true) { 206 227 window.spacepack = spacepack; 207 228 } 208 229
+4 -1
packages/core-extensions/tsconfig.json
··· 1 1 { 2 - "extends": "../../tsconfig.json" 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["ESNext", "DOM", "DOM.Iterable"] 5 + } 3 6 }
+10 -3
packages/injector/package.json
··· 1 1 { 2 2 "name": "@moonlight-mod/injector", 3 3 "private": true, 4 + "engines": { 5 + "node": ">=22", 6 + "pnpm": ">=10", 7 + "npm": "pnpm", 8 + "yarn": "pnpm" 9 + }, 4 10 "dependencies": { 5 - "@moonlight-mod/types": "workspace:*", 6 - "@moonlight-mod/core": "workspace:*" 7 - } 11 + "@moonlight-mod/core": "workspace:*", 12 + "@moonlight-mod/types": "workspace:*" 13 + }, 14 + "engineStrict": true 8 15 }
+173 -113
packages/injector/src/index.ts
··· 5 5 app 6 6 } from "electron"; 7 7 import Module from "node:module"; 8 - import { constants } from "@moonlight-mod/types"; 9 - import { readConfig } from "@moonlight-mod/core/config"; 8 + import { constants, MoonlightBranch } from "@moonlight-mod/types"; 9 + import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 10 10 import { getExtensions } from "@moonlight-mod/core/extension"; 11 - import Logger from "@moonlight-mod/core/util/logger"; 12 - import { 13 - loadExtensions, 14 - loadProcessedExtensions 15 - } from "@moonlight-mod/core/extension/loader"; 11 + import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; 12 + import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; 16 13 import EventEmitter from "node:events"; 17 - import { join, resolve } from "node:path"; 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"; 18 19 19 20 const logger = new Logger("injector"); 20 21 21 22 let oldPreloadPath: string | undefined; 22 23 let corsAllow: string[] = []; 23 - let isMoonlightDesktop = false; 24 - let hasOpenAsar = false; 25 - let openAsarConfigPreload: string | undefined; 24 + let blockedUrls: RegExp[] = []; 25 + let injectorConfig: InjectorConfig | undefined; 26 + 27 + const scriptUrls = ["web.", "sentry."]; 28 + const blockedScripts = new Set<string>(); 26 29 27 30 ipcMain.on(constants.ipcGetOldPreloadPath, (e) => { 28 31 e.returnValue = oldPreloadPath; 29 32 }); 33 + 30 34 ipcMain.on(constants.ipcGetAppData, (e) => { 31 35 e.returnValue = app.getPath("appData"); 32 36 }); 33 - ipcMain.on(constants.ipcGetIsMoonlightDesktop, (e) => { 34 - e.returnValue = isMoonlightDesktop; 37 + ipcMain.on(constants.ipcGetInjectorConfig, (e) => { 38 + e.returnValue = injectorConfig; 35 39 }); 36 40 ipcMain.handle(constants.ipcMessageBox, (_, opts) => { 37 41 electron.dialog.showMessageBoxSync(opts); ··· 40 44 corsAllow = list; 41 45 }); 42 46 43 - function patchCsp(headers: Record<string, string[]>) { 44 - const directives = [ 45 - "style-src", 46 - "connect-src", 47 - "img-src", 48 - "font-src", 49 - "media-src", 50 - "worker-src", 51 - "prefetch-src" 52 - ]; 53 - const values = ["*", "blob:", "data:", "'unsafe-inline'", "disclip:"]; 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[]) => { 52 + // We compile the patterns into a RegExp based on a janky match pattern-like syntax 53 + const compiled = list 54 + .map((pattern) => { 55 + const match = pattern.match(reMatchPattern); 56 + if (!match?.groups) return; 57 + 58 + let regex = ""; 59 + if (match.groups.scheme === "*") regex += ".+?"; 60 + else regex += escapeRegExp(match.groups.scheme); 61 + regex += ":\\/\\/"; 62 + 63 + const parts = match.groups.host.split("."); 64 + if (parts[0] === "*") { 65 + parts.shift(); 66 + regex += "(?:.+?\\.)?"; 67 + } 68 + regex += escapeRegExp(parts.join(".")); 69 + 70 + regex += "\\/" + escapeRegExp(match.groups.path).replace("\\*", ".*?"); 71 + 72 + return new RegExp("^" + regex + "$"); 73 + }) 74 + .filter(Boolean) as RegExp[]; 75 + 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:"]; 54 82 55 83 const csp = "content-security-policy"; 56 84 if (headers[csp] == null) return; ··· 67 95 68 96 for (const directive of directives) { 69 97 parts[directive] = values; 98 + } 99 + 100 + for (const [directive, urls] of Object.entries(extensionCspOverrides)) { 101 + parts[directive] ??= []; 102 + parts[directive].push(...urls); 70 103 } 71 104 72 105 const stringified = Object.entries<string[]>(parts) ··· 77 110 headers[csp] = [stringified]; 78 111 } 79 112 80 - function removeOpenAsarEventIfPresent(eventHandler: (...args: any[]) => void) { 81 - const code = eventHandler.toString(); 82 - if (code.indexOf("bw.webContents.on('dom-ready'") > -1) { 83 - electron.app.off("browser-window-created", eventHandler); 84 - } 85 - } 86 - 87 113 class BrowserWindow extends ElectronBrowserWindow { 88 114 constructor(opts: BrowserWindowConstructorOptions) { 89 - oldPreloadPath = opts.webPreferences!.preload; 115 + const isMainWindow = opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1; 90 116 91 - // Only overwrite preload if its the actual main client window 92 - if (opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1) { 117 + if (isMainWindow) { 118 + if (!oldPreloadPath) oldPreloadPath = opts.webPreferences!.preload; 93 119 opts.webPreferences!.preload = require.resolve("./node-preload.js"); 94 120 } 95 121 96 122 // Event for modifying window options 97 - moonlightHost.events.emit("window-options", opts); 123 + moonlightHost.events.emit("window-options", opts, isMainWindow); 98 124 99 125 super(opts); 100 126 101 127 // Event for when a window is created 102 - moonlightHost.events.emit("window-created", this); 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 + } 103 141 104 142 this.webContents.session.webRequest.onHeadersReceived((details, cb) => { 105 143 if (details.responseHeaders != null) { 106 144 // Patch CSP so things can use externally hosted assets 107 145 if (details.resourceType === "mainFrame") { 108 - patchCsp(details.responseHeaders); 146 + patchCsp(details.responseHeaders, extensionCspOverrides); 109 147 } 110 148 111 149 // Allow plugins to bypass CORS for specific URLs 112 150 if (corsAllow.some((x) => details.url.startsWith(x))) { 113 - details.responseHeaders["access-control-allow-origin"] = ["*"]; 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] = ["*"]; 114 158 } 159 + 160 + moonlightHost.events.emit("headers-received", details, isMainWindow); 115 161 116 162 cb({ cancel: false, responseHeaders: details.responseHeaders }); 117 163 } 118 164 }); 119 165 120 - if (hasOpenAsar) { 121 - // Remove DOM injections 122 - // Settings can still be opened via: 123 - // `DiscordNative.ipc.send("DISCORD_UPDATED_QUOTES","o")` 124 - // @ts-expect-error Electron internals 125 - const events = electron.app._events["browser-window-created"]; 126 - if (Array.isArray(events)) { 127 - for (const event of events) { 128 - removeOpenAsarEventIfPresent(event); 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); 129 195 } 130 - } else if (events != null) { 131 - removeOpenAsarEventIfPresent(events); 196 + 197 + if (hasUrl) return cb({ cancel: true }); 132 198 } 133 199 134 - // Config screen fails to context bridge properly 135 - // Less than ideal, but better than disabling it everywhere 136 - if (opts.webPreferences!.preload === openAsarConfigPreload) { 137 - opts.webPreferences!.sandbox = false; 138 - } 139 - } 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 + }); 140 204 } 141 205 } 142 206 ··· 157 221 writable: false 158 222 }); 159 223 160 - export async function inject(asarPath: string) { 161 - isMoonlightDesktop = asarPath === "moonlightDesktop"; 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 + 162 235 try { 163 - const config = readConfig(); 164 - const extensions = getExtensions(); 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(); 165 242 166 243 // Duplicated in node-preload... oops 167 - // eslint-disable-next-line no-inner-declarations 168 244 function getConfig(ext: string) { 169 245 const val = config.extensions[ext]; 170 246 if (val == null || typeof val === "boolean") return undefined; 171 247 return val.config; 172 248 } 173 - 174 249 global.moonlightHost = { 250 + get config() { 251 + return config; 252 + }, 253 + extensions, 254 + processedExtensions, 175 255 asarPath, 176 - config, 177 256 events: new EventEmitter(), 178 - extensions, 179 - processedExtensions: { 180 - extensions: [], 181 - dependencyGraph: new Map() 182 - }, 257 + 258 + version: MOONLIGHT_VERSION, 259 + branch: MOONLIGHT_BRANCH as MoonlightBranch, 183 260 184 261 getConfig, 185 - getConfigOption: <T>(ext: string, name: string) => { 186 - const config = getConfig(ext); 187 - if (config == null) return undefined; 188 - const option = config[name]; 189 - if (option == null) return undefined; 190 - return option as T; 262 + getConfigPath, 263 + getConfigOption(ext, name) { 264 + const manifest = getManifest(extensions, ext); 265 + return getConfigOption(ext, name, config, manifest?.settings); 191 266 }, 192 - getLogger: (id: string) => { 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) { 193 277 return new Logger(id); 278 + }, 279 + getMoonlightDir() { 280 + return moonlightDir; 281 + }, 282 + getExtensionDir: (ext: string) => { 283 + return path.join(extensionsPath, ext); 194 284 } 195 285 }; 196 286 197 - // Check if we're running with OpenAsar 198 - try { 199 - require.resolve(join(asarPath, "updater", "updater.js")); 200 - hasOpenAsar = true; 201 - openAsarConfigPreload = resolve(asarPath, "config", "preload.js"); 202 - // eslint-disable-next-line no-empty 203 - } catch {} 204 - 205 - if (hasOpenAsar) { 206 - // Disable command line switch injection 207 - // I personally think that the command line switches should be vetted by 208 - // the user and not just "trust that these are sane defaults that work 209 - // always". I'm not hating on Ducko or anything, I'm just opinionated. 210 - // Someone can always make a command line modifier plugin, thats the point 211 - // of having host modules. 212 - try { 213 - const cmdSwitchesPath = require.resolve( 214 - join(asarPath, "cmdSwitches.js") 215 - ); 216 - require.cache[cmdSwitchesPath] = new Module( 217 - cmdSwitchesPath, 218 - require.cache[require.resolve(asarPath)] 219 - ); 220 - require.cache[cmdSwitchesPath]!.exports = () => {}; 221 - } catch (error) { 222 - logger.error("Failed to disable OpenAsar's command line flags:", error); 223 - } 224 - } 225 - 226 287 patchElectron(); 227 288 228 - global.moonlightHost.processedExtensions = await loadExtensions(extensions); 229 289 await loadProcessedExtensions(global.moonlightHost.processedExtensions); 230 290 } catch (error) { 231 291 logger.error("Failed to inject:", error); 232 292 } 233 293 234 - if (isMoonlightDesktop) return; 294 + if (injectorConfig?.disablePersist !== true) { 295 + persist(asarPath); 296 + } 235 297 236 - // Need to do this instead of require() or it breaks require.main 237 - // @ts-expect-error Module internals 238 - Module._load(asarPath, Module, true); 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 + } 239 303 } 240 304 241 305 function patchElectron() { ··· 249 313 configurable: false 250 314 }); 251 315 } else { 252 - Object.defineProperty( 253 - electronClone, 254 - property, 255 - Object.getOwnPropertyDescriptor(electron, property)! 256 - ); 316 + Object.defineProperty(electronClone, property, Object.getOwnPropertyDescriptor(electron, property)!); 257 317 } 258 318 } 259 319
+8 -1
packages/node-preload/package.json
··· 1 1 { 2 2 "name": "@moonlight-mod/node-preload", 3 3 "private": true, 4 + "engines": { 5 + "node": ">=22", 6 + "pnpm": ">=10", 7 + "npm": "pnpm", 8 + "yarn": "pnpm" 9 + }, 4 10 "dependencies": { 5 11 "@moonlight-mod/core": "workspace:*", 6 12 "@moonlight-mod/types": "workspace:*" 7 - } 13 + }, 14 + "engineStrict": true 8 15 }
+144 -46
packages/node-preload/src/index.ts
··· 1 1 import { webFrame, ipcRenderer, contextBridge } from "electron"; 2 - import fs from "fs"; 3 - import path from "path"; 2 + import fs from "node:fs"; 3 + import path from "node:path"; 4 4 5 5 import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 6 - import { constants } from "@moonlight-mod/types"; 6 + import { constants, MoonlightBranch } from "@moonlight-mod/types"; 7 7 import { getExtensions } from "@moonlight-mod/core/extension"; 8 - import { getExtensionsPath } from "@moonlight-mod/core/util/data"; 9 - import Logger from "@moonlight-mod/core/util/logger"; 10 - import { 11 - loadExtensions, 12 - loadProcessedExtensions 13 - } from "@moonlight-mod/core/extension/loader"; 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 + } 14 25 15 26 async function injectGlobals() { 16 - const config = readConfig(); 17 - const extensions = getExtensions(); 18 - const processed = await loadExtensions(extensions); 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 + }; 19 38 20 - function getConfig(ext: string) { 21 - const val = config.extensions[ext]; 22 - if (val == null || typeof val === "boolean") return undefined; 23 - return val.config; 24 - } 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(); 25 47 26 48 global.moonlightNode = { 27 - config, 28 - extensions: getExtensions(), 29 - processedExtensions: processed, 49 + get config() { 50 + return config; 51 + }, 52 + extensions, 53 + processedExtensions, 30 54 nativesCache: {}, 31 - getConfig, 32 - getConfigOption: <T>(ext: string, name: string) => { 33 - const config = getConfig(ext); 34 - if (config == null) return undefined; 35 - const option = config[name]; 36 - if (option == null) return undefined; 37 - return option as T; 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); 38 76 }, 77 + 39 78 getNatives: (ext: string) => global.moonlightNode.nativesCache[ext], 40 79 getLogger: (id: string) => { 41 80 return new Logger(id); 42 81 }, 43 - 44 - getExtensionDir: (ext: string) => { 45 - const extPath = getExtensionsPath(); 46 - return path.join(extPath, ext); 82 + getMoonlightDir() { 83 + return moonlightDir; 47 84 }, 48 - writeConfig 85 + getExtensionDir: (ext: string) => { 86 + return path.join(extensionsPath, ext); 87 + } 49 88 }; 50 89 51 - await loadProcessedExtensions(processed); 90 + await loadProcessedExtensions(processedExtensions); 52 91 contextBridge.exposeInMainWorld("moonlightNode", moonlightNode); 53 92 54 - const extCors = moonlightNode.processedExtensions.extensions 55 - .map((x) => x.manifest.cors ?? []) 56 - .flat(); 93 + const extCors = moonlightNode.processedExtensions.extensions.flatMap((x) => x.manifest.cors ?? []); 94 + for (const cors of extCors) { 95 + registerCors(cors); 96 + } 57 97 58 98 for (const repo of moonlightNode.config.repositories) { 59 99 const url = new URL(repo); 60 100 url.pathname = "/"; 61 - extCors.push(url.toString()); 101 + registerCors(url.toString()); 62 102 } 63 103 64 - ipcRenderer.invoke(constants.ipcSetCorsList, extCors); 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; 65 112 } 66 113 67 114 async function loadPreload() { 68 115 const webPreloadPath = path.join(__dirname, "web-preload.js"); 69 116 const webPreload = fs.readFileSync(webPreloadPath, "utf8"); 70 117 await webFrame.executeJavaScript(webPreload); 118 + 119 + const func = await webFrame.executeJavaScript("async () => { await window._moonlightWebLoad(); }"); 120 + await func(); 71 121 } 72 122 73 - async function init(oldPreloadPath: string) { 123 + async function init() { 74 124 try { 75 125 await injectGlobals(); 76 126 await loadPreload(); ··· 81 131 message: message 82 132 }); 83 133 } 134 + } 84 135 85 - // Let Discord start even if we fail 86 - if (oldPreloadPath) require(oldPreloadPath); 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 + }); 87 190 } 88 - 89 - const oldPreloadPath: string = ipcRenderer.sendSync( 90 - constants.ipcGetOldPreloadPath 91 - ); 92 - init(oldPreloadPath);
+4 -1
packages/node-preload/tsconfig.json
··· 1 1 { 2 - "extends": "../../tsconfig.json" 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["DOM", "ESNext", "DOM.Iterable"] 5 + } 3 6 }
+15 -6
packages/types/package.json
··· 1 1 { 2 2 "name": "@moonlight-mod/types", 3 - "version": "1.1.8", 4 - "main": "./src/index.ts", 5 - "types": "./src/index.ts", 3 + "version": "1.3.17", 6 4 "exports": { 7 5 ".": "./src/index.ts", 8 6 "./import": "./src/import.d.ts", 9 7 "./*": "./src/*.ts" 10 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 + }, 11 18 "dependencies": { 12 - "@types/flux": "^3.1.12", 13 - "@types/react": "^18.2.22", 14 - "csstype": "^3.1.2", 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", 15 24 "standalone-electron-types": "^1.0.0" 16 25 } 17 26 }
+49 -3
packages/types/src/config.ts
··· 6 6 patchAll?: boolean; 7 7 }; 8 8 9 - export type ConfigExtensions = 10 - | { [key: string]: boolean } 11 - | { [key: string]: ConfigExtension }; 9 + export type ConfigExtensions = { [key: string]: boolean } | { [key: string]: ConfigExtension }; 12 10 13 11 export type ConfigExtension = { 14 12 enabled: boolean; ··· 35 33 }; 36 34 37 35 export type BooleanSettingType = { 36 + /** 37 + * Displays as a simple switch. 38 + */ 38 39 type: ExtensionSettingType.Boolean; 39 40 default?: boolean; 40 41 }; 41 42 42 43 export type NumberSettingType = { 44 + /** 45 + * Displays as a simple slider. 46 + */ 43 47 type: ExtensionSettingType.Number; 44 48 default?: number; 45 49 min?: number; ··· 47 51 }; 48 52 49 53 export type StringSettingType = { 54 + /** 55 + * Displays as a single line string input. 56 + */ 50 57 type: ExtensionSettingType.String; 51 58 default?: string; 52 59 }; 53 60 54 61 export type MultilineTextInputSettingType = { 62 + /** 63 + * Displays as a multiple line string input. 64 + */ 55 65 type: ExtensionSettingType.MultilineString; 56 66 default?: string; 57 67 }; 58 68 59 69 export type SelectSettingType = { 70 + /** 71 + * A dropdown to pick between one of many values. 72 + */ 60 73 type: ExtensionSettingType.Select; 61 74 options: SelectOption[]; 62 75 default?: string; 63 76 }; 64 77 65 78 export type MultiSelectSettingType = { 79 + /** 80 + * A dropdown to pick multiple values. 81 + */ 66 82 type: ExtensionSettingType.MultiSelect; 67 83 options: string[]; 68 84 default?: string[]; 69 85 }; 70 86 71 87 export type ListSettingType = { 88 + /** 89 + * A list of strings that the user can add or remove from. 90 + */ 72 91 type: ExtensionSettingType.List; 73 92 default?: string[]; 74 93 }; 75 94 76 95 export type DictionarySettingType = { 96 + /** 97 + * A dictionary (key-value pair) that the user can add or remove from. 98 + */ 77 99 type: ExtensionSettingType.Dictionary; 78 100 default?: Record<string, string>; 79 101 }; 80 102 81 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 + */ 82 108 type: ExtensionSettingType.Custom; 83 109 default?: any; 84 110 }; 85 111 112 + export enum ExtensionSettingsAdvice { 113 + None = "none", 114 + Reload = "reload", 115 + Restart = "restart" 116 + } 117 + 86 118 export type ExtensionSettingsManifest = { 119 + /** 120 + * A human friendly name for the setting. 121 + */ 87 122 displayName?: string; 123 + 124 + /** 125 + * A longer description for the setting. 126 + * Markdown is not supported. 127 + */ 88 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; 89 135 } & ( 90 136 | BooleanSettingType 91 137 | NumberSettingType
+11 -1
packages/types/src/constants.ts
··· 2 2 export const distDir = "dist"; 3 3 export const coreExtensionsDir = "core-extensions"; 4 4 export const repoUrlFile = ".moonlight-repo-url"; 5 + export const installedVersionFile = ".moonlight-installed-version"; 5 6 7 + export const ipcNodePreloadKickoff = "_moonlight_nodePreloadKickoff"; 6 8 export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath"; 9 + 7 10 export const ipcGetAppData = "_moonlight_getAppData"; 8 - export const ipcGetIsMoonlightDesktop = "_moonlight_getIsMoonlightDesktop"; 11 + export const ipcGetInjectorConfig = "_moonlight_getInjectorConfig"; 9 12 export const ipcMessageBox = "_moonlight_messageBox"; 10 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"];
+30
packages/types/src/core/event.ts
··· 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
-409
packages/types/src/coreExtensions/components.ts
··· 1 - import type { 2 - Component, 3 - Ref, 4 - PropsWithChildren, 5 - PropsWithoutRef, 6 - CSSProperties, 7 - ReactNode, 8 - ReactElement, 9 - ComponentClass, 10 - ComponentType, 11 - MouseEventHandler, 12 - KeyboardEventHandler 13 - } from "react"; 14 - import * as CSS from "csstype"; 15 - 16 - export enum TextInputSizes { 17 - DEFAULT = "inputDefault", 18 - MINI = "inputMini" 19 - } 20 - 21 - interface TextInput 22 - extends ComponentClass< 23 - PropsWithoutRef<{ 24 - value?: string; 25 - name?: string; 26 - className?: string; 27 - inputClassName?: string; 28 - inputPrefix?: string; 29 - disabled?: boolean; 30 - size?: TextInputSizes; 31 - editable?: boolean; 32 - inputRef?: Ref<any>; 33 - prefixElement?: Component; 34 - focusProps?: PropsWithoutRef<any>; 35 - error?: string; 36 - minLength?: number; 37 - maxLength?: number; 38 - onChange?: (value: string, name: string) => void; 39 - onFocus?: (event: any, name: string) => void; 40 - onBlur?: (event: any, name: string) => void; 41 - }> 42 - > { 43 - Sizes: typeof TextInputSizes; 44 - } 45 - 46 - export enum TextAreaAutoComplete { 47 - ON = "on", 48 - OFF = "off" 49 - } 50 - 51 - export enum TextAreaWrap { 52 - HARD = "hard", 53 - SOFT = "soft", 54 - OFF = "off" 55 - } 56 - 57 - interface TextArea 58 - extends ComponentClass< 59 - PropsWithoutRef<{ 60 - value?: string; 61 - defaultValue?: string; 62 - autoComplete?: TextAreaAutoComplete; 63 - autoFocus?: boolean; 64 - cols?: number; 65 - disabled?: boolean; 66 - form?: string; 67 - maxLength?: number; 68 - minLength?: number; 69 - name?: string; 70 - onChange?: (value: string, name: string) => void; 71 - onChangeCapture?: (value: string, name: string) => void; 72 - onInput?: (value: string, name: string) => void; 73 - onInputCapture?: (value: string, name: string) => void; 74 - onInvalid?: (value: string, name: string) => void; 75 - onInvalidCapture?: (value: string, name: string) => void; 76 - onSelect?: (value: string, name: string) => void; 77 - onSelectCapture?: (value: string, name: string) => void; 78 - placeholder?: string; 79 - readOnly?: boolean; 80 - required?: boolean; 81 - rows?: number; 82 - wrap?: TextAreaWrap; 83 - className?: string; 84 - }> 85 - > { 86 - AutoCompletes: typeof TextAreaAutoComplete; 87 - Wraps: typeof TextAreaWrap; 88 - } 89 - 90 - export enum FormTextTypes { 91 - DEFAULT = "default", 92 - DESCRIPTION = "description", 93 - ERROR = "error", 94 - INPUT_PLACEHOLDER = "placeholder", 95 - LABEL_BOLD = "labelBold", 96 - LABEL_DESCRIPTOR = "labelDescriptor", 97 - LABEL_SELECTED = "labelSelected", 98 - SUCCESS = "success" 99 - } 100 - 101 - interface FormText 102 - extends ComponentClass< 103 - PropsWithChildren<{ 104 - type?: FormTextTypes; 105 - className?: string; 106 - disabled?: boolean; 107 - selectable?: boolean; 108 - style?: CSSProperties; 109 - }> 110 - > { 111 - Types: FormTextTypes; 112 - } 113 - 114 - declare enum SliderMarkerPosition { 115 - ABOVE, 116 - BELOW 117 - } 118 - 119 - declare enum ButtonLooks { 120 - FILLED = "lookFilled", 121 - INVERTED = "lookInverted", 122 - OUTLINED = "lookOutlined", 123 - LINK = "lookLink", 124 - BLANK = "lookBlank" 125 - } 126 - declare enum ButtonColors { 127 - BRAND = "colorBrand", 128 - RED = "colorRed", 129 - GREEN = "colorGreen", 130 - YELLOW = "colorYellow", 131 - PRIMARY = "colorPrimary", 132 - LINK = "colorLink", 133 - WHITE = "colorWhite", 134 - BLACK = "colorBlack", 135 - TRANSPARENT = "colorTransparent", 136 - BRAND_NEW = "colorBrandNew", 137 - CUSTOM = "" 138 - } 139 - declare enum ButtonBorderColors { 140 - BRAND = "borderBrand", 141 - RED = "borderRed", 142 - GREEN = "borderGreen", 143 - YELLOW = "borderYellow", 144 - PRIMARY = "borderPrimary", 145 - LINK = "borderLink", 146 - WHITE = "borderWhite", 147 - BLACK = "borderBlack", 148 - TRANSPARENT = "borderTransparent", 149 - BRAND_NEW = "borderBrandNew" 150 - } 151 - declare enum ButtonHovers { 152 - DEFAULT = "", 153 - BRAND = "hoverBrand", 154 - RED = "hoverRed", 155 - GREEN = "hoverGreen", 156 - YELLOW = "hoverYellow", 157 - PRIMARY = "hoverPrimary", 158 - LINK = "hoverLink", 159 - WHITE = "hoverWhite", 160 - BLACK = "hoverBlack", 161 - TRANSPARENT = "hoverTransparent" 162 - } 163 - declare enum ButtonSizes { 164 - NONE = "", 165 - TINY = "sizeTiny", 166 - SMALL = "sizeSmall", 167 - MEDIUM = "sizeMedium", 168 - LARGE = "sizeLarge", 169 - XLARGE = "sizeXlarge", 170 - MIN = "sizeMin", 171 - MAX = "sizeMax", 172 - ICON = "sizeIcon" 173 - } 174 - 175 - type Button = ComponentType< 176 - PropsWithChildren<{ 177 - look?: ButtonLooks; 178 - color?: ButtonColors; 179 - borderColor?: ButtonBorderColors; 180 - hover?: ButtonHovers; 181 - size?: ButtonSizes; 182 - fullWidth?: boolean; 183 - grow?: boolean; 184 - disabled?: boolean; 185 - submitting?: boolean; 186 - type?: string; 187 - style?: CSSProperties; 188 - wrapperClassName?: string; 189 - className?: string; 190 - innerClassName?: string; 191 - onClick?: MouseEventHandler; 192 - onDoubleClick?: MouseEventHandler; 193 - onMouseDown?: MouseEventHandler; 194 - onMouseUp?: MouseEventHandler; 195 - onMouseEnter?: MouseEventHandler; 196 - onMouseLeave?: MouseEventHandler; 197 - onKeyDown?: KeyboardEventHandler; 198 - rel?: any; 199 - buttonRef?: Ref<any>; 200 - focusProps?: PropsWithChildren<any>; 201 - "aria-label"?: string; 202 - submittingStartedLabel?: string; 203 - submittingFinishedLabel?: string; 204 - }> 205 - > & { 206 - Looks: typeof ButtonLooks; 207 - Colors: typeof ButtonColors; 208 - BorderColors: typeof ButtonBorderColors; 209 - Hovers: typeof ButtonHovers; 210 - Sizes: typeof ButtonSizes; 211 - }; 212 - 213 - export enum FlexDirection { 214 - VERTICAL = "vertical", 215 - HORIZONTAL = "horizontal", 216 - HORIZONTAL_REVERSE = "horizontalReverse" 217 - } 218 - 219 - declare enum FlexAlign { 220 - START = "alignStart", 221 - END = "alignEnd", 222 - CENTER = "alignCenter", 223 - STRETCH = "alignStretch", 224 - BASELINE = "alignBaseline" 225 - } 226 - declare enum FlexJustify { 227 - START = "justifyStart", 228 - END = "justifyEnd", 229 - CENTER = "justifyCenter", 230 - BETWEEN = "justifyBetween", 231 - AROUND = "justifyAround" 232 - } 233 - declare enum FlexWrap { 234 - NO_WRAP = "noWrap", 235 - WRAP = "wrap", 236 - WRAP_REVERSE = "wrapReverse" 237 - } 238 - interface Flex 239 - extends ComponentClass< 240 - PropsWithChildren<{ 241 - className?: string; 242 - direction?: FlexDirection; 243 - justify?: FlexJustify; 244 - align?: FlexAlign; 245 - wrap?: FlexWrap; 246 - shrink?: CSS.Property.FlexShrink; 247 - grow?: CSS.Property.FlexGrow; 248 - basis?: CSS.Property.FlexBasis; 249 - style?: CSSProperties; 250 - }> 251 - > { 252 - Direction: typeof FlexDirection; 253 - Align: typeof FlexAlign; 254 - Justify: typeof FlexJustify; 255 - Wrap: typeof FlexWrap; 256 - Child: Component< 257 - PropsWithChildren<{ 258 - className?: string; 259 - shrink?: CSS.Property.FlexShrink; 260 - grow?: CSS.Property.FlexGrow; 261 - basis?: CSS.Property.FlexBasis; 262 - style?: CSSProperties; 263 - wrap?: boolean; 264 - }> 265 - >; 266 - } 267 - 268 - // TODO: wtaf is up with react types not working in jsx 269 - export type CommonComponents = { 270 - [index: string]: any; 271 - Clickable: ComponentClass< 272 - PropsWithChildren<{ 273 - onClick?: () => void; 274 - href?: any; 275 - onKeyPress?: () => void; 276 - ignoreKeyPress?: boolean; 277 - innerRef?: Ref<any>; 278 - focusProps?: any; 279 - tag?: string | Component; 280 - role?: any; 281 - tabIndex?: any; 282 - className?: string; 283 - }> 284 - >; 285 - TextInput: TextInput; 286 - TextArea: TextArea; 287 - FormDivider: ComponentClass<any>; 288 - FormSection: ComponentClass< 289 - PropsWithChildren<{ 290 - className?: string; 291 - titleClassName?: string; 292 - title?: ReactNode; 293 - icon?: ReactNode; 294 - disabled?: boolean; 295 - htmlFor?: any; 296 - tag?: string; 297 - }> 298 - >; 299 - FormText: FormText; 300 - FormTitle: ComponentClass< 301 - PropsWithChildren<{ 302 - tag?: string; 303 - className?: string; 304 - faded?: boolean; 305 - disabled?: boolean; 306 - required?: boolean; 307 - error?: string; 308 - }> 309 - >; 310 - FormSwitch: ComponentClass<PropsWithChildren<any>>; 311 - FormItem: ComponentClass<PropsWithChildren<any>>; 312 - Slider: ComponentClass< 313 - PropsWithChildren<{ 314 - disabled?: boolean; 315 - stickToMarkers?: boolean; 316 - className?: string; 317 - barStyles?: CSSProperties; 318 - fillStyles?: CSSProperties; 319 - mini?: boolean; 320 - hideBubble?: boolean; 321 - initialValue?: number; 322 - orientation?: "horizontal" | "vertical"; 323 - onValueRender?: (value: number) => string; 324 - renderMarker?: (marker: number) => ReactNode; 325 - getAriaValueText?: (value: number) => string; 326 - barClassName?: string; 327 - grabberClassName?: string; 328 - grabberStyles?: CSSProperties; 329 - markerPosition?: SliderMarkerPosition; 330 - "aria-hidden"?: "true" | "false"; 331 - "aria-label"?: string; 332 - "aria-labelledby"?: string; 333 - "aria-describedby"?: string; 334 - minValue?: number; 335 - maxValue?: number; 336 - asValueChanges?: (value: number) => void; 337 - onValueChange?: (value: number) => void; 338 - keyboardStep?: number; 339 - }> 340 - >; 341 - Switch: ComponentClass<PropsWithChildren<any>>; 342 - Button: Button; 343 - Tooltip: ComponentClass<PropsWithChildren<any>>; 344 - SmallSlider: Component; 345 - Avatar: Component; 346 - Scroller: Component; 347 - Text: ComponentClass<PropsWithChildren<any>>; 348 - Heading: ComponentClass<PropsWithChildren<any>>; 349 - LegacyText: Component; 350 - Flex: Flex; 351 - Card: ComponentClass<PropsWithChildren<any>>; 352 - Popout: ComponentClass<PropsWithChildren<any>>; 353 - Dialog: ComponentClass<PropsWithChildren<any>>; 354 - Menu: ComponentClass<PropsWithChildren<any>>; 355 - MenuItem: ComponentClass<PropsWithChildren<any>>; 356 - MenuGroup: ComponentClass<PropsWithChildren<any>>; 357 - MenuCheckboxItem: ComponentClass<PropsWithChildren<any>>; 358 - CardClasses: { 359 - card: string; 360 - cardHeader: string; 361 - }; 362 - ControlClasses: { 363 - container: string; 364 - control: string; 365 - disabled: string; 366 - dividerDefault: string; 367 - labelRow: string; 368 - note: string; 369 - title: string; 370 - titleDefault: string; 371 - titleMini: string; 372 - }; 373 - MarkdownParser: { 374 - parse: (text: string) => ReactElement; 375 - }; 376 - SettingsNotice: React.ComponentType<{ 377 - submitting: boolean; 378 - onReset: () => void; 379 - onSave: () => void; 380 - }>; 381 - TabBar: React.ComponentType<any> & { 382 - Item: React.ComponentType<any>; 383 - }; 384 - SingleSelect: React.ComponentType<{ 385 - autofocus?: boolean; 386 - clearable?: boolean; 387 - value?: string; 388 - options?: { 389 - value: string; 390 - label: string; 391 - }[]; 392 - onChange?: (value: string) => void; 393 - }>; 394 - Select: React.ComponentType<{ 395 - autofocus?: boolean; 396 - clearable?: boolean; 397 - value?: string[]; 398 - options?: { 399 - value: string; 400 - label: string; 401 - }[]; 402 - onChange?: (value: string[]) => void; 403 - }>; 404 - 405 - // TODO 406 - useVariableSelect: any; 407 - multiSelect: any; 408 - tokens: any; 409 - };
+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 */ 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"; 111 11 112 12 export type ContextMenu = { 113 - addItem: ( 114 - navId: string, 115 - item: (props: any) => React.ReactComponentElement<MenuElement>, 116 - anchorId: string, 117 - before?: boolean 118 - ) => void; 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; 119 21 120 22 MenuCheckboxItem: MenuCheckboxItem; 121 23 MenuControlItem: MenuControlItem; ··· 157 59 label: string; 158 60 }; 159 61 160 - export type EvilItemParser = ( 161 - el: 162 - | React.ReactComponentElement<MenuElement> 163 - | React.ReactComponentElement<MenuElement>[] 164 - ) => InternalItem[]; 62 + export type EvilItemParser = (el: MenuElement | MenuElement[]) => InternalItem[]; 63 + 64 + export type { Menu, MenuElement };
+20 -23
packages/types/src/coreExtensions/markdown.ts
··· 11 11 12 12 export type ASTNode = SingleASTNode | Array<SingleASTNode>; 13 13 14 - export type Parser = ( 15 - source: string, 16 - state?: State | null | undefined 17 - ) => Array<SingleASTNode>; 14 + export type Parser = (source: string, state?: State | null | undefined) => Array<SingleASTNode>; 18 15 19 - export type ParseFunction = ( 20 - capture: Capture, 21 - nestedParse: Parser, 22 - state: State 23 - ) => UntypedASTNode | ASTNode; 16 + export type ParseFunction = (capture: Capture, nestedParse: Parser, state: State) => UntypedASTNode | ASTNode; 24 17 25 18 export type Capture = 26 19 | (Array<string> & { ··· 38 31 39 32 export type MatchFunction = { 40 33 regex?: RegExp; 41 - } & (( 42 - source: string, 43 - state: State, 44 - prevCapture: string 45 - ) => Capture | null | undefined); 34 + } & ((source: string, state: State, prevCapture: string) => Capture | null | undefined); 46 35 47 - export type Output<Result> = ( 48 - node: ASTNode, 49 - state?: State | null | undefined 50 - ) => Result; 36 + export type Output<Result> = (node: ASTNode, state?: State | null | undefined) => Result; 51 37 52 - export type SingleNodeOutput<Result> = ( 53 - node: SingleASTNode, 54 - nestedOutput: Output<Result>, 55 - state: State 56 - ) => Result; 38 + export type SingleNodeOutput<Result> = (node: SingleASTNode, nestedOutput: Output<Result>, state: State) => Result; 57 39 58 40 // }}} 59 41 ··· 100 82 slateDecorators: Record<string, string>; 101 83 ruleBlacklists: Record<Ruleset, Record<string, boolean>>; 102 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 + */ 103 94 addRule: ( 104 95 name: string, 105 96 markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule, 106 97 slate: (rules: Record<string, SlateRule>) => SlateRule, 107 98 decorator?: string | undefined 108 99 ) => void; 100 + 101 + /** 102 + * Blacklist a rule from a ruleset. 103 + * @param ruleset The ruleset name 104 + * @param name The rule name 105 + */ 109 106 blacklistFromRuleset: (ruleset: Ruleset, name: string) => void; 110 107 };
+17
packages/types/src/coreExtensions/moonbase.ts
··· 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 + };
+36
packages/types/src/coreExtensions/notices.ts
··· 1 + import type { Store } from "@moonlight-mod/mappings/discord/packages/flux/Store"; 2 + 3 + export type NoticeButton = { 4 + name: string; 5 + onClick: () => boolean; // return true to dismiss the notice after the button is clicked 6 + }; 7 + 8 + export type Notice = { 9 + element: React.ReactNode; 10 + color?: string; 11 + showClose?: boolean; 12 + buttons?: NoticeButton[]; 13 + onDismiss?: () => void; 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 + };
+71
packages/types/src/coreExtensions/settings.ts
··· 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>[]; 6 + element: React.FunctionComponent; 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 + 23 + export type Settings = { 24 + ourSections: SettingsSection[]; 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 + };
+97
packages/types/src/coreExtensions/spacepack.ts
··· 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 + };
+8 -80
packages/types/src/coreExtensions.ts
··· 1 - import { FluxDefault, Store } from "./discord/common/Flux"; 2 - import { CommonComponents as CommonComponents_ } from "./coreExtensions/components"; 3 - import { Dispatcher } from "flux"; 4 - import React, { ReactElement } from "react"; 5 - import { 6 - WebpackModule, 7 - WebpackModuleFunc, 8 - WebpackRequireType 9 - } from "./discord"; 10 - 11 - export type Spacepack = { 12 - inspect: (module: number | string) => WebpackModuleFunc | null; 13 - findByCode: (...args: (string | RegExp)[]) => any[]; 14 - findByExports: (...args: string[]) => any[]; 15 - require: WebpackRequireType; 16 - modules: Record<string, WebpackModuleFunc>; 17 - cache: Record<string, any>; 18 - findObjectFromKey: (exports: Record<string, any>, key: string) => any | null; 19 - findObjectFromValue: (exports: Record<string, any>, value: any) => any | null; 20 - findObjectFromKeyValuePair: ( 21 - exports: Record<string, any>, 22 - key: string, 23 - value: any 24 - ) => any | null; 25 - findFunctionByStrings: ( 26 - exports: Record<string, any>, 27 - ...strings: (string | RegExp)[] 28 - // eslint-disable-next-line @typescript-eslint/ban-types 29 - ) => Function | null; 30 - lazyLoad: ( 31 - find: string | RegExp | (string | RegExp)[], 32 - chunk: RegExp, 33 - module: RegExp 34 - ) => Promise<any>; 35 - filterReal: (modules: WebpackModule[]) => WebpackModule[]; 36 - }; 37 - 38 - export type NoticeProps = { 39 - stores: Store<any>[]; 40 - element: React.FunctionComponent; 41 - }; 42 - 43 - export type SettingsSection = 44 - | { section: "DIVIDER"; pos: number } 45 - | { section: "HEADER"; label: string; pos: number } 46 - | { 47 - section: string; 48 - label: string; 49 - color: string | null; 50 - element: React.FunctionComponent; 51 - pos: number; 52 - notice?: NoticeProps; 53 - _moonlight_submenu?: () => ReactElement | ReactElement[]; 54 - }; 55 - 56 - export type Settings = { 57 - ourSections: SettingsSection[]; 58 - sectionNames: string[]; 59 - sectionMenuItems: Record<string, ReactElement[]>; 60 - 61 - addSection: ( 62 - section: string, 63 - label: string, 64 - element: React.FunctionComponent, 65 - color?: string | null, 66 - pos?: number, 67 - notice?: NoticeProps 68 - ) => void; 69 - addSectionMenuItems: (section: string, ...items: ReactElement[]) => void; 70 - 71 - addDivider: (pos: number | null) => void; 72 - addHeader: (label: string, pos: number | null) => void; 73 - _mutateSections: (sections: SettingsSection[]) => SettingsSection[]; 74 - }; 75 - 76 - export type CommonReact = typeof import("react"); 77 - export type CommonFlux = FluxDefault; 78 - export type CommonComponents = CommonComponents_; // lol 79 - export type CommonFluxDispatcher = Dispatcher<any>; 80 - 1 + export * as Spacepack from "./coreExtensions/spacepack"; 2 + export * as Settings from "./coreExtensions/settings"; 81 3 export * as Markdown from "./coreExtensions/markdown"; 82 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";
-57
packages/types/src/discord/common/Flux.ts
··· 1 - /* 2 - It seems like Discord maintains their own version of Flux that doesn't match 3 - the types on NPM. This is a heavy work in progress - if you encounter rough 4 - edges, please contribute! 5 - */ 6 - 7 - import { DependencyList } from "react"; 8 - import { Store as FluxStore } from "flux/utils"; 9 - import { Dispatcher as FluxDispatcher } from "flux"; 10 - import { ComponentConstructor } from "flux/lib/FluxContainer"; 11 - 12 - export declare abstract class Store<T> extends FluxStore<T> { 13 - static getAll: () => Store<any>[]; 14 - getName: () => string; 15 - emitChange: () => void; 16 - } 17 - 18 - interface ConnectStores { 19 - <T>( 20 - stores: Store<any>[], 21 - callback: T, 22 - context?: any 23 - ): ComponentConstructor<T>; 24 - } 25 - 26 - export type FluxDefault = { 27 - DeviceSettingsStore: any; // TODO 28 - Emitter: any; // @types/fbemitter 29 - OfflineCacheStore: any; // TODO 30 - PersistedStore: any; // TODO 31 - Store: typeof Store; 32 - Dispatcher: typeof FluxDispatcher; 33 - connectStores: ConnectStores; 34 - initialize: () => void; 35 - initialized: Promise<boolean>; 36 - destroy: () => void; 37 - useStateFromStores: UseStateFromStores; 38 - useStateFromStoresArray: UseStateFromStoresArray; 39 - useStateFromStoresObject: UseStateFromStoresObject; 40 - }; 41 - 42 - interface UseStateFromStores { 43 - <T>( 44 - stores: Store<any>[], 45 - callback: () => T, 46 - deps?: DependencyList, 47 - shouldUpdate?: (oldState: T, newState: T) => boolean 48 - ): T; 49 - } 50 - 51 - interface UseStateFromStoresArray { 52 - <T>(stores: Store<any>[], callback: () => T, deps?: DependencyList): T; 53 - } 54 - 55 - interface UseStateFromStoresObject { 56 - <T>(stores: Store<any>[], callback: () => T, deps?: DependencyList): T; 57 - }
+31 -22
packages/types/src/discord/require.ts
··· 1 - import { 2 - Spacepack, 3 - CommonReact, 4 - CommonFlux, 5 - Settings, 6 - CommonComponents, 7 - CommonFluxDispatcher 8 - } from "../coreExtensions"; 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"; 9 5 import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu"; 10 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 11 12 12 declare function WebpackRequire(id: string): any; 13 - declare function WebpackRequire(id: "spacepack_spacepack"): { 14 - default: Spacepack; 15 - spacepack: Spacepack; 16 - }; 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; 17 24 18 - declare function WebpackRequire(id: "common_components"): CommonComponents; 19 - declare function WebpackRequire(id: "common_flux"): CommonFlux; 20 - declare function WebpackRequire( 21 - id: "common_fluxDispatcher" 22 - ): CommonFluxDispatcher; 23 - declare function WebpackRequire(id: "common_react"): CommonReact; 25 + declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser; 26 + declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu; 27 + 28 + declare function WebpackRequire(id: "markdown_markdown"): Markdown; 29 + 30 + declare function WebpackRequire(id: "moonbase_moonbase"): Moonbase; 31 + 32 + declare function WebpackRequire(id: "notices_notices"): Notices; 24 33 25 34 declare function WebpackRequire(id: "settings_settings"): { 26 35 Settings: Settings; 27 36 default: Settings; 28 37 }; 29 38 30 - declare function WebpackRequire(id: "markdown_markdown"): Markdown; 31 - 32 - declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser; 33 - declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu; 39 + declare function WebpackRequire(id: "spacepack_spacepack"): { 40 + default: Spacepack; 41 + spacepack: Spacepack; 42 + }; 34 43 35 44 export default WebpackRequire;
+10 -16
packages/types/src/discord/webpack.ts
··· 1 1 import WebpackRequire from "./require"; 2 + import { WebpackRequire as MappingsWebpackRequire } from "@moonlight-mod/mappings"; 2 3 3 - export type WebpackRequireType = typeof WebpackRequire & { 4 - c: Record<string, WebpackModule>; 5 - m: Record<string, WebpackModuleFunc>; 6 - e: (module: number | string) => Promise<void>; 7 - }; 4 + export type WebpackRequireType = typeof MappingsWebpackRequire & 5 + typeof WebpackRequire & { 6 + c: Record<string, WebpackModule>; 7 + m: Record<string, WebpackModuleFunc>; 8 + e: (module: number | string) => Promise<void>; 9 + }; 8 10 9 11 export type WebpackModule = { 10 12 id: string | number; 11 - loaded: boolean; 13 + loaded?: boolean; 12 14 exports: any; 13 15 }; 14 16 15 - export type WebpackModuleFunc = (( 16 - module: any, 17 - exports: any, 18 - require: WebpackRequireType 19 - ) => void) & { 17 + export type WebpackModuleFunc = ((module: any, exports: any, require: WebpackRequireType) => void) & { 20 18 __moonlight?: boolean; 21 19 }; 22 20 23 - export type WebpackJsonpEntry = [ 24 - number[], 25 - { [id: string]: WebpackModuleFunc }, 26 - (require: WebpackRequireType) => any 27 - ]; 21 + export type WebpackJsonpEntry = [number[], { [id: string]: WebpackModuleFunc }, (require: WebpackRequireType) => any]; 28 22 29 23 export type WebpackJsonp = WebpackJsonpEntry[] & { 30 24 push: {
+116 -4
packages/types/src/extension.ts
··· 28 28 }; 29 29 30 30 export type ExtensionManifest = { 31 + $schema?: string; 32 + 33 + /** 34 + * A unique identifier for your extension. 35 + */ 31 36 id: string; 37 + 38 + /** 39 + * A version string for your extension - doesn't need to follow a specific format. Required for publishing. 40 + */ 32 41 version?: string; 33 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 + */ 34 56 meta?: { 57 + /** 58 + * A human friendly name for your extension as a proper noun. 59 + */ 35 60 name?: string; 61 + 62 + /** 63 + * A short tagline that appears below the name. 64 + */ 36 65 tagline?: string; 66 + 67 + /** 68 + * A longer description that can use Markdown. 69 + */ 37 70 description?: string; 71 + 72 + /** 73 + * List of authors that worked on this extension - accepts string or object with ID. 74 + */ 38 75 authors?: ExtensionAuthor[]; 39 - deprecated?: boolean; 76 + 77 + /** 78 + * A list of tags that are relevant to the extension. 79 + */ 40 80 tags?: ExtensionTag[]; 81 + 82 + /** 83 + * The URL to the source repository. 84 + */ 41 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; 42 102 }; 43 103 104 + /** 105 + * A list of extension IDs that are required for the extension to load. 106 + */ 44 107 dependencies?: string[]; 108 + 109 + /** 110 + * A list of extension IDs that the user may want to install. 111 + */ 45 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 + */ 46 118 incompatible?: string[]; 47 119 120 + /** 121 + * A list of settings for your extension, where the key is the settings ID. 122 + */ 48 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 + */ 49 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[]>; 50 144 }; 51 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 + 52 163 export enum ExtensionLoadSource { 53 164 Developer, 54 165 Core, ··· 65 176 webpackModules?: Record<string, string>; 66 177 nodePath?: string; 67 178 hostPath?: string; 179 + style?: string; 68 180 }; 69 181 }; 70 182 ··· 96 208 export type Patch = { 97 209 find: PatchMatch; 98 210 replace: PatchReplace | PatchReplace[]; 211 + hardFail?: boolean; // if any patches fail, all fail 99 212 prerequisite?: () => boolean; 100 213 }; 101 214 102 215 export type ExplicitExtensionDependency = { 103 - ext: string; 216 + ext?: string; 104 217 id: string; 105 218 }; 106 219 ··· 123 236 id: number; 124 237 }; 125 238 126 - export type IdentifiedWebpackModule = ExtensionWebpackModule & 127 - ExplicitExtensionDependency; 239 + export type IdentifiedWebpackModule = ExtensionWebpackModule & ExplicitExtensionDependency;
+19
packages/types/src/fs.ts
··· 1 + export type MoonlightFS = { 2 + readFile: (path: string) => Promise<Uint8Array>; 3 + readFileString: (path: string) => Promise<string>; 4 + writeFile: (path: string, data: Uint8Array) => Promise<void>; 5 + writeFileString: (path: string, data: string) => Promise<void>; 6 + unlink: (path: string) => Promise<void>; 7 + 8 + readdir: (path: string) => Promise<string[]>; 9 + mkdir: (path: string) => Promise<void>; 10 + rmdir: (path: string) => Promise<void>; 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; 18 + basename: (path: string) => string; 19 + };
+67 -15
packages/types/src/globals.ts
··· 1 - import { Logger } from "./logger"; 2 - import { Config, ConfigExtension } from "./config"; 3 - import { 4 - DetectedExtension, 5 - IdentifiedPatch, 6 - IdentifiedWebpackModule, 7 - ProcessedExtensions 8 - } from "./extension"; 9 - import EventEmitter from "events"; 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"; 10 15 11 16 export type MoonlightHost = { 12 - asarPath: string; 13 17 config: Config; 14 - events: EventEmitter; 15 18 extensions: DetectedExtension[]; 16 19 processedExtensions: ProcessedExtensions; 20 + asarPath: string; 21 + events: EventEmitter; 22 + 23 + version: string; 24 + branch: MoonlightBranch; 17 25 18 26 getConfig: (ext: string) => ConfigExtension["config"]; 27 + getConfigPath: () => Promise<string>; 19 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 + 20 32 getLogger: (id: string) => Logger; 33 + getMoonlightDir: () => string; 34 + getExtensionDir: (ext: string) => string; 21 35 }; 22 36 23 37 export type MoonlightNode = { ··· 25 39 extensions: DetectedExtension[]; 26 40 processedExtensions: ProcessedExtensions; 27 41 nativesCache: Record<string, any>; 42 + isBrowser: boolean; 43 + events: MoonlightEventEmitter<NodeEventType, NodeEventPayloads>; 44 + 45 + version: string; 46 + branch: MoonlightBranch; 28 47 29 48 getConfig: (ext: string) => ConfigExtension["config"]; 30 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 + 31 53 getNatives: (ext: string) => any | undefined; 32 54 getLogger: (id: string) => Logger; 55 + getMoonlightDir: () => string; 56 + getExtensionDir: (ext: string) => string; 57 + }; 33 58 34 - getExtensionDir: (ext: string) => string; 35 - writeConfig: (config: Config) => void; 59 + export type MoonlightNodeSandboxed = { 60 + fs: MoonlightFS; 61 + addCors: (url: string) => void; 62 + addBlocked: (url: string) => void; 36 63 }; 37 64 38 65 export type MoonlightWeb = { 66 + patched: Map<string, Set<string>>; 39 67 unpatched: Set<IdentifiedPatch>; 40 68 pendingModules: Set<IdentifiedWebpackModule>; 41 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; 42 77 43 - getConfig: (ext: string) => ConfigExtension["config"]; 44 - getConfigOption: <T>(ext: string, name: string) => T | undefined; 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 + 45 88 getNatives: (ext: string) => any | undefined; 46 89 getLogger: (id: string) => Logger; 90 + 91 + lunast: LunAST; 92 + moonmap: Moonmap; 47 93 }; 48 94 49 95 export enum MoonlightEnv { ··· 51 97 NodePreload = "node-preload", 52 98 WebPreload = "web-preload" 53 99 } 100 + 101 + export enum MoonlightBranch { 102 + STABLE = "stable", 103 + NIGHTLY = "nightly", 104 + DEV = "dev" 105 + }
+52 -27
packages/types/src/import.d.ts
··· 1 - declare module "@moonlight-mod/wp/spacepack_spacepack" { 1 + declare module "@moonlight-mod/wp/appPanels_appPanels" { 2 2 import { CoreExtensions } from "@moonlight-mod/types"; 3 - export const spacepack: CoreExtensions.Spacepack; 4 - export default spacepack; 3 + const AppPanels: CoreExtensions.AppPanels.AppPanels; 4 + export = AppPanels; 5 5 } 6 6 7 - declare module "@moonlight-mod/wp/common_components" { 7 + declare module "@moonlight-mod/wp/commands_commands" { 8 8 import { CoreExtensions } from "@moonlight-mod/types"; 9 - const CommonComponent: CoreExtensions.CommonComponents; 10 - export = CommonComponent; 9 + export const commands: CoreExtensions.Commands.Commands; 10 + export default commands; 11 11 } 12 12 13 - declare module "@moonlight-mod/wp/common_flux" { 13 + declare module "@moonlight-mod/wp/common_ErrorBoundary" { 14 14 import { CoreExtensions } from "@moonlight-mod/types"; 15 - const Flux: CoreExtensions.CommonFlux; 16 - // FIXME: This is wrong, the default export differs from the named exports. 17 - export = Flux; 15 + const ErrorBoundary: CoreExtensions.Common.ErrorBoundary; 16 + export = ErrorBoundary; 18 17 } 19 - 20 - declare module "@moonlight-mod/wp/common_fluxDispatcher" { 18 + declare module "@moonlight-mod/wp/common_icons" { 21 19 import { CoreExtensions } from "@moonlight-mod/types"; 22 - const Dispatcher: CoreExtensions.CommonFluxDispatcher; 23 - export default Dispatcher; 20 + export const icons: CoreExtensions.Common.Icons; 21 + export default icons; 24 22 } 25 - 26 23 declare module "@moonlight-mod/wp/common_stores"; 27 24 28 - declare module "@moonlight-mod/wp/common_react" { 29 - import React from "react"; 30 - export = React; 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; 31 39 } 32 40 33 - declare module "@moonlight-mod/wp/settings_settings" { 41 + declare module "@moonlight-mod/wp/contextMenu_evilMenu" { 34 42 import { CoreExtensions } from "@moonlight-mod/types"; 35 - export const Settings: CoreExtensions.Settings; 36 - export default Settings; 43 + const EvilParser: CoreExtensions.ContextMenu.EvilItemParser; 44 + export = EvilParser; 45 + } 46 + declare module "@moonlight-mod/wp/contextMenu_contextMenu" { 47 + import { CoreExtensions } from "@moonlight-mod/types"; 48 + const ContextMenu: CoreExtensions.ContextMenu.ContextMenu; 49 + export = ContextMenu; 37 50 } 38 51 39 52 declare module "@moonlight-mod/wp/markdown_markdown" { ··· 42 55 export = Markdown; 43 56 } 44 57 45 - declare module "@moonlight-mod/wp/contextMenu_evilMenu" { 58 + declare module "@moonlight-mod/wp/moonbase_moonbase" { 59 + import { CoreExtensions } from "@moonlight-mod/types"; 60 + const Moonbase: CoreExtensions.Moonbase.Moonbase; 61 + export = Moonbase; 62 + } 63 + 64 + declare module "@moonlight-mod/wp/notices_notices" { 46 65 import { CoreExtensions } from "@moonlight-mod/types"; 47 - const EvilParser: CoreExtensions.ContextMenu.EvilItemParser; 48 - export = EvilParser; 66 + const Notices: CoreExtensions.Notices.Notices; 67 + export = Notices; 68 + } 69 + 70 + declare module "@moonlight-mod/wp/settings_settings" { 71 + import { CoreExtensions } from "@moonlight-mod/types"; 72 + export const Settings: CoreExtensions.Settings.Settings; 73 + export default Settings; 49 74 } 50 75 51 - declare module "@moonlight-mod/wp/contextMenu_contextMenu" { 76 + declare module "@moonlight-mod/wp/spacepack_spacepack" { 52 77 import { CoreExtensions } from "@moonlight-mod/types"; 53 - const ContextMenu: CoreExtensions.ContextMenu.ContextMenu; 54 - export = ContextMenu; 78 + export const spacepack: CoreExtensions.Spacepack.Spacepack; 79 + export default spacepack; 55 80 }
+14 -7
packages/types/src/index.ts
··· 1 1 /// <reference types="standalone-electron-types" /> 2 2 /// <reference types="react" /> 3 - /// <reference types="flux" /> 4 3 /// <reference types="./import" /> 4 + /// <reference types="./mappings" /> 5 5 /* eslint-disable no-var */ 6 6 7 - import { 8 - MoonlightEnv, 9 - MoonlightHost, 10 - MoonlightNode, 11 - MoonlightWeb 12 - } from "./globals"; 7 + import { MoonlightEnv, MoonlightHost, MoonlightNode, MoonlightNodeSandboxed, MoonlightWeb } from "./globals"; 13 8 14 9 export * from "./discord"; 15 10 export * from "./config"; ··· 18 13 export * from "./globals"; 19 14 export * from "./logger"; 20 15 export * as constants from "./constants"; 16 + export * from "./fs"; 17 + 18 + export type { AST } from "@moonlight-mod/lunast"; 19 + export { ModuleExport, ModuleExportType } from "@moonlight-mod/moonmap"; 21 20 22 21 declare global { 23 22 const MOONLIGHT_ENV: MoonlightEnv; ··· 25 24 const MOONLIGHT_INJECTOR: boolean; 26 25 const MOONLIGHT_NODE_PRELOAD: boolean; 27 26 const MOONLIGHT_WEB_PRELOAD: boolean; 27 + const MOONLIGHT_BROWSER: boolean; 28 + const MOONLIGHT_BRANCH: string; 29 + const MOONLIGHT_VERSION: string; 28 30 29 31 var moonlightHost: MoonlightHost; 30 32 var moonlightNode: MoonlightNode; 33 + var moonlightNodeSandboxed: MoonlightNodeSandboxed; 31 34 var moonlight: MoonlightWeb; 35 + var _moonlight_coreExtensionsStr: string; 36 + 37 + var _moonlightBrowserInit: undefined | (() => Promise<void>); 38 + var _moonlightWebLoad: undefined | (() => Promise<void>); 32 39 }
+888
packages/types/src/mappings.d.ts
··· 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 1 { 2 2 "compilerOptions": { 3 - "target": "es2016", 4 - "module": "es6", 5 - "esModuleInterop": true, 6 - "forceConsistentCasingInFileNames": true, 7 - "strict": true, 8 - "moduleResolution": "bundler", 3 + "target": "ES2016", 9 4 "jsx": "react", 10 - "declaration": true 5 + "module": "ES6", 6 + "moduleResolution": "bundler", 7 + "strict": true, 8 + "declaration": true, 9 + "esModuleInterop": true, 10 + "forceConsistentCasingInFileNames": true 11 11 }, 12 12 "include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"] 13 13 }
+13 -1
packages/web-preload/package.json
··· 1 1 { 2 2 "name": "@moonlight-mod/web-preload", 3 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 + }, 4 12 "dependencies": { 5 - "@moonlight-mod/core": "workspace:*" 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:*" 6 18 } 7 19 }
+40 -8
packages/web-preload/src/index.ts
··· 1 1 import { loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; 2 - import { installWebpackPatcher } from "@moonlight-mod/core/patch"; 2 + import { installWebpackPatcher, onModuleLoad, registerPatch, registerWebpackModule } from "@moonlight-mod/core/patch"; 3 + import { constants, MoonlightBranch } from "@moonlight-mod/types"; 3 4 import { installStyles } from "@moonlight-mod/core/styles"; 4 - import Logger from "@moonlight-mod/core/util/logger"; 5 + import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; 6 + import LunAST from "@moonlight-mod/lunast"; 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"; 5 11 6 - (async () => { 12 + async function load() { 13 + delete window._moonlightWebLoad; 14 + initLogger(moonlightNode.config); 7 15 const logger = new Logger("web-preload"); 8 16 9 17 window.moonlight = { 18 + patched: new Map(), 10 19 unpatched: new Set(), 11 20 pendingModules: new Set(), 12 21 enabledExtensions: new Set(), 13 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 + 14 35 getConfig: moonlightNode.getConfig.bind(moonlightNode), 15 36 getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode), 37 + setConfigOption: moonlightNode.setConfigOption.bind(moonlightNode), 38 + writeConfig: moonlightNode.writeConfig.bind(moonlightNode), 39 + 16 40 getNatives: moonlightNode.getNatives.bind(moonlightNode), 17 - getLogger: (id: string) => { 41 + getLogger(id) { 18 42 return new Logger(id); 19 - } 43 + }, 44 + 45 + lunast: new LunAST(), 46 + moonmap: new Moonmap() 20 47 }; 21 48 22 49 try { 50 + loadMappings(window.moonlight.moonmap, window.moonlight.lunast); 23 51 await loadProcessedExtensions(moonlightNode.processedExtensions); 24 52 await installWebpackPatcher(); 25 53 } catch (e) { 26 54 logger.error("Error setting up web-preload", e); 27 55 } 28 56 29 - window.addEventListener("DOMContentLoaded", () => { 57 + if (document.readyState === "complete") { 30 58 installStyles(); 31 - }); 32 - })(); 59 + } else { 60 + window.addEventListener("load", installStyles); 61 + } 62 + } 63 + 64 + window._moonlightWebLoad = load;
+4 -1
packages/web-preload/tsconfig.json
··· 1 1 { 2 - "extends": "../../tsconfig.json" 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["ESNext", "DOM"] 5 + } 3 6 }
+1683 -1139
pnpm-lock.yaml
··· 4 4 autoInstallPeers: true 5 5 excludeLinksFromLockfile: false 6 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 + 7 62 importers: 8 63 9 64 .: 10 65 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) 14 - '@typescript-eslint/parser': 15 - specifier: ^6.13.2 16 - version: 6.13.2(eslint@8.55.0)(typescript@5.3.2) 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 17 72 esbuild: 18 - specifier: ^0.19.3 73 + specifier: catalog:dev 19 74 version: 0.19.3 20 75 esbuild-copy-static-files: 21 - specifier: ^0.1.0 76 + specifier: catalog:dev 22 77 version: 0.1.0 23 78 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)(prettier@3.1.0) 32 - eslint-plugin-react: 33 - specifier: ^7.33.2 34 - version: 7.33.2(eslint@8.55.0) 79 + specifier: catalog:dev 80 + version: 9.23.0(jiti@2.4.2) 35 81 husky: 36 - specifier: ^8.0.3 82 + specifier: catalog:dev 37 83 version: 8.0.3 38 84 prettier: 39 - specifier: ^3.1.0 85 + specifier: catalog:dev 40 86 version: 3.1.0 87 + taze: 88 + specifier: catalog:dev 89 + version: 19.0.4 41 90 typescript: 42 - specifier: ^5.3.2 43 - version: 5.3.2 91 + specifier: catalog:dev 92 + version: 5.8.2 93 + 94 + packages/browser: 95 + dependencies: 96 + '@moonlight-mod/core': 97 + specifier: workspace:* 98 + version: link:../core 99 + '@moonlight-mod/types': 100 + specifier: workspace:* 101 + version: link:../types 102 + '@moonlight-mod/web-preload': 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 44 115 45 116 packages/core: 46 117 dependencies: ··· 50 121 51 122 packages/core-extensions: 52 123 dependencies: 53 - '@electron/asar': 54 - specifier: ^3.2.5 55 - version: 3.2.5 124 + '@moonlight-mod/core': 125 + specifier: workspace:* 126 + version: link:../core 56 127 '@moonlight-mod/types': 57 128 specifier: workspace:* 58 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 59 136 60 137 packages/injector: 61 138 dependencies: ··· 77 154 78 155 packages/types: 79 156 dependencies: 80 - '@types/flux': 81 - specifier: ^3.1.12 82 - version: 3.1.12 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 83 166 '@types/react': 84 - specifier: ^18.2.22 85 - version: 18.2.22 167 + specifier: ^18.3.10 168 + version: 18.3.20 86 169 csstype: 87 - specifier: ^3.1.2 88 - version: 3.1.2 170 + specifier: ^3.1.3 171 + version: 3.1.3 89 172 standalone-electron-types: 90 173 specifier: ^1.0.0 91 174 version: 1.0.0 ··· 95 178 '@moonlight-mod/core': 96 179 specifier: workspace:* 97 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 98 193 99 194 packages: 100 195 ··· 102 197 resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 103 198 engines: {node: '>=0.10.0'} 104 199 105 - '@electron/asar@3.2.5': 106 - resolution: {integrity: sha512-Ypahc2ElTj9YOrFvUHuoXv5Z/V1nPA5enlhmQapc578m/HZBHKTbqhoL5JZQjje2+/6Ti5AHh7Gj1/haeJa63Q==} 107 - engines: {node: '>=10.12.0'} 200 + '@antfu/ni@24.3.0': 201 + resolution: {integrity: sha512-wBSav4mBxvHEW9RbdSo1SWLQ6MAlT0Dc423weC58yOWqW4OcMvtnNDdDrxOZeJ88fEIyPK93gDUWIelBxzSf8g==} 108 202 hasBin: true 109 203 110 204 '@esbuild/android-arm64@0.19.3': ··· 239 333 cpu: [x64] 240 334 os: [win32] 241 335 242 - '@eslint-community/eslint-utils@4.4.0': 243 - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 336 + '@eslint-community/eslint-utils@4.5.1': 337 + resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} 244 338 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 245 339 peerDependencies: 246 340 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 247 341 248 - '@eslint-community/regexpp@4.10.0': 249 - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} 342 + '@eslint-community/regexpp@4.12.1': 343 + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} 250 344 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 251 345 252 - '@eslint/eslintrc@2.1.4': 253 - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} 254 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 346 + '@eslint/config-array@0.19.2': 347 + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} 348 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 255 349 256 - '@eslint/js@8.55.0': 257 - resolution: {integrity: sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==} 258 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 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} 259 353 260 - '@humanwhocodes/config-array@0.11.13': 261 - resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} 262 - engines: {node: '>=10.10.0'} 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'} 263 385 264 386 '@humanwhocodes/module-importer@1.0.1': 265 387 resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 266 388 engines: {node: '>=12.22'} 267 389 268 - '@humanwhocodes/object-schema@2.0.1': 269 - resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} 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==} 270 417 271 418 '@nodelib/fs.scandir@2.1.5': 272 419 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} ··· 280 427 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 281 428 engines: {node: '>= 8'} 282 429 283 - '@pkgr/utils@2.4.2': 284 - resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} 430 + '@pkgr/core@0.2.0': 431 + resolution: {integrity: sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==} 285 432 engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 286 433 287 - '@types/fbemitter@2.0.33': 288 - resolution: {integrity: sha512-KcSilwdl0D8YgXGL6l9d+rTBm2W7pDyTZrDEw0+IzqQ724676KJtMeO+xHodJewKFWZT+GFWaJubA5mpMxSkcg==} 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==} 289 440 290 - '@types/flux@3.1.12': 291 - resolution: {integrity: sha512-HZ8o/DTVNgcgnXoDyn0ZnjqEZMT4Chr4w5ktMQSbQAnqVDklasmRqNGd2agZDsk5i0jYHQLgQQuM782bWG7fUA==} 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==} 292 473 293 474 '@types/json-schema@7.0.15': 294 475 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 476 + 477 + '@types/lodash@4.17.14': 478 + resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} 295 479 296 480 '@types/node@18.17.17': 297 481 resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} 298 482 299 - '@types/prop-types@15.7.6': 300 - resolution: {integrity: sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==} 483 + '@types/node@22.13.6': 484 + resolution: {integrity: sha512-GYmF65GI7417CpZXsEXMjT8goQQDnpRnJnDw6jIYa+le3V/lMazPZ4vZmK1B/9R17fh2VLr2zuy9d/h5xgrLAg==} 301 485 302 - '@types/react@18.2.22': 303 - resolution: {integrity: sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==} 486 + '@types/node@22.14.0': 487 + resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} 304 488 305 - '@types/scheduler@0.16.3': 306 - resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} 489 + '@types/platform@1.3.6': 490 + resolution: {integrity: sha512-ZmSaqHuvzv+jC232cFoz2QqPUkaj6EvMmCrWcx3WRr7xTPVFCMUOTcOq8m2d+Zw1iKRc1kDiaA+jtNrV0hkVew==} 307 491 308 - '@types/semver@7.5.6': 309 - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} 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==} 310 497 311 - '@typescript-eslint/eslint-plugin@6.13.2': 312 - resolution: {integrity: sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==} 313 - engines: {node: ^16.0.0 || >=18.0.0} 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} 314 501 peerDependencies: 315 - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha 316 - eslint: ^7.0.0 || ^8.0.0 317 - typescript: '*' 318 - peerDependenciesMeta: 319 - typescript: 320 - optional: true 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' 321 505 322 - '@typescript-eslint/parser@6.13.2': 323 - resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==} 324 - engines: {node: ^16.0.0 || >=18.0.0} 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} 325 509 peerDependencies: 326 - eslint: ^7.0.0 || ^8.0.0 327 - typescript: '*' 328 - peerDependenciesMeta: 329 - typescript: 330 - optional: true 510 + eslint: ^8.57.0 || ^9.0.0 511 + typescript: '>=4.8.4 <5.9.0' 331 512 332 - '@typescript-eslint/scope-manager@6.13.2': 333 - resolution: {integrity: sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==} 334 - engines: {node: ^16.0.0 || >=18.0.0} 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} 335 516 336 - '@typescript-eslint/type-utils@6.13.2': 337 - resolution: {integrity: sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==} 338 - engines: {node: ^16.0.0 || >=18.0.0} 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} 339 520 peerDependencies: 340 - eslint: ^7.0.0 || ^8.0.0 341 - typescript: '*' 342 - peerDependenciesMeta: 343 - typescript: 344 - optional: true 521 + eslint: ^8.57.0 || ^9.0.0 522 + typescript: '>=4.8.4 <5.9.0' 345 523 346 - '@typescript-eslint/types@6.13.2': 347 - resolution: {integrity: sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==} 348 - engines: {node: ^16.0.0 || >=18.0.0} 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} 349 527 350 - '@typescript-eslint/typescript-estree@6.13.2': 351 - resolution: {integrity: sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==} 352 - engines: {node: ^16.0.0 || >=18.0.0} 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} 353 531 peerDependencies: 354 - typescript: '*' 355 - peerDependenciesMeta: 356 - typescript: 357 - optional: true 532 + typescript: '>=4.8.4 <5.9.0' 358 533 359 - '@typescript-eslint/utils@6.13.2': 360 - resolution: {integrity: sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==} 361 - engines: {node: ^16.0.0 || >=18.0.0} 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} 362 537 peerDependencies: 363 - eslint: ^7.0.0 || ^8.0.0 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 364 552 365 - '@typescript-eslint/visitor-keys@6.13.2': 366 - resolution: {integrity: sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==} 367 - engines: {node: ^16.0.0 || >=18.0.0} 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 368 559 369 - '@ungap/structured-clone@1.2.0': 370 - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} 560 + abort-controller@3.0.0: 561 + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} 562 + engines: {node: '>=6.5'} 371 563 372 564 acorn-jsx@5.3.2: 373 565 resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 374 566 peerDependencies: 375 567 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 376 568 377 - acorn@8.11.2: 378 - resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} 569 + acorn@8.14.1: 570 + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 379 571 engines: {node: '>=0.4.0'} 380 572 hasBin: true 381 573 382 574 ajv@6.12.6: 383 575 resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 384 576 385 - ansi-regex@5.0.1: 386 - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 387 - engines: {node: '>=8'} 388 - 389 577 ansi-styles@4.3.0: 390 578 resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 391 579 engines: {node: '>=8'} 392 580 581 + ansis@3.17.0: 582 + resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} 583 + engines: {node: '>=14'} 584 + 393 585 argparse@2.0.1: 394 586 resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 395 587 396 - array-buffer-byte-length@1.0.0: 397 - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} 588 + array-buffer-byte-length@1.0.2: 589 + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} 590 + engines: {node: '>= 0.4'} 398 591 399 - array-includes@3.1.7: 400 - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} 592 + array-includes@3.1.8: 593 + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} 401 594 engines: {node: '>= 0.4'} 402 595 403 - array-union@2.1.0: 404 - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 405 - engines: {node: '>=8'} 596 + array.prototype.findlast@1.2.5: 597 + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} 598 + engines: {node: '>= 0.4'} 406 599 407 - array.prototype.flat@1.3.2: 408 - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} 600 + array.prototype.flat@1.3.3: 601 + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} 409 602 engines: {node: '>= 0.4'} 410 603 411 - array.prototype.flatmap@1.3.2: 412 - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} 604 + array.prototype.flatmap@1.3.3: 605 + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} 413 606 engines: {node: '>= 0.4'} 414 607 415 - array.prototype.tosorted@1.1.2: 416 - resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} 608 + array.prototype.tosorted@1.1.4: 609 + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} 610 + engines: {node: '>= 0.4'} 417 611 418 - arraybuffer.prototype.slice@1.0.2: 419 - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} 612 + arraybuffer.prototype.slice@1.0.4: 613 + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} 420 614 engines: {node: '>= 0.4'} 421 615 422 - asynciterator.prototype@1.0.0: 423 - resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} 616 + astring@1.9.0: 617 + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} 618 + hasBin: true 424 619 425 - available-typed-arrays@1.0.5: 426 - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} 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==} 427 626 engines: {node: '>= 0.4'} 428 627 429 628 balanced-match@1.0.2: 430 629 resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 431 630 432 - big-integer@1.6.52: 433 - resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} 434 - engines: {node: '>=0.6'} 435 - 436 - bplist-parser@0.2.0: 437 - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} 438 - engines: {node: '>= 5.10.0'} 631 + base64-js@1.5.1: 632 + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 439 633 440 634 brace-expansion@1.1.11: 441 635 resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 442 636 443 - braces@3.0.2: 444 - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 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==} 445 642 engines: {node: '>=8'} 446 643 447 - bundle-name@3.0.0: 448 - resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} 449 - engines: {node: '>=12'} 644 + buffer@6.0.3: 645 + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 450 646 451 - call-bind@1.0.5: 452 - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} 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'} 453 662 454 663 callsites@3.1.0: 455 664 resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} ··· 466 675 color-name@1.1.4: 467 676 resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 468 677 469 - commander@5.1.0: 470 - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} 471 - engines: {node: '>= 6'} 472 - 473 678 concat-map@0.0.1: 474 - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 679 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 475 680 476 - cross-spawn@7.0.3: 477 - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 681 + cross-spawn@7.0.6: 682 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 478 683 engines: {node: '>= 8'} 479 684 480 - csstype@3.1.2: 481 - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} 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'} 482 699 483 - debug@4.3.4: 484 - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 700 + debug@4.4.0: 701 + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 485 702 engines: {node: '>=6.0'} 486 703 peerDependencies: 487 704 supports-color: '*' ··· 492 709 deep-is@0.1.4: 493 710 resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 494 711 495 - default-browser-id@3.0.0: 496 - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} 497 - engines: {node: '>=12'} 498 - 499 - default-browser@4.0.0: 500 - resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} 501 - engines: {node: '>=14.16'} 502 - 503 - define-data-property@1.1.1: 504 - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} 712 + define-data-property@1.1.4: 713 + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 505 714 engines: {node: '>= 0.4'} 506 - 507 - define-lazy-prop@3.0.0: 508 - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} 509 - engines: {node: '>=12'} 510 715 511 716 define-properties@1.2.1: 512 717 resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} 513 718 engines: {node: '>= 0.4'} 514 719 515 - dir-glob@3.0.1: 516 - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 517 - engines: {node: '>=8'} 720 + defu@6.1.4: 721 + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 722 + 723 + destr@2.0.4: 724 + resolution: {integrity: sha512-FCAorltMy7QwX0QU38jOkhrv20LBpsHA8ogzvMhhPHCCKVCaN6GxrB0GGaWEWBUYI4eEjjfJ95RdP6dk9IdMQA==} 518 725 519 726 doctrine@2.1.0: 520 727 resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} 521 728 engines: {node: '>=0.10.0'} 522 729 523 - doctrine@3.0.0: 524 - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 525 - engines: {node: '>=6.0.0'} 730 + dunder-proto@1.0.1: 731 + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 732 + engines: {node: '>= 0.4'} 526 733 527 - es-abstract@1.22.3: 528 - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} 734 + es-abstract@1.23.9: 735 + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} 529 736 engines: {node: '>= 0.4'} 530 737 531 - es-iterator-helpers@1.0.15: 532 - resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} 738 + es-define-property@1.0.1: 739 + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 740 + engines: {node: '>= 0.4'} 533 741 534 - es-set-tostringtag@2.0.2: 535 - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} 742 + es-errors@1.3.0: 743 + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 536 744 engines: {node: '>= 0.4'} 537 745 538 - es-shim-unscopables@1.0.2: 539 - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} 746 + es-iterator-helpers@1.2.1: 747 + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} 748 + engines: {node: '>= 0.4'} 540 749 541 - es-to-primitive@1.2.1: 542 - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} 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==} 543 764 engines: {node: '>= 0.4'} 544 765 545 766 esbuild-copy-static-files@0.1.0: ··· 560 781 peerDependencies: 561 782 eslint: '>=7.0.0' 562 783 563 - eslint-plugin-prettier@5.0.1: 564 - resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} 784 + eslint-plugin-prettier@5.2.6: 785 + resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==} 565 786 engines: {node: ^14.18.0 || >=16.0.0} 566 787 peerDependencies: 567 788 '@types/eslint': '>=8.0.0' 568 789 eslint: '>=8.0.0' 569 - eslint-config-prettier: '*' 790 + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' 570 791 prettier: '>=3.0.0' 571 792 peerDependenciesMeta: 572 793 '@types/eslint': ··· 574 795 eslint-config-prettier: 575 796 optional: true 576 797 577 - eslint-plugin-react@7.33.2: 578 - resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} 798 + eslint-plugin-react@7.37.5: 799 + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} 579 800 engines: {node: '>=4'} 580 801 peerDependencies: 581 - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 802 + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 582 803 583 - eslint-scope@7.2.2: 584 - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 585 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 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} 586 807 587 808 eslint-visitor-keys@3.4.3: 588 809 resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 589 810 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 590 811 591 - eslint@8.55.0: 592 - resolution: {integrity: sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==} 593 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 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} 594 819 hasBin: true 820 + peerDependencies: 821 + jiti: '*' 822 + peerDependenciesMeta: 823 + jiti: 824 + optional: true 595 825 596 - espree@9.6.1: 597 - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 598 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 826 + espree@10.3.0: 827 + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} 828 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 599 829 600 - esquery@1.5.0: 601 - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 830 + esquery@1.6.0: 831 + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 602 832 engines: {node: '>=0.10'} 603 833 604 834 esrecurse@4.3.0: ··· 608 838 estraverse@5.3.0: 609 839 resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 610 840 engines: {node: '>=4.0'} 841 + 842 + estree-toolkit@1.7.8: 843 + resolution: {integrity: sha512-v0Q0L+0agSDFe3x9Sj7aAzrI9afvsfr5r7AM2SNk/8bKYRQ3tUf4PQEUWe99LkWysmT1PsuSpW+W1w/xZmCKeg==} 611 844 612 845 esutils@2.0.3: 613 846 resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 614 847 engines: {node: '>=0.10.0'} 615 848 616 - execa@5.1.1: 617 - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} 618 - engines: {node: '>=10'} 849 + event-target-shim@5.0.1: 850 + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} 851 + engines: {node: '>=6'} 619 852 620 - execa@7.2.0: 621 - resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} 622 - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} 853 + eventemitter3@5.0.1: 854 + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} 855 + 856 + events@3.3.0: 857 + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 858 + engines: {node: '>=0.8.x'} 623 859 624 860 fast-deep-equal@3.1.3: 625 861 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} ··· 637 873 fast-levenshtein@2.0.6: 638 874 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 639 875 640 - fastq@1.15.0: 641 - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 876 + fastq@1.17.1: 877 + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} 642 878 643 - file-entry-cache@6.0.1: 644 - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 645 - engines: {node: ^10.12.0 || >=12.0.0} 879 + fdir@6.4.3: 880 + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} 881 + peerDependencies: 882 + picomatch: ^3 || ^4 883 + peerDependenciesMeta: 884 + picomatch: 885 + optional: true 646 886 647 - fill-range@7.0.1: 648 - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 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==} 649 893 engines: {node: '>=8'} 650 894 895 + find-up-simple@1.0.1: 896 + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} 897 + engines: {node: '>=18'} 898 + 651 899 find-up@5.0.0: 652 900 resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 653 901 engines: {node: '>=10'} 654 902 655 - flat-cache@3.2.0: 656 - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} 657 - engines: {node: ^10.12.0 || >=12.0.0} 903 + flat-cache@4.0.1: 904 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 905 + engines: {node: '>=16'} 658 906 659 907 flatted@3.2.9: 660 908 resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} 661 909 662 - for-each@0.3.3: 663 - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} 664 - 665 - fs.realpath@1.0.0: 666 - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 910 + for-each@0.3.5: 911 + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} 912 + engines: {node: '>= 0.4'} 667 913 668 914 function-bind@1.1.2: 669 915 resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 670 916 671 - function.prototype.name@1.1.6: 672 - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} 917 + function.prototype.name@1.1.8: 918 + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} 673 919 engines: {node: '>= 0.4'} 674 920 675 921 functions-have-names@1.2.3: 676 922 resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} 677 923 678 - get-intrinsic@1.2.2: 679 - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} 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'} 680 930 681 - get-stream@6.0.1: 682 - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} 683 - engines: {node: '>=10'} 931 + get-proto@1.0.1: 932 + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 933 + engines: {node: '>= 0.4'} 684 934 685 - get-symbol-description@1.0.0: 686 - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} 935 + get-symbol-description@1.1.0: 936 + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} 687 937 engines: {node: '>= 0.4'} 688 938 689 939 glob-parent@5.1.2: ··· 694 944 resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 695 945 engines: {node: '>=10.13.0'} 696 946 697 - glob@7.2.3: 698 - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 947 + globals@14.0.0: 948 + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 949 + engines: {node: '>=18'} 699 950 700 - globals@13.23.0: 701 - resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} 702 - engines: {node: '>=8'} 703 - 704 - globalthis@1.0.3: 705 - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} 951 + globalthis@1.0.4: 952 + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} 706 953 engines: {node: '>= 0.4'} 707 954 708 - globby@11.1.0: 709 - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 710 - engines: {node: '>=10'} 711 - 712 - gopd@1.0.1: 713 - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} 955 + gopd@1.2.0: 956 + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 957 + engines: {node: '>= 0.4'} 714 958 715 959 graphemer@1.4.0: 716 960 resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 717 961 718 - has-bigints@1.0.2: 719 - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} 962 + has-bigints@1.1.0: 963 + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} 964 + engines: {node: '>= 0.4'} 720 965 721 966 has-flag@4.0.0: 722 967 resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 723 968 engines: {node: '>=8'} 724 969 725 - has-property-descriptors@1.0.1: 726 - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} 970 + has-property-descriptors@1.0.2: 971 + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} 727 972 728 - has-proto@1.0.1: 729 - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} 973 + has-proto@1.2.0: 974 + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} 730 975 engines: {node: '>= 0.4'} 731 976 732 - has-symbols@1.0.3: 733 - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 977 + has-symbols@1.1.0: 978 + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 734 979 engines: {node: '>= 0.4'} 735 980 736 - has-tostringtag@1.0.0: 737 - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} 981 + has-tostringtag@1.0.2: 982 + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 738 983 engines: {node: '>= 0.4'} 739 984 740 - hasown@2.0.0: 741 - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} 985 + hasown@2.0.2: 986 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 742 987 engines: {node: '>= 0.4'} 743 988 744 - human-signals@2.1.0: 745 - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} 746 - engines: {node: '>=10.17.0'} 747 - 748 - human-signals@4.3.1: 749 - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} 750 - engines: {node: '>=14.18.0'} 751 - 752 989 husky@8.0.3: 753 990 resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} 754 991 engines: {node: '>=14'} 755 992 hasBin: true 756 993 757 - ignore@5.3.0: 758 - resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} 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==} 759 999 engines: {node: '>= 4'} 760 1000 761 1001 import-fresh@3.3.0: ··· 766 1006 resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 767 1007 engines: {node: '>=0.8.19'} 768 1008 769 - inflight@1.0.6: 770 - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 1009 + internal-slot@1.1.0: 1010 + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} 1011 + engines: {node: '>= 0.4'} 771 1012 772 - inherits@2.0.4: 773 - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 774 - 775 - internal-slot@1.0.6: 776 - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} 1013 + is-array-buffer@3.0.5: 1014 + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} 777 1015 engines: {node: '>= 0.4'} 778 1016 779 - is-array-buffer@3.0.2: 780 - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} 781 - 782 - is-async-function@2.0.0: 783 - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} 1017 + is-async-function@2.1.1: 1018 + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} 784 1019 engines: {node: '>= 0.4'} 785 1020 786 - is-bigint@1.0.4: 787 - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} 1021 + is-bigint@1.1.0: 1022 + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} 1023 + engines: {node: '>= 0.4'} 788 1024 789 - is-boolean-object@1.1.2: 790 - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} 1025 + is-boolean-object@1.2.2: 1026 + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} 791 1027 engines: {node: '>= 0.4'} 792 1028 793 1029 is-callable@1.2.7: 794 1030 resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 795 1031 engines: {node: '>= 0.4'} 796 1032 797 - is-core-module@2.13.1: 798 - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} 799 - 800 - is-date-object@1.0.5: 801 - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} 1033 + is-core-module@2.16.1: 1034 + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} 802 1035 engines: {node: '>= 0.4'} 803 1036 804 - is-docker@2.2.1: 805 - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} 806 - engines: {node: '>=8'} 807 - hasBin: true 1037 + is-data-view@1.0.2: 1038 + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} 1039 + engines: {node: '>= 0.4'} 808 1040 809 - is-docker@3.0.0: 810 - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} 811 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 812 - hasBin: true 1041 + is-date-object@1.1.0: 1042 + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} 1043 + engines: {node: '>= 0.4'} 813 1044 814 1045 is-extglob@2.1.1: 815 1046 resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 816 1047 engines: {node: '>=0.10.0'} 817 1048 818 - is-finalizationregistry@1.0.2: 819 - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} 1049 + is-finalizationregistry@1.1.1: 1050 + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} 1051 + engines: {node: '>= 0.4'} 820 1052 821 - is-generator-function@1.0.10: 822 - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} 1053 + is-generator-function@1.1.0: 1054 + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} 823 1055 engines: {node: '>= 0.4'} 824 1056 825 1057 is-glob@4.0.3: 826 1058 resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 827 1059 engines: {node: '>=0.10.0'} 828 1060 829 - is-inside-container@1.0.0: 830 - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} 831 - engines: {node: '>=14.16'} 832 - hasBin: true 833 - 834 - is-map@2.0.2: 835 - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} 836 - 837 - is-negative-zero@2.0.2: 838 - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} 1061 + is-map@2.0.3: 1062 + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} 839 1063 engines: {node: '>= 0.4'} 840 1064 841 - is-number-object@1.0.7: 842 - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} 1065 + is-number-object@1.1.1: 1066 + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} 843 1067 engines: {node: '>= 0.4'} 844 1068 845 1069 is-number@7.0.0: 846 1070 resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 847 1071 engines: {node: '>=0.12.0'} 848 1072 849 - is-path-inside@3.0.3: 850 - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 851 - engines: {node: '>=8'} 1073 + is-regex@1.2.1: 1074 + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} 1075 + engines: {node: '>= 0.4'} 852 1076 853 - is-regex@1.1.4: 854 - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} 1077 + is-set@2.0.3: 1078 + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} 855 1079 engines: {node: '>= 0.4'} 856 1080 857 - is-set@2.0.2: 858 - resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} 1081 + is-shared-array-buffer@1.0.4: 1082 + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} 1083 + engines: {node: '>= 0.4'} 859 1084 860 - is-shared-array-buffer@1.0.2: 861 - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} 1085 + is-string@1.1.1: 1086 + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} 1087 + engines: {node: '>= 0.4'} 862 1088 863 - is-stream@2.0.1: 864 - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 865 - engines: {node: '>=8'} 866 - 867 - is-stream@3.0.0: 868 - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} 869 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 870 - 871 - is-string@1.0.7: 872 - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} 1089 + is-symbol@1.1.1: 1090 + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} 873 1091 engines: {node: '>= 0.4'} 874 1092 875 - is-symbol@1.0.4: 876 - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} 1093 + is-typed-array@1.1.15: 1094 + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} 877 1095 engines: {node: '>= 0.4'} 878 1096 879 - is-typed-array@1.1.12: 880 - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} 1097 + is-weakmap@2.0.2: 1098 + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} 881 1099 engines: {node: '>= 0.4'} 882 1100 883 - is-weakmap@2.0.1: 884 - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} 1101 + is-weakref@1.1.1: 1102 + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} 1103 + engines: {node: '>= 0.4'} 885 1104 886 - is-weakref@1.0.2: 887 - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} 888 - 889 - is-weakset@2.0.2: 890 - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} 891 - 892 - is-wsl@2.2.0: 893 - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} 894 - engines: {node: '>=8'} 1105 + is-weakset@2.0.4: 1106 + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} 1107 + engines: {node: '>= 0.4'} 895 1108 896 1109 isarray@2.0.5: 897 1110 resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} ··· 899 1112 isexe@2.0.0: 900 1113 resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 901 1114 902 - iterator.prototype@1.1.2: 903 - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} 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 904 1122 905 1123 js-tokens@4.0.0: 906 1124 resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} ··· 940 1158 resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 941 1159 hasBin: true 942 1160 943 - lru-cache@6.0.0: 944 - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 945 - engines: {node: '>=10'} 946 - 947 - merge-stream@2.0.0: 948 - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 1161 + math-intrinsics@1.1.0: 1162 + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 1163 + engines: {node: '>= 0.4'} 949 1164 950 1165 merge2@1.4.1: 951 1166 resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 952 1167 engines: {node: '>= 8'} 953 1168 954 - micromatch@4.0.5: 955 - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 1169 + meriyah@6.0.1: 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==} 956 1178 engines: {node: '>=8.6'} 957 1179 958 - mimic-fn@2.1.0: 959 - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 960 - engines: {node: '>=6'} 961 - 962 - mimic-fn@4.0.0: 963 - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} 964 - engines: {node: '>=12'} 1180 + mimic-function@5.0.1: 1181 + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} 1182 + engines: {node: '>=18'} 965 1183 966 1184 minimatch@3.1.2: 967 1185 resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 968 1186 969 - ms@2.1.2: 970 - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1187 + minimatch@9.0.5: 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==} 971 1196 972 1197 natural-compare@1.4.0: 973 1198 resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 974 1199 975 - npm-run-path@4.0.1: 976 - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 977 - engines: {node: '>=8'} 978 - 979 - npm-run-path@5.1.0: 980 - resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} 981 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1200 + node-fetch-native@1.6.6: 1201 + resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} 982 1202 983 1203 object-assign@4.1.1: 984 1204 resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 985 1205 engines: {node: '>=0.10.0'} 986 1206 987 - object-inspect@1.13.1: 988 - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} 1207 + object-inspect@1.13.4: 1208 + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 1209 + engines: {node: '>= 0.4'} 989 1210 990 1211 object-keys@1.1.1: 991 1212 resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 992 1213 engines: {node: '>= 0.4'} 993 1214 994 - object.assign@4.1.5: 995 - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} 1215 + object.assign@4.1.7: 1216 + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} 996 1217 engines: {node: '>= 0.4'} 997 1218 998 - object.entries@1.1.7: 999 - resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} 1219 + object.entries@1.1.9: 1220 + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} 1000 1221 engines: {node: '>= 0.4'} 1001 1222 1002 - object.fromentries@2.0.7: 1003 - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} 1223 + object.fromentries@2.0.8: 1224 + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} 1004 1225 engines: {node: '>= 0.4'} 1005 1226 1006 - object.hasown@1.1.3: 1007 - resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} 1008 - 1009 - object.values@1.1.7: 1010 - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} 1227 + object.values@1.2.1: 1228 + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} 1011 1229 engines: {node: '>= 0.4'} 1012 1230 1013 - once@1.4.0: 1014 - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1015 - 1016 - onetime@5.1.2: 1017 - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 1018 - engines: {node: '>=6'} 1019 - 1020 - onetime@6.0.0: 1021 - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} 1022 - engines: {node: '>=12'} 1231 + ofetch@1.4.1: 1232 + resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} 1023 1233 1024 - open@9.1.0: 1025 - resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} 1026 - engines: {node: '>=14.16'} 1234 + onetime@7.0.0: 1235 + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} 1236 + engines: {node: '>=18'} 1027 1237 1028 1238 optionator@0.9.3: 1029 1239 resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1030 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'} 1031 1245 1032 1246 p-limit@3.1.0: 1033 1247 resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} ··· 1037 1251 resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1038 1252 engines: {node: '>=10'} 1039 1253 1254 + package-manager-detector@1.1.0: 1255 + resolution: {integrity: sha512-Y8f9qUlBzW8qauJjd/eu6jlpJZsuPJm2ZAV0cDVd420o4EdpH5RPdoCv+60/TdJflGatr4sDfpAL6ArWZbM5tA==} 1256 + 1040 1257 parent-module@1.0.1: 1041 1258 resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1042 1259 engines: {node: '>=6'} ··· 1045 1262 resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1046 1263 engines: {node: '>=8'} 1047 1264 1048 - path-is-absolute@1.0.1: 1049 - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1050 - engines: {node: '>=0.10.0'} 1051 - 1052 1265 path-key@3.1.1: 1053 1266 resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1054 1267 engines: {node: '>=8'} 1055 - 1056 - path-key@4.0.0: 1057 - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} 1058 - engines: {node: '>=12'} 1059 1268 1060 1269 path-parse@1.0.7: 1061 1270 resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1062 1271 1063 - path-type@4.0.0: 1064 - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1065 - engines: {node: '>=8'} 1066 - 1067 - picocolors@1.0.0: 1068 - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 1272 + pathe@2.0.3: 1273 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 1069 1274 1070 1275 picomatch@2.3.1: 1071 1276 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1072 1277 engines: {node: '>=8.6'} 1073 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 + 1074 1290 prelude-ls@1.2.1: 1075 1291 resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1076 1292 engines: {node: '>= 0.8.0'} ··· 1084 1300 engines: {node: '>=14'} 1085 1301 hasBin: true 1086 1302 1303 + process@0.11.10: 1304 + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} 1305 + engines: {node: '>= 0.6.0'} 1306 + 1087 1307 prop-types@15.8.1: 1088 1308 resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} 1089 1309 ··· 1091 1311 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1092 1312 engines: {node: '>=6'} 1093 1313 1314 + quansync@0.2.10: 1315 + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} 1316 + 1094 1317 queue-microtask@1.2.3: 1095 1318 resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1096 1319 1097 1320 react-is@16.13.1: 1098 1321 resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} 1099 1322 1100 - reflect.getprototypeof@1.0.4: 1101 - resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} 1323 + readable-stream@4.5.2: 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==} 1102 1329 engines: {node: '>= 0.4'} 1103 1330 1104 - regexp.prototype.flags@1.5.1: 1105 - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} 1331 + regexp.prototype.flags@1.5.4: 1332 + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} 1106 1333 engines: {node: '>= 0.4'} 1107 1334 1108 1335 resolve-from@4.0.0: ··· 1113 1340 resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} 1114 1341 hasBin: true 1115 1342 1343 + restore-cursor@5.1.0: 1344 + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} 1345 + engines: {node: '>=18'} 1346 + 1116 1347 reusify@1.0.4: 1117 1348 resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1118 1349 engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1119 1350 1120 - rimraf@3.0.2: 1121 - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1122 - hasBin: true 1123 - 1124 - run-applescript@5.0.0: 1125 - resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} 1126 - engines: {node: '>=12'} 1127 - 1128 1351 run-parallel@1.2.0: 1129 1352 resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1130 1353 1131 - safe-array-concat@1.0.1: 1132 - resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} 1354 + safe-array-concat@1.1.3: 1355 + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} 1133 1356 engines: {node: '>=0.4'} 1134 1357 1135 - safe-regex-test@1.0.0: 1136 - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} 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'} 1137 1368 1138 1369 semver@6.3.1: 1139 1370 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1140 1371 hasBin: true 1141 1372 1142 - semver@7.5.4: 1143 - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 1373 + semver@7.7.1: 1374 + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} 1144 1375 engines: {node: '>=10'} 1145 1376 hasBin: true 1146 1377 1147 - set-function-length@1.1.1: 1148 - resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} 1378 + set-function-length@1.2.2: 1379 + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} 1149 1380 engines: {node: '>= 0.4'} 1150 1381 1151 - set-function-name@2.0.1: 1152 - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} 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==} 1153 1388 engines: {node: '>= 0.4'} 1154 1389 1155 1390 shebang-command@2.0.0: ··· 1160 1395 resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1161 1396 engines: {node: '>=8'} 1162 1397 1163 - side-channel@1.0.4: 1164 - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} 1398 + side-channel-list@1.0.0: 1399 + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} 1400 + engines: {node: '>= 0.4'} 1165 1401 1166 - signal-exit@3.0.7: 1167 - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 1402 + side-channel-map@1.0.1: 1403 + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} 1404 + engines: {node: '>= 0.4'} 1168 1405 1169 - slash@3.0.0: 1170 - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1171 - engines: {node: '>=8'} 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'} 1172 1417 1173 1418 standalone-electron-types@1.0.0: 1174 1419 resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} 1175 1420 1176 - string.prototype.matchall@4.0.10: 1177 - resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} 1178 - 1179 - string.prototype.trim@1.2.8: 1180 - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} 1421 + string.prototype.matchall@4.0.12: 1422 + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} 1181 1423 engines: {node: '>= 0.4'} 1182 1424 1183 - string.prototype.trimend@1.0.7: 1184 - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} 1425 + string.prototype.repeat@1.0.0: 1426 + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} 1185 1427 1186 - string.prototype.trimstart@1.0.7: 1187 - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} 1428 + string.prototype.trim@1.2.10: 1429 + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} 1430 + engines: {node: '>= 0.4'} 1188 1431 1189 - strip-ansi@6.0.1: 1190 - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1191 - engines: {node: '>=8'} 1432 + string.prototype.trimend@1.0.9: 1433 + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} 1434 + engines: {node: '>= 0.4'} 1192 1435 1193 - strip-final-newline@2.0.0: 1194 - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 1195 - engines: {node: '>=6'} 1436 + string.prototype.trimstart@1.0.8: 1437 + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} 1438 + engines: {node: '>= 0.4'} 1196 1439 1197 - strip-final-newline@3.0.0: 1198 - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} 1199 - engines: {node: '>=12'} 1440 + string_decoder@1.3.0: 1441 + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1200 1442 1201 1443 strip-json-comments@3.1.1: 1202 1444 resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} ··· 1210 1452 resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1211 1453 engines: {node: '>= 0.4'} 1212 1454 1213 - synckit@0.8.6: 1214 - resolution: {integrity: sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==} 1455 + synckit@0.11.1: 1456 + resolution: {integrity: sha512-fWZqNBZNNFp/7mTUy1fSsydhKsAKJ+u90Nk7kOK5Gcq9vObaqLBLjWFDBkyVU9Vvc6Y71VbOevMuGhqv02bT+Q==} 1215 1457 engines: {node: ^14.18.0 || >=16.0.0} 1216 1458 1217 - text-table@0.2.0: 1218 - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 1459 + taze@19.0.4: 1460 + resolution: {integrity: sha512-bviyNotzqcIWpVBCC4QYVb2yupzKyUDGQi2m/8GERdiPaudVMtgAqaE98+x0cDDaByYRMJCyhQWM04ikUL6+kQ==} 1461 + hasBin: true 1219 1462 1220 - titleize@3.0.0: 1221 - resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} 1222 - engines: {node: '>=12'} 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'} 1223 1469 1224 1470 to-regex-range@5.0.1: 1225 1471 resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1226 1472 engines: {node: '>=8.0'} 1227 1473 1228 - ts-api-utils@1.0.3: 1229 - resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} 1230 - engines: {node: '>=16.13.0'} 1474 + ts-api-utils@2.1.0: 1475 + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} 1476 + engines: {node: '>=18.12'} 1231 1477 peerDependencies: 1232 - typescript: '>=4.2.0' 1478 + typescript: '>=4.8.4' 1233 1479 1234 - tslib@2.6.2: 1235 - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} 1480 + tslib@2.8.1: 1481 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1236 1482 1237 1483 type-check@0.4.0: 1238 1484 resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1239 1485 engines: {node: '>= 0.8.0'} 1240 1486 1241 - type-fest@0.20.2: 1242 - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1243 - engines: {node: '>=10'} 1487 + typed-array-buffer@1.0.3: 1488 + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} 1489 + engines: {node: '>= 0.4'} 1244 1490 1245 - typed-array-buffer@1.0.0: 1246 - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} 1491 + typed-array-byte-length@1.0.3: 1492 + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} 1247 1493 engines: {node: '>= 0.4'} 1248 1494 1249 - typed-array-byte-length@1.0.0: 1250 - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} 1495 + typed-array-byte-offset@1.0.4: 1496 + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} 1251 1497 engines: {node: '>= 0.4'} 1252 1498 1253 - typed-array-byte-offset@1.0.0: 1254 - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} 1499 + typed-array-length@1.0.7: 1500 + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} 1255 1501 engines: {node: '>= 0.4'} 1256 1502 1257 - typed-array-length@1.0.4: 1258 - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} 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' 1259 1509 1260 - typescript@5.3.2: 1261 - resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==} 1510 + typescript@5.8.2: 1511 + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} 1262 1512 engines: {node: '>=14.17'} 1263 1513 hasBin: true 1264 1514 1265 - unbox-primitive@1.0.2: 1266 - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} 1515 + ufo@1.5.4: 1516 + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} 1267 1517 1268 - untildify@4.0.0: 1269 - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} 1270 - engines: {node: '>=8'} 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==} 1271 1530 1272 1531 uri-js@4.4.1: 1273 1532 resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1274 1533 1275 - which-boxed-primitive@1.0.2: 1276 - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} 1534 + utilium@1.10.1: 1535 + resolution: {integrity: sha512-GQINDTb/ocyz4acQj3GXAe0wipYxws6L+9ouqaq10KlInTk9DGvW9TJd0pYa/Xu3cppNnZuB4T/sBuSXpcN2ng==} 1277 1536 1278 - which-builtin-type@1.1.3: 1279 - resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} 1537 + which-boxed-primitive@1.1.1: 1538 + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} 1280 1539 engines: {node: '>= 0.4'} 1281 1540 1282 - which-collection@1.0.1: 1283 - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} 1541 + which-builtin-type@1.2.1: 1542 + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} 1543 + engines: {node: '>= 0.4'} 1284 1544 1285 - which-typed-array@1.1.13: 1286 - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} 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==} 1287 1551 engines: {node: '>= 0.4'} 1288 1552 1289 1553 which@2.0.2: ··· 1291 1555 engines: {node: '>= 8'} 1292 1556 hasBin: true 1293 1557 1294 - wrappy@1.0.2: 1295 - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1296 - 1297 - yallist@4.0.0: 1298 - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1558 + yaml@2.7.1: 1559 + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} 1560 + engines: {node: '>= 14'} 1561 + hasBin: true 1299 1562 1300 1563 yocto-queue@0.1.0: 1301 1564 resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1302 1565 engines: {node: '>=10'} 1303 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 + 1304 1585 snapshots: 1305 1586 1306 1587 '@aashutoshrathi/word-wrap@1.2.6': {} 1307 1588 1308 - '@electron/asar@3.2.5': 1589 + '@antfu/ni@24.3.0': 1309 1590 dependencies: 1310 - commander: 5.1.0 1311 - glob: 7.2.3 1312 - minimatch: 3.1.2 1591 + ansis: 3.17.0 1592 + fzf: 0.5.2 1593 + package-manager-detector: 1.1.0 1594 + tinyexec: 1.0.1 1313 1595 1314 1596 '@esbuild/android-arm64@0.19.3': 1315 1597 optional: true ··· 1377 1659 '@esbuild/win32-x64@0.19.3': 1378 1660 optional: true 1379 1661 1380 - '@eslint-community/eslint-utils@4.4.0(eslint@8.55.0)': 1662 + '@eslint-community/eslint-utils@4.5.1(eslint@9.23.0(jiti@2.4.2))': 1381 1663 dependencies: 1382 - eslint: 8.55.0 1664 + eslint: 9.23.0(jiti@2.4.2) 1383 1665 eslint-visitor-keys: 3.4.3 1384 1666 1385 - '@eslint-community/regexpp@4.10.0': {} 1667 + '@eslint-community/regexpp@4.12.1': {} 1386 1668 1387 - '@eslint/eslintrc@2.1.4': 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': 1388 1688 dependencies: 1389 1689 ajv: 6.12.6 1390 - debug: 4.3.4 1391 - espree: 9.6.1 1392 - globals: 13.23.0 1393 - ignore: 5.3.0 1690 + debug: 4.4.0 1691 + espree: 10.3.0 1692 + globals: 14.0.0 1693 + ignore: 5.3.2 1394 1694 import-fresh: 3.3.0 1395 1695 js-yaml: 4.1.0 1396 1696 minimatch: 3.1.2 ··· 1398 1698 transitivePeerDependencies: 1399 1699 - supports-color 1400 1700 1401 - '@eslint/js@8.55.0': {} 1701 + '@eslint/js@9.23.0': {} 1702 + 1703 + '@eslint/object-schema@2.1.6': {} 1402 1704 1403 - '@humanwhocodes/config-array@0.11.13': 1705 + '@eslint/plugin-kit@0.2.8': 1404 1706 dependencies: 1405 - '@humanwhocodes/object-schema': 2.0.1 1406 - debug: 4.3.4 1407 - minimatch: 3.1.2 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) 1408 1732 transitivePeerDependencies: 1733 + - '@types/eslint' 1734 + - prettier 1409 1735 - supports-color 1410 1736 1411 - '@humanwhocodes/module-importer@1.0.1': {} 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 1412 1742 1413 - '@humanwhocodes/object-schema@2.0.1': {} 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': {} 1414 1761 1415 1762 '@nodelib/fs.scandir@2.1.5': 1416 1763 dependencies: ··· 1422 1769 '@nodelib/fs.walk@1.2.8': 1423 1770 dependencies: 1424 1771 '@nodelib/fs.scandir': 2.1.5 1425 - fastq: 1.15.0 1772 + fastq: 1.17.1 1426 1773 1427 - '@pkgr/utils@2.4.2': 1774 + '@pkgr/core@0.2.0': {} 1775 + 1776 + '@quansync/fs@0.1.2': 1428 1777 dependencies: 1429 - cross-spawn: 7.0.3 1430 - fast-glob: 3.3.2 1431 - is-glob: 4.0.3 1432 - open: 9.1.0 1433 - picocolors: 1.0.0 1434 - tslib: 2.6.2 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: 1795 + '@types/estree': 1.0.6 1796 + 1797 + '@types/estree@1.0.6': {} 1798 + 1799 + '@types/estree@1.0.7': 1800 + optional: true 1435 1801 1436 - '@types/fbemitter@2.0.33': {} 1802 + '@types/fbemitter@2.0.35': {} 1437 1803 1438 - '@types/flux@3.1.12': 1804 + '@types/filesystem@0.0.36': 1439 1805 dependencies: 1440 - '@types/fbemitter': 2.0.33 1441 - '@types/react': 18.2.22 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': {} 1442 1818 1443 1819 '@types/json-schema@7.0.15': {} 1820 + 1821 + '@types/lodash@4.17.14': {} 1444 1822 1445 1823 '@types/node@18.17.17': {} 1446 1824 1447 - '@types/prop-types@15.7.6': {} 1825 + '@types/node@22.13.6': 1826 + dependencies: 1827 + undici-types: 6.20.0 1448 1828 1449 - '@types/react@18.2.22': 1829 + '@types/node@22.14.0': 1450 1830 dependencies: 1451 - '@types/prop-types': 15.7.6 1452 - '@types/scheduler': 0.16.3 1453 - csstype: 3.1.2 1831 + undici-types: 6.21.0 1454 1832 1455 - '@types/scheduler@0.16.3': {} 1833 + '@types/platform@1.3.6': {} 1456 1834 1457 - '@types/semver@7.5.6': {} 1835 + '@types/prop-types@15.7.13': {} 1458 1836 1459 - '@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.2)': 1837 + '@types/react@18.3.20': 1460 1838 dependencies: 1461 - '@eslint-community/regexpp': 4.10.0 1462 - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1463 - '@typescript-eslint/scope-manager': 6.13.2 1464 - '@typescript-eslint/type-utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1465 - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1466 - '@typescript-eslint/visitor-keys': 6.13.2 1467 - debug: 4.3.4 1468 - eslint: 8.55.0 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) 1469 1851 graphemer: 1.4.0 1470 - ignore: 5.3.0 1852 + ignore: 5.3.2 1471 1853 natural-compare: 1.4.0 1472 - semver: 7.5.4 1473 - ts-api-utils: 1.0.3(typescript@5.3.2) 1474 - typescript: 5.3.2 1854 + ts-api-utils: 2.1.0(typescript@5.8.2) 1855 + typescript: 5.8.2 1475 1856 transitivePeerDependencies: 1476 1857 - supports-color 1477 1858 1478 - '@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2)': 1859 + '@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 1479 1860 dependencies: 1480 - '@typescript-eslint/scope-manager': 6.13.2 1481 - '@typescript-eslint/types': 6.13.2 1482 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 1483 - '@typescript-eslint/visitor-keys': 6.13.2 1484 - debug: 4.3.4 1485 - eslint: 8.55.0 1486 - typescript: 5.3.2 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 1487 1868 transitivePeerDependencies: 1488 1869 - supports-color 1489 1870 1490 - '@typescript-eslint/scope-manager@6.13.2': 1871 + '@typescript-eslint/scope-manager@8.29.0': 1491 1872 dependencies: 1492 - '@typescript-eslint/types': 6.13.2 1493 - '@typescript-eslint/visitor-keys': 6.13.2 1873 + '@typescript-eslint/types': 8.29.0 1874 + '@typescript-eslint/visitor-keys': 8.29.0 1494 1875 1495 - '@typescript-eslint/type-utils@6.13.2(eslint@8.55.0)(typescript@5.3.2)': 1876 + '@typescript-eslint/type-utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 1496 1877 dependencies: 1497 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 1498 - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1499 - debug: 4.3.4 1500 - eslint: 8.55.0 1501 - ts-api-utils: 1.0.3(typescript@5.3.2) 1502 - typescript: 5.3.2 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 1503 1884 transitivePeerDependencies: 1504 1885 - supports-color 1505 1886 1506 - '@typescript-eslint/types@6.13.2': {} 1887 + '@typescript-eslint/types@8.29.0': {} 1507 1888 1508 - '@typescript-eslint/typescript-estree@6.13.2(typescript@5.3.2)': 1889 + '@typescript-eslint/typescript-estree@8.29.0(typescript@5.8.2)': 1509 1890 dependencies: 1510 - '@typescript-eslint/types': 6.13.2 1511 - '@typescript-eslint/visitor-keys': 6.13.2 1512 - debug: 4.3.4 1513 - globby: 11.1.0 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 1514 1895 is-glob: 4.0.3 1515 - semver: 7.5.4 1516 - ts-api-utils: 1.0.3(typescript@5.3.2) 1517 - typescript: 5.3.2 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 1518 1900 transitivePeerDependencies: 1519 1901 - supports-color 1520 1902 1521 - '@typescript-eslint/utils@6.13.2(eslint@8.55.0)(typescript@5.3.2)': 1903 + '@typescript-eslint/utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 1522 1904 dependencies: 1523 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) 1524 - '@types/json-schema': 7.0.15 1525 - '@types/semver': 7.5.6 1526 - '@typescript-eslint/scope-manager': 6.13.2 1527 - '@typescript-eslint/types': 6.13.2 1528 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 1529 - eslint: 8.55.0 1530 - semver: 7.5.4 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 1531 1911 transitivePeerDependencies: 1532 1912 - supports-color 1533 - - typescript 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 1534 1929 1535 - '@typescript-eslint/visitor-keys@6.13.2': 1930 + '@zenfs/dom@1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1)': 1536 1931 dependencies: 1537 - '@typescript-eslint/types': 6.13.2 1538 - eslint-visitor-keys: 3.4.3 1932 + '@zenfs/core': 2.0.0 1933 + utilium: 1.10.1 1539 1934 1540 - '@ungap/structured-clone@1.2.0': {} 1935 + abort-controller@3.0.0: 1936 + dependencies: 1937 + event-target-shim: 5.0.1 1541 1938 1542 - acorn-jsx@5.3.2(acorn@8.11.2): 1939 + acorn-jsx@5.3.2(acorn@8.14.1): 1543 1940 dependencies: 1544 - acorn: 8.11.2 1941 + acorn: 8.14.1 1545 1942 1546 - acorn@8.11.2: {} 1943 + acorn@8.14.1: {} 1547 1944 1548 1945 ajv@6.12.6: 1549 1946 dependencies: ··· 1552 1949 json-schema-traverse: 0.4.1 1553 1950 uri-js: 4.4.1 1554 1951 1555 - ansi-regex@5.0.1: {} 1556 - 1557 1952 ansi-styles@4.3.0: 1558 1953 dependencies: 1559 1954 color-convert: 2.0.1 1955 + 1956 + ansis@3.17.0: {} 1560 1957 1561 1958 argparse@2.0.1: {} 1562 1959 1563 - array-buffer-byte-length@1.0.0: 1960 + array-buffer-byte-length@1.0.2: 1564 1961 dependencies: 1565 - call-bind: 1.0.5 1566 - is-array-buffer: 3.0.2 1962 + call-bound: 1.0.4 1963 + is-array-buffer: 3.0.5 1567 1964 1568 - array-includes@3.1.7: 1965 + array-includes@3.1.8: 1569 1966 dependencies: 1570 - call-bind: 1.0.5 1967 + call-bind: 1.0.8 1571 1968 define-properties: 1.2.1 1572 - es-abstract: 1.22.3 1573 - get-intrinsic: 1.2.2 1574 - is-string: 1.0.7 1575 - 1576 - array-union@2.1.0: {} 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 1577 1973 1578 - array.prototype.flat@1.3.2: 1974 + array.prototype.findlast@1.2.5: 1579 1975 dependencies: 1580 - call-bind: 1.0.5 1976 + call-bind: 1.0.8 1581 1977 define-properties: 1.2.1 1582 - es-abstract: 1.22.3 1583 - es-shim-unscopables: 1.0.2 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 1584 1982 1585 - array.prototype.flatmap@1.3.2: 1983 + array.prototype.flat@1.3.3: 1586 1984 dependencies: 1587 - call-bind: 1.0.5 1985 + call-bind: 1.0.8 1588 1986 define-properties: 1.2.1 1589 - es-abstract: 1.22.3 1590 - es-shim-unscopables: 1.0.2 1987 + es-abstract: 1.23.9 1988 + es-shim-unscopables: 1.1.0 1591 1989 1592 - array.prototype.tosorted@1.1.2: 1990 + array.prototype.flatmap@1.3.3: 1593 1991 dependencies: 1594 - call-bind: 1.0.5 1992 + call-bind: 1.0.8 1595 1993 define-properties: 1.2.1 1596 - es-abstract: 1.22.3 1597 - es-shim-unscopables: 1.0.2 1598 - get-intrinsic: 1.2.2 1994 + es-abstract: 1.23.9 1995 + es-shim-unscopables: 1.1.0 1599 1996 1600 - arraybuffer.prototype.slice@1.0.2: 1997 + array.prototype.tosorted@1.1.4: 1601 1998 dependencies: 1602 - array-buffer-byte-length: 1.0.0 1603 - call-bind: 1.0.5 1999 + call-bind: 1.0.8 1604 2000 define-properties: 1.2.1 1605 - es-abstract: 1.22.3 1606 - get-intrinsic: 1.2.2 1607 - is-array-buffer: 3.0.2 1608 - is-shared-array-buffer: 1.0.2 2001 + es-abstract: 1.23.9 2002 + es-errors: 1.3.0 2003 + es-shim-unscopables: 1.1.0 1609 2004 1610 - asynciterator.prototype@1.0.0: 2005 + arraybuffer.prototype.slice@1.0.4: 1611 2006 dependencies: 1612 - has-symbols: 1.0.3 1613 - 1614 - available-typed-arrays@1.0.5: {} 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 1615 2014 1616 - balanced-match@1.0.2: {} 2015 + astring@1.9.0: {} 1617 2016 1618 - big-integer@1.6.52: {} 2017 + async-function@1.0.0: {} 1619 2018 1620 - bplist-parser@0.2.0: 2019 + available-typed-arrays@1.0.7: 1621 2020 dependencies: 1622 - big-integer: 1.6.52 2021 + possible-typed-array-names: 1.1.0 2022 + 2023 + balanced-match@1.0.2: {} 2024 + 2025 + base64-js@1.5.1: {} 1623 2026 1624 2027 brace-expansion@1.1.11: 1625 2028 dependencies: 1626 2029 balanced-match: 1.0.2 1627 2030 concat-map: 0.0.1 1628 2031 1629 - braces@3.0.2: 2032 + brace-expansion@2.0.1: 1630 2033 dependencies: 1631 - fill-range: 7.0.1 2034 + balanced-match: 1.0.2 1632 2035 1633 - bundle-name@3.0.0: 2036 + braces@3.0.3: 1634 2037 dependencies: 1635 - run-applescript: 5.0.0 2038 + fill-range: 7.1.1 1636 2039 1637 - call-bind@1.0.5: 2040 + buffer@6.0.3: 1638 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 1639 2050 function-bind: 1.1.2 1640 - get-intrinsic: 1.2.2 1641 - set-function-length: 1.1.1 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 1642 2063 1643 2064 callsites@3.1.0: {} 1644 2065 ··· 1653 2074 1654 2075 color-name@1.1.4: {} 1655 2076 1656 - commander@5.1.0: {} 1657 - 1658 2077 concat-map@0.0.1: {} 1659 2078 1660 - cross-spawn@7.0.3: 2079 + cross-spawn@7.0.6: 1661 2080 dependencies: 1662 2081 path-key: 3.1.1 1663 2082 shebang-command: 2.0.0 1664 2083 which: 2.0.2 1665 2084 1666 - csstype@3.1.2: {} 2085 + csstype@3.1.3: {} 1667 2086 1668 - debug@4.3.4: 2087 + data-view-buffer@1.0.2: 1669 2088 dependencies: 1670 - ms: 2.1.2 2089 + call-bound: 1.0.4 2090 + es-errors: 1.3.0 2091 + is-data-view: 1.0.2 1671 2092 1672 - deep-is@0.1.4: {} 1673 - 1674 - default-browser-id@3.0.0: 2093 + data-view-byte-length@1.0.2: 1675 2094 dependencies: 1676 - bplist-parser: 0.2.0 1677 - untildify: 4.0.0 2095 + call-bound: 1.0.4 2096 + es-errors: 1.3.0 2097 + is-data-view: 1.0.2 1678 2098 1679 - default-browser@4.0.0: 2099 + data-view-byte-offset@1.0.1: 1680 2100 dependencies: 1681 - bundle-name: 3.0.0 1682 - default-browser-id: 3.0.0 1683 - execa: 7.2.0 1684 - titleize: 3.0.0 2101 + call-bound: 1.0.4 2102 + es-errors: 1.3.0 2103 + is-data-view: 1.0.2 1685 2104 1686 - define-data-property@1.1.1: 2105 + debug@4.4.0: 1687 2106 dependencies: 1688 - get-intrinsic: 1.2.2 1689 - gopd: 1.0.1 1690 - has-property-descriptors: 1.0.1 2107 + ms: 2.1.3 1691 2108 1692 - define-lazy-prop@3.0.0: {} 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 1693 2116 1694 2117 define-properties@1.2.1: 1695 2118 dependencies: 1696 - define-data-property: 1.1.1 1697 - has-property-descriptors: 1.0.1 2119 + define-data-property: 1.1.4 2120 + has-property-descriptors: 1.0.2 1698 2121 object-keys: 1.1.1 1699 2122 1700 - dir-glob@3.0.1: 1701 - dependencies: 1702 - path-type: 4.0.0 2123 + defu@6.1.4: {} 2124 + 2125 + destr@2.0.4: {} 1703 2126 1704 2127 doctrine@2.1.0: 1705 2128 dependencies: 1706 2129 esutils: 2.0.3 1707 2130 1708 - doctrine@3.0.0: 2131 + dunder-proto@1.0.1: 1709 2132 dependencies: 1710 - esutils: 2.0.3 2133 + call-bind-apply-helpers: 1.0.2 2134 + es-errors: 1.3.0 2135 + gopd: 1.2.0 1711 2136 1712 - es-abstract@1.22.3: 2137 + es-abstract@1.23.9: 1713 2138 dependencies: 1714 - array-buffer-byte-length: 1.0.0 1715 - arraybuffer.prototype.slice: 1.0.2 1716 - available-typed-arrays: 1.0.5 1717 - call-bind: 1.0.5 1718 - es-set-tostringtag: 2.0.2 1719 - es-to-primitive: 1.2.1 1720 - function.prototype.name: 1.1.6 1721 - get-intrinsic: 1.2.2 1722 - get-symbol-description: 1.0.0 1723 - globalthis: 1.0.3 1724 - gopd: 1.0.1 1725 - has-property-descriptors: 1.0.1 1726 - has-proto: 1.0.1 1727 - has-symbols: 1.0.3 1728 - hasown: 2.0.0 1729 - internal-slot: 1.0.6 1730 - is-array-buffer: 3.0.2 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 1731 2164 is-callable: 1.2.7 1732 - is-negative-zero: 2.0.2 1733 - is-regex: 1.1.4 1734 - is-shared-array-buffer: 1.0.2 1735 - is-string: 1.0.7 1736 - is-typed-array: 1.1.12 1737 - is-weakref: 1.0.2 1738 - object-inspect: 1.13.1 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 1739 2173 object-keys: 1.1.1 1740 - object.assign: 4.1.5 1741 - regexp.prototype.flags: 1.5.1 1742 - safe-array-concat: 1.0.1 1743 - safe-regex-test: 1.0.0 1744 - string.prototype.trim: 1.2.8 1745 - string.prototype.trimend: 1.0.7 1746 - string.prototype.trimstart: 1.0.7 1747 - typed-array-buffer: 1.0.0 1748 - typed-array-byte-length: 1.0.0 1749 - typed-array-byte-offset: 1.0.0 1750 - typed-array-length: 1.0.4 1751 - unbox-primitive: 1.0.2 1752 - which-typed-array: 1.1.13 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 1753 2190 1754 - es-iterator-helpers@1.0.15: 2191 + es-define-property@1.0.1: {} 2192 + 2193 + es-errors@1.3.0: {} 2194 + 2195 + es-iterator-helpers@1.2.1: 1755 2196 dependencies: 1756 - asynciterator.prototype: 1.0.0 1757 - call-bind: 1.0.5 2197 + call-bind: 1.0.8 2198 + call-bound: 1.0.4 1758 2199 define-properties: 1.2.1 1759 - es-abstract: 1.22.3 1760 - es-set-tostringtag: 2.0.2 2200 + es-abstract: 1.23.9 2201 + es-errors: 1.3.0 2202 + es-set-tostringtag: 2.1.0 1761 2203 function-bind: 1.1.2 1762 - get-intrinsic: 1.2.2 1763 - globalthis: 1.0.3 1764 - has-property-descriptors: 1.0.1 1765 - has-proto: 1.0.1 1766 - has-symbols: 1.0.3 1767 - internal-slot: 1.0.6 1768 - iterator.prototype: 1.1.2 1769 - safe-array-concat: 1.0.1 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 1770 2213 1771 - es-set-tostringtag@2.0.2: 2214 + es-object-atoms@1.1.1: 1772 2215 dependencies: 1773 - get-intrinsic: 1.2.2 1774 - has-tostringtag: 1.0.0 1775 - hasown: 2.0.0 2216 + es-errors: 1.3.0 1776 2217 1777 - es-shim-unscopables@1.0.2: 2218 + es-set-tostringtag@2.1.0: 1778 2219 dependencies: 1779 - hasown: 2.0.0 2220 + es-errors: 1.3.0 2221 + get-intrinsic: 1.3.0 2222 + has-tostringtag: 1.0.2 2223 + hasown: 2.0.2 1780 2224 1781 - es-to-primitive@1.2.1: 2225 + es-shim-unscopables@1.1.0: 2226 + dependencies: 2227 + hasown: 2.0.2 2228 + 2229 + es-to-primitive@1.3.0: 1782 2230 dependencies: 1783 2231 is-callable: 1.2.7 1784 - is-date-object: 1.0.5 1785 - is-symbol: 1.0.4 2232 + is-date-object: 1.1.0 2233 + is-symbol: 1.1.1 1786 2234 1787 2235 esbuild-copy-static-files@0.1.0: {} 1788 2236 ··· 1813 2261 1814 2262 escape-string-regexp@4.0.0: {} 1815 2263 1816 - eslint-config-prettier@9.1.0(eslint@8.55.0): 2264 + eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)): 1817 2265 dependencies: 1818 - eslint: 8.55.0 2266 + eslint: 9.23.0(jiti@2.4.2) 1819 2267 1820 - eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.1.0)(eslint@8.55.0)(prettier@3.1.0): 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): 1821 2269 dependencies: 1822 - eslint: 8.55.0 1823 - eslint-config-prettier: 9.1.0(eslint@8.55.0) 2270 + eslint: 9.23.0(jiti@2.4.2) 1824 2271 prettier: 3.1.0 1825 2272 prettier-linter-helpers: 1.0.0 1826 - synckit: 0.8.6 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)) 1827 2277 1828 - eslint-plugin-react@7.33.2(eslint@8.55.0): 2278 + eslint-plugin-react@7.37.5(eslint@9.23.0(jiti@2.4.2)): 1829 2279 dependencies: 1830 - array-includes: 3.1.7 1831 - array.prototype.flatmap: 1.3.2 1832 - array.prototype.tosorted: 1.1.2 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 1833 2284 doctrine: 2.1.0 1834 - es-iterator-helpers: 1.0.15 1835 - eslint: 8.55.0 2285 + es-iterator-helpers: 1.2.1 2286 + eslint: 9.23.0(jiti@2.4.2) 1836 2287 estraverse: 5.3.0 2288 + hasown: 2.0.2 1837 2289 jsx-ast-utils: 3.3.5 1838 2290 minimatch: 3.1.2 1839 - object.entries: 1.1.7 1840 - object.fromentries: 2.0.7 1841 - object.hasown: 1.1.3 1842 - object.values: 1.1.7 2291 + object.entries: 1.1.9 2292 + object.fromentries: 2.0.8 2293 + object.values: 1.2.1 1843 2294 prop-types: 15.8.1 1844 2295 resolve: 2.0.0-next.5 1845 2296 semver: 6.3.1 1846 - string.prototype.matchall: 4.0.10 2297 + string.prototype.matchall: 4.0.12 2298 + string.prototype.repeat: 1.0.0 1847 2299 1848 - eslint-scope@7.2.2: 2300 + eslint-scope@8.3.0: 1849 2301 dependencies: 1850 2302 esrecurse: 4.3.0 1851 2303 estraverse: 5.3.0 1852 2304 1853 2305 eslint-visitor-keys@3.4.3: {} 1854 2306 1855 - eslint@8.55.0: 2307 + eslint-visitor-keys@4.2.0: {} 2308 + 2309 + eslint@9.23.0(jiti@2.4.2): 1856 2310 dependencies: 1857 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) 1858 - '@eslint-community/regexpp': 4.10.0 1859 - '@eslint/eslintrc': 2.1.4 1860 - '@eslint/js': 8.55.0 1861 - '@humanwhocodes/config-array': 0.11.13 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 1862 2320 '@humanwhocodes/module-importer': 1.0.1 1863 - '@nodelib/fs.walk': 1.2.8 1864 - '@ungap/structured-clone': 1.2.0 2321 + '@humanwhocodes/retry': 0.4.2 2322 + '@types/estree': 1.0.6 2323 + '@types/json-schema': 7.0.15 1865 2324 ajv: 6.12.6 1866 2325 chalk: 4.1.2 1867 - cross-spawn: 7.0.3 1868 - debug: 4.3.4 1869 - doctrine: 3.0.0 2326 + cross-spawn: 7.0.6 2327 + debug: 4.4.0 1870 2328 escape-string-regexp: 4.0.0 1871 - eslint-scope: 7.2.2 1872 - eslint-visitor-keys: 3.4.3 1873 - espree: 9.6.1 1874 - esquery: 1.5.0 2329 + eslint-scope: 8.3.0 2330 + eslint-visitor-keys: 4.2.0 2331 + espree: 10.3.0 2332 + esquery: 1.6.0 1875 2333 esutils: 2.0.3 1876 2334 fast-deep-equal: 3.1.3 1877 - file-entry-cache: 6.0.1 2335 + file-entry-cache: 8.0.0 1878 2336 find-up: 5.0.0 1879 2337 glob-parent: 6.0.2 1880 - globals: 13.23.0 1881 - graphemer: 1.4.0 1882 - ignore: 5.3.0 2338 + ignore: 5.3.2 1883 2339 imurmurhash: 0.1.4 1884 2340 is-glob: 4.0.3 1885 - is-path-inside: 3.0.3 1886 - js-yaml: 4.1.0 1887 2341 json-stable-stringify-without-jsonify: 1.0.1 1888 - levn: 0.4.1 1889 2342 lodash.merge: 4.6.2 1890 2343 minimatch: 3.1.2 1891 2344 natural-compare: 1.4.0 1892 2345 optionator: 0.9.3 1893 - strip-ansi: 6.0.1 1894 - text-table: 0.2.0 2346 + optionalDependencies: 2347 + jiti: 2.4.2 1895 2348 transitivePeerDependencies: 1896 2349 - supports-color 1897 2350 1898 - espree@9.6.1: 2351 + espree@10.3.0: 1899 2352 dependencies: 1900 - acorn: 8.11.2 1901 - acorn-jsx: 5.3.2(acorn@8.11.2) 1902 - eslint-visitor-keys: 3.4.3 2353 + acorn: 8.14.1 2354 + acorn-jsx: 5.3.2(acorn@8.14.1) 2355 + eslint-visitor-keys: 4.2.0 1903 2356 1904 - esquery@1.5.0: 2357 + esquery@1.6.0: 1905 2358 dependencies: 1906 2359 estraverse: 5.3.0 1907 2360 ··· 1911 2364 1912 2365 estraverse@5.3.0: {} 1913 2366 2367 + estree-toolkit@1.7.8: 2368 + dependencies: 2369 + '@types/estree': 1.0.6 2370 + '@types/estree-jsx': 1.0.5 2371 + 1914 2372 esutils@2.0.3: {} 1915 2373 1916 - execa@5.1.1: 1917 - dependencies: 1918 - cross-spawn: 7.0.3 1919 - get-stream: 6.0.1 1920 - human-signals: 2.1.0 1921 - is-stream: 2.0.1 1922 - merge-stream: 2.0.0 1923 - npm-run-path: 4.0.1 1924 - onetime: 5.1.2 1925 - signal-exit: 3.0.7 1926 - strip-final-newline: 2.0.0 2374 + event-target-shim@5.0.1: {} 1927 2375 1928 - execa@7.2.0: 1929 - dependencies: 1930 - cross-spawn: 7.0.3 1931 - get-stream: 6.0.1 1932 - human-signals: 4.3.1 1933 - is-stream: 3.0.0 1934 - merge-stream: 2.0.0 1935 - npm-run-path: 5.1.0 1936 - onetime: 6.0.0 1937 - signal-exit: 3.0.7 1938 - strip-final-newline: 3.0.0 2376 + eventemitter3@5.0.1: {} 2377 + 2378 + events@3.3.0: {} 1939 2379 1940 2380 fast-deep-equal@3.1.3: {} 1941 2381 ··· 1947 2387 '@nodelib/fs.walk': 1.2.8 1948 2388 glob-parent: 5.1.2 1949 2389 merge2: 1.4.1 1950 - micromatch: 4.0.5 2390 + micromatch: 4.0.8 1951 2391 1952 2392 fast-json-stable-stringify@2.1.0: {} 1953 2393 1954 2394 fast-levenshtein@2.0.6: {} 1955 2395 1956 - fastq@1.15.0: 2396 + fastq@1.17.1: 1957 2397 dependencies: 1958 2398 reusify: 1.0.4 1959 2399 1960 - file-entry-cache@6.0.1: 2400 + fdir@6.4.3(picomatch@4.0.2): 2401 + optionalDependencies: 2402 + picomatch: 4.0.2 2403 + 2404 + file-entry-cache@8.0.0: 1961 2405 dependencies: 1962 - flat-cache: 3.2.0 2406 + flat-cache: 4.0.1 1963 2407 1964 - fill-range@7.0.1: 2408 + fill-range@7.1.1: 1965 2409 dependencies: 1966 2410 to-regex-range: 5.0.1 2411 + 2412 + find-up-simple@1.0.1: {} 1967 2413 1968 2414 find-up@5.0.0: 1969 2415 dependencies: 1970 2416 locate-path: 6.0.0 1971 2417 path-exists: 4.0.0 1972 2418 1973 - flat-cache@3.2.0: 2419 + flat-cache@4.0.1: 1974 2420 dependencies: 1975 2421 flatted: 3.2.9 1976 2422 keyv: 4.5.4 1977 - rimraf: 3.0.2 1978 2423 1979 2424 flatted@3.2.9: {} 1980 2425 1981 - for-each@0.3.3: 2426 + for-each@0.3.5: 1982 2427 dependencies: 1983 2428 is-callable: 1.2.7 1984 2429 1985 - fs.realpath@1.0.0: {} 1986 - 1987 2430 function-bind@1.1.2: {} 1988 2431 1989 - function.prototype.name@1.1.6: 2432 + function.prototype.name@1.1.8: 1990 2433 dependencies: 1991 - call-bind: 1.0.5 2434 + call-bind: 1.0.8 2435 + call-bound: 1.0.4 1992 2436 define-properties: 1.2.1 1993 - es-abstract: 1.22.3 1994 2437 functions-have-names: 1.2.3 2438 + hasown: 2.0.2 2439 + is-callable: 1.2.7 1995 2440 1996 2441 functions-have-names@1.2.3: {} 1997 2442 1998 - get-intrinsic@1.2.2: 2443 + fzf@0.5.2: {} 2444 + 2445 + get-intrinsic@1.3.0: 1999 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 2000 2451 function-bind: 1.1.2 2001 - has-proto: 1.0.1 2002 - has-symbols: 1.0.3 2003 - hasown: 2.0.0 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 2004 2457 2005 - get-stream@6.0.1: {} 2458 + get-proto@1.0.1: 2459 + dependencies: 2460 + dunder-proto: 1.0.1 2461 + es-object-atoms: 1.1.1 2006 2462 2007 - get-symbol-description@1.0.0: 2463 + get-symbol-description@1.1.0: 2008 2464 dependencies: 2009 - call-bind: 1.0.5 2010 - get-intrinsic: 1.2.2 2465 + call-bound: 1.0.4 2466 + es-errors: 1.3.0 2467 + get-intrinsic: 1.3.0 2011 2468 2012 2469 glob-parent@5.1.2: 2013 2470 dependencies: ··· 2017 2474 dependencies: 2018 2475 is-glob: 4.0.3 2019 2476 2020 - glob@7.2.3: 2021 - dependencies: 2022 - fs.realpath: 1.0.0 2023 - inflight: 1.0.6 2024 - inherits: 2.0.4 2025 - minimatch: 3.1.2 2026 - once: 1.4.0 2027 - path-is-absolute: 1.0.1 2477 + globals@14.0.0: {} 2028 2478 2029 - globals@13.23.0: 2030 - dependencies: 2031 - type-fest: 0.20.2 2032 - 2033 - globalthis@1.0.3: 2479 + globalthis@1.0.4: 2034 2480 dependencies: 2035 2481 define-properties: 1.2.1 2482 + gopd: 1.2.0 2036 2483 2037 - globby@11.1.0: 2038 - dependencies: 2039 - array-union: 2.1.0 2040 - dir-glob: 3.0.1 2041 - fast-glob: 3.3.2 2042 - ignore: 5.3.0 2043 - merge2: 1.4.1 2044 - slash: 3.0.0 2045 - 2046 - gopd@1.0.1: 2047 - dependencies: 2048 - get-intrinsic: 1.2.2 2484 + gopd@1.2.0: {} 2049 2485 2050 2486 graphemer@1.4.0: {} 2051 2487 2052 - has-bigints@1.0.2: {} 2488 + has-bigints@1.1.0: {} 2053 2489 2054 2490 has-flag@4.0.0: {} 2055 2491 2056 - has-property-descriptors@1.0.1: 2492 + has-property-descriptors@1.0.2: 2057 2493 dependencies: 2058 - get-intrinsic: 1.2.2 2494 + es-define-property: 1.0.1 2059 2495 2060 - has-proto@1.0.1: {} 2496 + has-proto@1.2.0: 2497 + dependencies: 2498 + dunder-proto: 1.0.1 2061 2499 2062 - has-symbols@1.0.3: {} 2500 + has-symbols@1.1.0: {} 2063 2501 2064 - has-tostringtag@1.0.0: 2502 + has-tostringtag@1.0.2: 2065 2503 dependencies: 2066 - has-symbols: 1.0.3 2504 + has-symbols: 1.1.0 2067 2505 2068 - hasown@2.0.0: 2506 + hasown@2.0.2: 2069 2507 dependencies: 2070 2508 function-bind: 1.1.2 2071 2509 2072 - human-signals@2.1.0: {} 2073 - 2074 - human-signals@4.3.1: {} 2075 - 2076 2510 husky@8.0.3: {} 2077 2511 2078 - ignore@5.3.0: {} 2512 + ieee754@1.2.1: {} 2513 + 2514 + ignore@5.3.2: {} 2079 2515 2080 2516 import-fresh@3.3.0: 2081 2517 dependencies: ··· 2084 2520 2085 2521 imurmurhash@0.1.4: {} 2086 2522 2087 - inflight@1.0.6: 2523 + internal-slot@1.1.0: 2088 2524 dependencies: 2089 - once: 1.4.0 2090 - wrappy: 1.0.2 2525 + es-errors: 1.3.0 2526 + hasown: 2.0.2 2527 + side-channel: 1.1.0 2091 2528 2092 - inherits@2.0.4: {} 2093 - 2094 - internal-slot@1.0.6: 2529 + is-array-buffer@3.0.5: 2095 2530 dependencies: 2096 - get-intrinsic: 1.2.2 2097 - hasown: 2.0.0 2098 - side-channel: 1.0.4 2531 + call-bind: 1.0.8 2532 + call-bound: 1.0.4 2533 + get-intrinsic: 1.3.0 2099 2534 2100 - is-array-buffer@3.0.2: 2535 + is-async-function@2.1.1: 2101 2536 dependencies: 2102 - call-bind: 1.0.5 2103 - get-intrinsic: 1.2.2 2104 - is-typed-array: 1.1.12 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 2105 2542 2106 - is-async-function@2.0.0: 2543 + is-bigint@1.1.0: 2107 2544 dependencies: 2108 - has-tostringtag: 1.0.0 2545 + has-bigints: 1.1.0 2109 2546 2110 - is-bigint@1.0.4: 2547 + is-boolean-object@1.2.2: 2111 2548 dependencies: 2112 - has-bigints: 1.0.2 2113 - 2114 - is-boolean-object@1.1.2: 2115 - dependencies: 2116 - call-bind: 1.0.5 2117 - has-tostringtag: 1.0.0 2549 + call-bound: 1.0.4 2550 + has-tostringtag: 1.0.2 2118 2551 2119 2552 is-callable@1.2.7: {} 2120 2553 2121 - is-core-module@2.13.1: 2554 + is-core-module@2.16.1: 2122 2555 dependencies: 2123 - hasown: 2.0.0 2556 + hasown: 2.0.2 2124 2557 2125 - is-date-object@1.0.5: 2558 + is-data-view@1.0.2: 2126 2559 dependencies: 2127 - has-tostringtag: 1.0.0 2560 + call-bound: 1.0.4 2561 + get-intrinsic: 1.3.0 2562 + is-typed-array: 1.1.15 2128 2563 2129 - is-docker@2.2.1: {} 2130 - 2131 - is-docker@3.0.0: {} 2564 + is-date-object@1.1.0: 2565 + dependencies: 2566 + call-bound: 1.0.4 2567 + has-tostringtag: 1.0.2 2132 2568 2133 2569 is-extglob@2.1.1: {} 2134 2570 2135 - is-finalizationregistry@1.0.2: 2571 + is-finalizationregistry@1.1.1: 2136 2572 dependencies: 2137 - call-bind: 1.0.5 2573 + call-bound: 1.0.4 2138 2574 2139 - is-generator-function@1.0.10: 2575 + is-generator-function@1.1.0: 2140 2576 dependencies: 2141 - has-tostringtag: 1.0.0 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 2142 2581 2143 2582 is-glob@4.0.3: 2144 2583 dependencies: 2145 2584 is-extglob: 2.1.1 2146 2585 2147 - is-inside-container@1.0.0: 2148 - dependencies: 2149 - is-docker: 3.0.0 2150 - 2151 - is-map@2.0.2: {} 2152 - 2153 - is-negative-zero@2.0.2: {} 2586 + is-map@2.0.3: {} 2154 2587 2155 - is-number-object@1.0.7: 2588 + is-number-object@1.1.1: 2156 2589 dependencies: 2157 - has-tostringtag: 1.0.0 2590 + call-bound: 1.0.4 2591 + has-tostringtag: 1.0.2 2158 2592 2159 2593 is-number@7.0.0: {} 2160 2594 2161 - is-path-inside@3.0.3: {} 2162 - 2163 - is-regex@1.1.4: 2595 + is-regex@1.2.1: 2164 2596 dependencies: 2165 - call-bind: 1.0.5 2166 - has-tostringtag: 1.0.0 2597 + call-bound: 1.0.4 2598 + gopd: 1.2.0 2599 + has-tostringtag: 1.0.2 2600 + hasown: 2.0.2 2167 2601 2168 - is-set@2.0.2: {} 2602 + is-set@2.0.3: {} 2169 2603 2170 - is-shared-array-buffer@1.0.2: 2604 + is-shared-array-buffer@1.0.4: 2171 2605 dependencies: 2172 - call-bind: 1.0.5 2173 - 2174 - is-stream@2.0.1: {} 2175 - 2176 - is-stream@3.0.0: {} 2606 + call-bound: 1.0.4 2177 2607 2178 - is-string@1.0.7: 2608 + is-string@1.1.1: 2179 2609 dependencies: 2180 - has-tostringtag: 1.0.0 2610 + call-bound: 1.0.4 2611 + has-tostringtag: 1.0.2 2181 2612 2182 - is-symbol@1.0.4: 2613 + is-symbol@1.1.1: 2183 2614 dependencies: 2184 - has-symbols: 1.0.3 2615 + call-bound: 1.0.4 2616 + has-symbols: 1.1.0 2617 + safe-regex-test: 1.1.0 2185 2618 2186 - is-typed-array@1.1.12: 2619 + is-typed-array@1.1.15: 2187 2620 dependencies: 2188 - which-typed-array: 1.1.13 2621 + which-typed-array: 1.1.19 2189 2622 2190 - is-weakmap@2.0.1: {} 2623 + is-weakmap@2.0.2: {} 2191 2624 2192 - is-weakref@1.0.2: 2625 + is-weakref@1.1.1: 2193 2626 dependencies: 2194 - call-bind: 1.0.5 2627 + call-bound: 1.0.4 2195 2628 2196 - is-weakset@2.0.2: 2629 + is-weakset@2.0.4: 2197 2630 dependencies: 2198 - call-bind: 1.0.5 2199 - get-intrinsic: 1.2.2 2200 - 2201 - is-wsl@2.2.0: 2202 - dependencies: 2203 - is-docker: 2.2.1 2631 + call-bound: 1.0.4 2632 + get-intrinsic: 1.3.0 2204 2633 2205 2634 isarray@2.0.5: {} 2206 2635 2207 2636 isexe@2.0.0: {} 2208 2637 2209 - iterator.prototype@1.1.2: 2638 + iterator.prototype@1.1.5: 2210 2639 dependencies: 2211 - define-properties: 1.2.1 2212 - get-intrinsic: 1.2.2 2213 - has-symbols: 1.0.3 2214 - reflect.getprototypeof: 1.0.4 2215 - set-function-name: 2.0.1 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: {} 2216 2648 2217 2649 js-tokens@4.0.0: {} 2218 2650 ··· 2228 2660 2229 2661 jsx-ast-utils@3.3.5: 2230 2662 dependencies: 2231 - array-includes: 3.1.7 2232 - array.prototype.flat: 1.3.2 2233 - object.assign: 4.1.5 2234 - object.values: 1.1.7 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 2235 2667 2236 2668 keyv@4.5.4: 2237 2669 dependencies: ··· 2252 2684 dependencies: 2253 2685 js-tokens: 4.0.0 2254 2686 2255 - lru-cache@6.0.0: 2256 - dependencies: 2257 - yallist: 4.0.0 2258 - 2259 - merge-stream@2.0.0: {} 2687 + math-intrinsics@1.1.0: {} 2260 2688 2261 2689 merge2@1.4.1: {} 2262 2690 2263 - micromatch@4.0.5: 2691 + meriyah@6.0.1: {} 2692 + 2693 + microdiff@1.5.0: {} 2694 + 2695 + micromatch@4.0.8: 2264 2696 dependencies: 2265 - braces: 3.0.2 2697 + braces: 3.0.3 2266 2698 picomatch: 2.3.1 2267 2699 2268 - mimic-fn@2.1.0: {} 2269 - 2270 - mimic-fn@4.0.0: {} 2700 + mimic-function@5.0.1: {} 2271 2701 2272 2702 minimatch@3.1.2: 2273 2703 dependencies: 2274 2704 brace-expansion: 1.1.11 2275 2705 2276 - ms@2.1.2: {} 2706 + minimatch@9.0.5: 2707 + dependencies: 2708 + brace-expansion: 2.0.1 2277 2709 2278 - natural-compare@1.4.0: {} 2710 + ms@2.1.3: {} 2279 2711 2280 - npm-run-path@4.0.1: 2281 - dependencies: 2282 - path-key: 3.1.1 2712 + nanotar@0.1.1: {} 2283 2713 2284 - npm-run-path@5.1.0: 2285 - dependencies: 2286 - path-key: 4.0.0 2714 + natural-compare@1.4.0: {} 2715 + 2716 + node-fetch-native@1.6.6: {} 2287 2717 2288 2718 object-assign@4.1.1: {} 2289 2719 2290 - object-inspect@1.13.1: {} 2720 + object-inspect@1.13.4: {} 2291 2721 2292 2722 object-keys@1.1.1: {} 2293 2723 2294 - object.assign@4.1.5: 2724 + object.assign@4.1.7: 2295 2725 dependencies: 2296 - call-bind: 1.0.5 2726 + call-bind: 1.0.8 2727 + call-bound: 1.0.4 2297 2728 define-properties: 1.2.1 2298 - has-symbols: 1.0.3 2729 + es-object-atoms: 1.1.1 2730 + has-symbols: 1.1.0 2299 2731 object-keys: 1.1.1 2300 2732 2301 - object.entries@1.1.7: 2302 - dependencies: 2303 - call-bind: 1.0.5 2304 - define-properties: 1.2.1 2305 - es-abstract: 1.22.3 2306 - 2307 - object.fromentries@2.0.7: 2733 + object.entries@1.1.9: 2308 2734 dependencies: 2309 - call-bind: 1.0.5 2735 + call-bind: 1.0.8 2736 + call-bound: 1.0.4 2310 2737 define-properties: 1.2.1 2311 - es-abstract: 1.22.3 2738 + es-object-atoms: 1.1.1 2312 2739 2313 - object.hasown@1.1.3: 2740 + object.fromentries@2.0.8: 2314 2741 dependencies: 2742 + call-bind: 1.0.8 2315 2743 define-properties: 1.2.1 2316 - es-abstract: 1.22.3 2744 + es-abstract: 1.23.9 2745 + es-object-atoms: 1.1.1 2317 2746 2318 - object.values@1.1.7: 2747 + object.values@1.2.1: 2319 2748 dependencies: 2320 - call-bind: 1.0.5 2749 + call-bind: 1.0.8 2750 + call-bound: 1.0.4 2321 2751 define-properties: 1.2.1 2322 - es-abstract: 1.22.3 2323 - 2324 - once@1.4.0: 2325 - dependencies: 2326 - wrappy: 1.0.2 2327 - 2328 - onetime@5.1.2: 2329 - dependencies: 2330 - mimic-fn: 2.1.0 2752 + es-object-atoms: 1.1.1 2331 2753 2332 - onetime@6.0.0: 2754 + ofetch@1.4.1: 2333 2755 dependencies: 2334 - mimic-fn: 4.0.0 2756 + destr: 2.0.4 2757 + node-fetch-native: 1.6.6 2758 + ufo: 1.5.4 2335 2759 2336 - open@9.1.0: 2760 + onetime@7.0.0: 2337 2761 dependencies: 2338 - default-browser: 4.0.0 2339 - define-lazy-prop: 3.0.0 2340 - is-inside-container: 1.0.0 2341 - is-wsl: 2.2.0 2762 + mimic-function: 5.0.1 2342 2763 2343 2764 optionator@0.9.3: 2344 2765 dependencies: ··· 2349 2770 prelude-ls: 1.2.1 2350 2771 type-check: 0.4.0 2351 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 + 2352 2779 p-limit@3.1.0: 2353 2780 dependencies: 2354 2781 yocto-queue: 0.1.0 ··· 2356 2783 p-locate@5.0.0: 2357 2784 dependencies: 2358 2785 p-limit: 3.1.0 2786 + 2787 + package-manager-detector@1.1.0: {} 2359 2788 2360 2789 parent-module@1.0.1: 2361 2790 dependencies: ··· 2363 2792 2364 2793 path-exists@4.0.0: {} 2365 2794 2366 - path-is-absolute@1.0.1: {} 2367 - 2368 2795 path-key@3.1.1: {} 2369 2796 2370 - path-key@4.0.0: {} 2371 - 2372 2797 path-parse@1.0.7: {} 2373 2798 2374 - path-type@4.0.0: {} 2375 - 2376 - picocolors@1.0.0: {} 2799 + pathe@2.0.3: {} 2377 2800 2378 2801 picomatch@2.3.1: {} 2379 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 + 2380 2811 prelude-ls@1.2.1: {} 2381 2812 2382 2813 prettier-linter-helpers@1.0.0: ··· 2385 2816 2386 2817 prettier@3.1.0: {} 2387 2818 2819 + process@0.11.10: {} 2820 + 2388 2821 prop-types@15.8.1: 2389 2822 dependencies: 2390 2823 loose-envify: 1.4.0 ··· 2393 2826 2394 2827 punycode@2.3.1: {} 2395 2828 2829 + quansync@0.2.10: {} 2830 + 2396 2831 queue-microtask@1.2.3: {} 2397 2832 2398 2833 react-is@16.13.1: {} 2399 2834 2400 - reflect.getprototypeof@1.0.4: 2835 + readable-stream@4.5.2: 2401 2836 dependencies: 2402 - call-bind: 1.0.5 2837 + abort-controller: 3.0.0 2838 + buffer: 6.0.3 2839 + events: 3.3.0 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 2403 2846 define-properties: 1.2.1 2404 - es-abstract: 1.22.3 2405 - get-intrinsic: 1.2.2 2406 - globalthis: 1.0.3 2407 - which-builtin-type: 1.1.3 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 2408 2853 2409 - regexp.prototype.flags@1.5.1: 2854 + regexp.prototype.flags@1.5.4: 2410 2855 dependencies: 2411 - call-bind: 1.0.5 2856 + call-bind: 1.0.8 2412 2857 define-properties: 1.2.1 2413 - set-function-name: 2.0.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 2414 2862 2415 2863 resolve-from@4.0.0: {} 2416 2864 2417 2865 resolve@2.0.0-next.5: 2418 2866 dependencies: 2419 - is-core-module: 2.13.1 2867 + is-core-module: 2.16.1 2420 2868 path-parse: 1.0.7 2421 2869 supports-preserve-symlinks-flag: 1.0.0 2422 2870 2423 - reusify@1.0.4: {} 2424 - 2425 - rimraf@3.0.2: 2871 + restore-cursor@5.1.0: 2426 2872 dependencies: 2427 - glob: 7.2.3 2873 + onetime: 7.0.0 2874 + signal-exit: 4.1.0 2428 2875 2429 - run-applescript@5.0.0: 2430 - dependencies: 2431 - execa: 5.1.1 2876 + reusify@1.0.4: {} 2432 2877 2433 2878 run-parallel@1.2.0: 2434 2879 dependencies: 2435 2880 queue-microtask: 1.2.3 2436 2881 2437 - safe-array-concat@1.0.1: 2882 + safe-array-concat@1.1.3: 2438 2883 dependencies: 2439 - call-bind: 1.0.5 2440 - get-intrinsic: 1.2.2 2441 - has-symbols: 1.0.3 2884 + call-bind: 1.0.8 2885 + call-bound: 1.0.4 2886 + get-intrinsic: 1.3.0 2887 + has-symbols: 1.1.0 2442 2888 isarray: 2.0.5 2443 2889 2444 - safe-regex-test@1.0.0: 2890 + safe-buffer@5.2.1: {} 2891 + 2892 + safe-push-apply@1.0.0: 2445 2893 dependencies: 2446 - call-bind: 1.0.5 2447 - get-intrinsic: 1.2.2 2448 - is-regex: 1.1.4 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 2449 2902 2450 2903 semver@6.3.1: {} 2451 2904 2452 - semver@7.5.4: 2453 - dependencies: 2454 - lru-cache: 6.0.0 2905 + semver@7.7.1: {} 2455 2906 2456 - set-function-length@1.1.1: 2907 + set-function-length@1.2.2: 2457 2908 dependencies: 2458 - define-data-property: 1.1.1 2459 - get-intrinsic: 1.2.2 2460 - gopd: 1.0.1 2461 - has-property-descriptors: 1.0.1 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 2462 2915 2463 - set-function-name@2.0.1: 2916 + set-function-name@2.0.2: 2464 2917 dependencies: 2465 - define-data-property: 1.1.1 2918 + define-data-property: 1.1.4 2919 + es-errors: 1.3.0 2466 2920 functions-have-names: 1.2.3 2467 - has-property-descriptors: 1.0.1 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 2468 2928 2469 2929 shebang-command@2.0.0: 2470 2930 dependencies: ··· 2472 2932 2473 2933 shebang-regex@3.0.0: {} 2474 2934 2475 - side-channel@1.0.4: 2935 + side-channel-list@1.0.0: 2476 2936 dependencies: 2477 - call-bind: 1.0.5 2478 - get-intrinsic: 1.2.2 2479 - object-inspect: 1.13.1 2937 + es-errors: 1.3.0 2938 + object-inspect: 1.13.4 2480 2939 2481 - signal-exit@3.0.7: {} 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 2482 2946 2483 - slash@3.0.0: {} 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: {} 2484 2964 2485 2965 standalone-electron-types@1.0.0: 2486 2966 dependencies: 2487 2967 '@types/node': 18.17.17 2488 2968 2489 - string.prototype.matchall@4.0.10: 2969 + string.prototype.matchall@4.0.12: 2490 2970 dependencies: 2491 - call-bind: 1.0.5 2971 + call-bind: 1.0.8 2972 + call-bound: 1.0.4 2492 2973 define-properties: 1.2.1 2493 - es-abstract: 1.22.3 2494 - get-intrinsic: 1.2.2 2495 - has-symbols: 1.0.3 2496 - internal-slot: 1.0.6 2497 - regexp.prototype.flags: 1.5.1 2498 - set-function-name: 2.0.1 2499 - side-channel: 1.0.4 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 2500 2984 2501 - string.prototype.trim@1.2.8: 2985 + string.prototype.repeat@1.0.0: 2502 2986 dependencies: 2503 - call-bind: 1.0.5 2504 2987 define-properties: 1.2.1 2505 - es-abstract: 1.22.3 2988 + es-abstract: 1.23.9 2506 2989 2507 - string.prototype.trimend@1.0.7: 2990 + string.prototype.trim@1.2.10: 2508 2991 dependencies: 2509 - call-bind: 1.0.5 2992 + call-bind: 1.0.8 2993 + call-bound: 1.0.4 2994 + define-data-property: 1.1.4 2510 2995 define-properties: 1.2.1 2511 - es-abstract: 1.22.3 2996 + es-abstract: 1.23.9 2997 + es-object-atoms: 1.1.1 2998 + has-property-descriptors: 1.0.2 2512 2999 2513 - string.prototype.trimstart@1.0.7: 3000 + string.prototype.trimend@1.0.9: 2514 3001 dependencies: 2515 - call-bind: 1.0.5 3002 + call-bind: 1.0.8 3003 + call-bound: 1.0.4 2516 3004 define-properties: 1.2.1 2517 - es-abstract: 1.22.3 3005 + es-object-atoms: 1.1.1 2518 3006 2519 - strip-ansi@6.0.1: 3007 + string.prototype.trimstart@1.0.8: 2520 3008 dependencies: 2521 - ansi-regex: 5.0.1 2522 - 2523 - strip-final-newline@2.0.0: {} 3009 + call-bind: 1.0.8 3010 + define-properties: 1.2.1 3011 + es-object-atoms: 1.1.1 2524 3012 2525 - strip-final-newline@3.0.0: {} 3013 + string_decoder@1.3.0: 3014 + dependencies: 3015 + safe-buffer: 5.2.1 2526 3016 2527 3017 strip-json-comments@3.1.1: {} 2528 3018 ··· 2532 3022 2533 3023 supports-preserve-symlinks-flag@1.0.0: {} 2534 3024 2535 - synckit@0.8.6: 3025 + synckit@0.11.1: 2536 3026 dependencies: 2537 - '@pkgr/utils': 2.4.2 2538 - tslib: 2.6.2 3027 + '@pkgr/core': 0.2.0 3028 + tslib: 2.8.1 2539 3029 2540 - text-table@0.2.0: {} 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 2541 3044 2542 - titleize@3.0.0: {} 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 2543 3051 2544 3052 to-regex-range@5.0.1: 2545 3053 dependencies: 2546 3054 is-number: 7.0.0 2547 3055 2548 - ts-api-utils@1.0.3(typescript@5.3.2): 3056 + ts-api-utils@2.1.0(typescript@5.8.2): 2549 3057 dependencies: 2550 - typescript: 5.3.2 3058 + typescript: 5.8.2 2551 3059 2552 - tslib@2.6.2: {} 3060 + tslib@2.8.1: {} 2553 3061 2554 3062 type-check@0.4.0: 2555 3063 dependencies: 2556 3064 prelude-ls: 1.2.1 2557 3065 2558 - type-fest@0.20.2: {} 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 2559 3071 2560 - typed-array-buffer@1.0.0: 3072 + typed-array-byte-length@1.0.3: 2561 3073 dependencies: 2562 - call-bind: 1.0.5 2563 - get-intrinsic: 1.2.2 2564 - is-typed-array: 1.1.12 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 2565 3079 2566 - typed-array-byte-length@1.0.0: 3080 + typed-array-byte-offset@1.0.4: 2567 3081 dependencies: 2568 - call-bind: 1.0.5 2569 - for-each: 0.3.3 2570 - has-proto: 1.0.1 2571 - is-typed-array: 1.1.12 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 2572 3089 2573 - typed-array-byte-offset@1.0.0: 3090 + typed-array-length@1.0.7: 2574 3091 dependencies: 2575 - available-typed-arrays: 1.0.5 2576 - call-bind: 1.0.5 2577 - for-each: 0.3.3 2578 - has-proto: 1.0.1 2579 - is-typed-array: 1.1.12 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 2580 3098 2581 - typed-array-length@1.0.4: 3099 + typescript-eslint@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2): 2582 3100 dependencies: 2583 - call-bind: 1.0.5 2584 - for-each: 0.3.3 2585 - is-typed-array: 1.1.12 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 2586 3108 2587 - typescript@5.3.2: {} 3109 + typescript@5.8.2: {} 2588 3110 2589 - unbox-primitive@1.0.2: 3111 + ufo@1.5.4: {} 3112 + 3113 + unbox-primitive@1.1.0: 2590 3114 dependencies: 2591 - call-bind: 1.0.5 2592 - has-bigints: 1.0.2 2593 - has-symbols: 1.0.3 2594 - which-boxed-primitive: 1.0.2 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 2595 3119 2596 - untildify@4.0.0: {} 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: {} 2597 3130 2598 3131 uri-js@4.4.1: 2599 3132 dependencies: 2600 3133 punycode: 2.3.1 2601 3134 2602 - which-boxed-primitive@1.0.2: 3135 + utilium@1.10.1: 2603 3136 dependencies: 2604 - is-bigint: 1.0.4 2605 - is-boolean-object: 1.1.2 2606 - is-number-object: 1.0.7 2607 - is-string: 1.0.7 2608 - is-symbol: 1.0.4 3137 + eventemitter3: 5.0.1 3138 + optionalDependencies: 3139 + '@xterm/xterm': 5.5.0 2609 3140 2610 - which-builtin-type@1.1.3: 3141 + which-boxed-primitive@1.1.1: 2611 3142 dependencies: 2612 - function.prototype.name: 1.1.6 2613 - has-tostringtag: 1.0.0 2614 - is-async-function: 2.0.0 2615 - is-date-object: 1.0.5 2616 - is-finalizationregistry: 1.0.2 2617 - is-generator-function: 1.0.10 2618 - is-regex: 1.1.4 2619 - is-weakref: 1.0.2 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 2620 3160 isarray: 2.0.5 2621 - which-boxed-primitive: 1.0.2 2622 - which-collection: 1.0.1 2623 - which-typed-array: 1.1.13 3161 + which-boxed-primitive: 1.1.1 3162 + which-collection: 1.0.2 3163 + which-typed-array: 1.1.19 2624 3164 2625 - which-collection@1.0.1: 3165 + which-collection@1.0.2: 2626 3166 dependencies: 2627 - is-map: 2.0.2 2628 - is-set: 2.0.2 2629 - is-weakmap: 2.0.1 2630 - is-weakset: 2.0.2 3167 + is-map: 2.0.3 3168 + is-set: 2.0.3 3169 + is-weakmap: 2.0.2 3170 + is-weakset: 2.0.4 2631 3171 2632 - which-typed-array@1.1.13: 3172 + which-typed-array@1.1.19: 2633 3173 dependencies: 2634 - available-typed-arrays: 1.0.5 2635 - call-bind: 1.0.5 2636 - for-each: 0.3.3 2637 - gopd: 1.0.1 2638 - has-tostringtag: 1.0.0 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 2639 3181 2640 3182 which@2.0.2: 2641 3183 dependencies: 2642 3184 isexe: 2.0.0 2643 3185 2644 - wrappy@1.0.2: {} 3186 + yaml@2.7.1: {} 2645 3187 2646 - yallist@4.0.0: {} 3188 + yocto-queue@0.1.0: {} 2647 3189 2648 - yocto-queue@0.1.0: {} 3190 + zustand@5.0.3(@types/react@18.3.20): 3191 + optionalDependencies: 3192 + '@types/react': 18.3.20
+31 -1
pnpm-workspace.yaml
··· 1 1 packages: 2 - - "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/
+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 + }
+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 -13
tsconfig.json
··· 1 1 { 2 + "extends": ["./tsconfig.base.json"], 2 3 "compilerOptions": { 3 - "target": "es2016", 4 - "module": "es6", 5 - "esModuleInterop": true, 6 - "forceConsistentCasingInFileNames": true, 7 - "strict": true, 8 - "moduleResolution": "bundler", 9 4 "baseUrl": "./packages/", 10 - "jsx": "react", 11 - "noEmit": true, 12 - 13 - // disable unreachable code detection because it breaks with esbuild labels 14 - "allowUnreachableCode": true 5 + "noEmit": true 15 6 }, 16 - "include": ["./packages/**/*", "./env.d.ts"], 17 - "exclude": ["node_modules"] 7 + "exclude": [ 8 + "**/node_modules/**", 9 + "**/dist/**", 10 + "**/build/**" 11 + ] 18 12 }