this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+13016 -5825
.github
.vscode
img
nix
packages
browser
core
core-extensions
src
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: 8 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: 8 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: 8 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 }}
+8
.gitignore
··· 3 3 dist.tar.gz 4 4 .DS_Store 5 5 eslint_report.json 6 + .eslintcache 7 + # Nix 8 + /result 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 -4
CHANGELOG.md
··· 1 - - Add `spacepack.filterReal` 2 - - More dependency resolver stages 3 - - Injector now supports "other patch methods" 4 - - OpenAsar fixes 1 + ## Core 2 + 3 + - Updated mappings 4 + - Fixed using remapped paths as patch finds not working
+125 -621
LICENSE
··· 1 - GNU AFFERO GENERAL PUBLIC LICENSE 2 - Version 3, 19 November 2007 1 + GNU LESSER GENERAL PUBLIC LICENSE 2 + Version 3, 29 June 2007 3 3 4 4 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> 5 5 Everyone is permitted to copy and distribute verbatim copies 6 6 of this license document, but changing it is not allowed. 7 7 8 - Preamble 9 8 10 - The GNU Affero General Public License is a free, copyleft license for 11 - software and other kinds of works, specifically designed to ensure 12 - cooperation with the community in the case of network server software. 13 - 14 - The licenses for most software and other practical works are designed 15 - to take away your freedom to share and change the works. By contrast, 16 - our General Public Licenses are intended to guarantee your freedom to 17 - share and change all versions of a program--to make sure it remains free 18 - software for all its users. 19 - 20 - When we speak of free software, we are referring to freedom, not 21 - price. Our General Public Licenses are designed to make sure that you 22 - have the freedom to distribute copies of free software (and charge for 23 - them if you wish), that you receive source code or can get it if you 24 - want it, that you can change the software or use pieces of it in new 25 - free programs, and that you know you can do these things. 26 - 27 - Developers that use our General Public Licenses protect your rights 28 - with two steps: (1) assert copyright on the software, and (2) offer 29 - you this License which gives you legal permission to copy, distribute 30 - and/or modify the software. 31 - 32 - A secondary benefit of defending all users' freedom is that 33 - improvements made in alternate versions of the program, if they 34 - receive widespread use, become available for other developers to 35 - incorporate. Many developers of free software are heartened and 36 - encouraged by the resulting cooperation. However, in the case of 37 - software used on network servers, this result may fail to come about. 38 - The GNU General Public License permits making a modified version and 39 - letting the public access it on a server without ever releasing its 40 - source code to the public. 41 - 42 - The GNU Affero General Public License is designed specifically to 43 - ensure that, in such cases, the modified source code becomes available 44 - to the community. It requires the operator of a network server to 45 - provide the source code of the modified version running there to the 46 - users of that server. Therefore, public use of a modified version, on 47 - a publicly accessible server, gives the public access to the source 48 - code of the modified version. 49 - 50 - An older license, called the Affero General Public License and 51 - published by Affero, was designed to accomplish similar goals. This is 52 - a different license, not a version of the Affero GPL, but Affero has 53 - released a new version of the Affero GPL which permits relicensing under 54 - this license. 55 - 56 - The precise terms and conditions for copying, distribution and 57 - modification follow. 58 - 59 - TERMS AND CONDITIONS 60 - 61 - 0. Definitions. 62 - 63 - "This License" refers to version 3 of the GNU Affero General Public License. 64 - 65 - "Copyright" also means copyright-like laws that apply to other kinds of 66 - works, such as semiconductor masks. 67 - 68 - "The Program" refers to any copyrightable work licensed under this 69 - License. Each licensee is addressed as "you". "Licensees" and 70 - "recipients" may be individuals or organizations. 71 - 72 - To "modify" a work means to copy from or adapt all or part of the work 73 - in a fashion requiring copyright permission, other than the making of an 74 - exact copy. The resulting work is called a "modified version" of the 75 - earlier work or a work "based on" the earlier work. 76 - 77 - A "covered work" means either the unmodified Program or a work based 78 - on the Program. 79 - 80 - To "propagate" a work means to do anything with it that, without 81 - permission, would make you directly or secondarily liable for 82 - infringement under applicable copyright law, except executing it on a 83 - computer or modifying a private copy. Propagation includes copying, 84 - distribution (with or without modification), making available to the 85 - public, and in some countries other activities as well. 86 - 87 - To "convey" a work means any kind of propagation that enables other 88 - parties to make or receive copies. Mere interaction with a user through 89 - a computer network, with no transfer of a copy, is not conveying. 90 - 91 - An interactive user interface displays "Appropriate Legal Notices" 92 - to the extent that it includes a convenient and prominently visible 93 - feature that (1) displays an appropriate copyright notice, and (2) 94 - tells the user that there is no warranty for the work (except to the 95 - extent that warranties are provided), that licensees may convey the 96 - work under this License, and how to view a copy of this License. If 97 - the interface presents a list of user commands or options, such as a 98 - menu, a prominent item in the list meets this criterion. 99 - 100 - 1. Source Code. 101 - 102 - The "source code" for a work means the preferred form of the work 103 - for making modifications to it. "Object code" means any non-source 104 - form of a work. 105 - 106 - A "Standard Interface" means an interface that either is an official 107 - standard defined by a recognized standards body, or, in the case of 108 - interfaces specified for a particular programming language, one that 109 - is widely used among developers working in that language. 110 - 111 - The "System Libraries" of an executable work include anything, other 112 - than the work as a whole, that (a) is included in the normal form of 113 - packaging a Major Component, but which is not part of that Major 114 - Component, and (b) serves only to enable use of the work with that 115 - Major Component, or to implement a Standard Interface for which an 116 - implementation is available to the public in source code form. A 117 - "Major Component", in this context, means a major essential component 118 - (kernel, window system, and so on) of the specific operating system 119 - (if any) on which the executable work runs, or a compiler used to 120 - produce the work, or an object code interpreter used to run it. 121 - 122 - The "Corresponding Source" for a work in object code form means all 123 - the source code needed to generate, install, and (for an executable 124 - work) run the object code and to modify the work, including scripts to 125 - control those activities. However, it does not include the work's 126 - System Libraries, or general-purpose tools or generally available free 127 - programs which are used unmodified in performing those activities but 128 - which are not part of the work. For example, Corresponding Source 129 - includes interface definition files associated with source files for 130 - the work, and the source code for shared libraries and dynamically 131 - linked subprograms that the work is specifically designed to require, 132 - such as by intimate data communication or control flow between those 133 - subprograms and other parts of the work. 134 - 135 - The Corresponding Source need not include anything that users 136 - can regenerate automatically from other parts of the Corresponding 137 - Source. 138 - 139 - The Corresponding Source for a work in source code form is that 140 - same work. 141 - 142 - 2. Basic Permissions. 143 - 144 - All rights granted under this License are granted for the term of 145 - copyright on the Program, and are irrevocable provided the stated 146 - conditions are met. This License explicitly affirms your unlimited 147 - permission to run the unmodified Program. The output from running a 148 - covered work is covered by this License only if the output, given its 149 - content, constitutes a covered work. This License acknowledges your 150 - rights of fair use or other equivalent, as provided by copyright law. 151 - 152 - You may make, run and propagate covered works that you do not 153 - convey, without conditions so long as your license otherwise remains 154 - in force. You may convey covered works to others for the sole purpose 155 - of having them make modifications exclusively for you, or provide you 156 - with facilities for running those works, provided that you comply with 157 - the terms of this License in conveying all material for which you do 158 - not control copyright. Those thus making or running the covered works 159 - for you must do so exclusively on your behalf, under your direction 160 - and control, on terms that prohibit them from making any copies of 161 - your copyrighted material outside their relationship with you. 162 - 163 - Conveying under any other circumstances is permitted solely under 164 - the conditions stated below. Sublicensing is not allowed; section 10 165 - makes it unnecessary. 166 - 167 - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 - 169 - No covered work shall be deemed part of an effective technological 170 - measure under any applicable law fulfilling obligations under article 171 - 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 - similar laws prohibiting or restricting circumvention of such 173 - measures. 174 - 175 - When you convey a covered work, you waive any legal power to forbid 176 - circumvention of technological measures to the extent such circumvention 177 - is effected by exercising rights under this License with respect to 178 - the covered work, and you disclaim any intention to limit operation or 179 - modification of the work as a means of enforcing, against the work's 180 - users, your or third parties' legal rights to forbid circumvention of 181 - technological measures. 182 - 183 - 4. Conveying Verbatim Copies. 184 - 185 - You may convey verbatim copies of the Program's source code as you 186 - receive it, in any medium, provided that you conspicuously and 187 - appropriately publish on each copy an appropriate copyright notice; 188 - keep intact all notices stating that this License and any 189 - non-permissive terms added in accord with section 7 apply to the code; 190 - keep intact all notices of the absence of any warranty; and give all 191 - recipients a copy of this License along with the Program. 192 - 193 - You may charge any price or no price for each copy that you convey, 194 - and you may offer support or warranty protection for a fee. 195 - 196 - 5. Conveying Modified Source Versions. 197 - 198 - You may convey a work based on the Program, or the modifications to 199 - produce it from the Program, in the form of source code under the 200 - terms of section 4, provided that you also meet all of these conditions: 201 - 202 - a) The work must carry prominent notices stating that you modified 203 - it, and giving a relevant date. 204 - 205 - b) The work must carry prominent notices stating that it is 206 - released under this License and any conditions added under section 207 - 7. This requirement modifies the requirement in section 4 to 208 - "keep intact all notices". 209 - 210 - c) You must license the entire work, as a whole, under this 211 - License to anyone who comes into possession of a copy. This 212 - License will therefore apply, along with any applicable section 7 213 - additional terms, to the whole of the work, and all its parts, 214 - regardless of how they are packaged. This License gives no 215 - permission to license the work in any other way, but it does not 216 - invalidate such permission if you have separately received it. 217 - 218 - d) If the work has interactive user interfaces, each must display 219 - Appropriate Legal Notices; however, if the Program has interactive 220 - interfaces that do not display Appropriate Legal Notices, your 221 - work need not make them do so. 222 - 223 - A compilation of a covered work with other separate and independent 224 - works, which are not by their nature extensions of the covered work, 225 - and which are not combined with it such as to form a larger program, 226 - in or on a volume of a storage or distribution medium, is called an 227 - "aggregate" if the compilation and its resulting copyright are not 228 - used to limit the access or legal rights of the compilation's users 229 - beyond what the individual works permit. Inclusion of a covered work 230 - in an aggregate does not cause this License to apply to the other 231 - parts of the aggregate. 232 - 233 - 6. Conveying Non-Source Forms. 234 - 235 - You may convey a covered work in object code form under the terms 236 - of sections 4 and 5, provided that you also convey the 237 - machine-readable Corresponding Source under the terms of this License, 238 - in one of these ways: 239 - 240 - a) Convey the object code in, or embodied in, a physical product 241 - (including a physical distribution medium), accompanied by the 242 - Corresponding Source fixed on a durable physical medium 243 - customarily used for software interchange. 244 - 245 - b) Convey the object code in, or embodied in, a physical product 246 - (including a physical distribution medium), accompanied by a 247 - written offer, valid for at least three years and valid for as 248 - long as you offer spare parts or customer support for that product 249 - model, to give anyone who possesses the object code either (1) a 250 - copy of the Corresponding Source for all the software in the 251 - product that is covered by this License, on a durable physical 252 - medium customarily used for software interchange, for a price no 253 - more than your reasonable cost of physically performing this 254 - conveying of source, or (2) access to copy the 255 - Corresponding Source from a network server at no charge. 256 - 257 - c) Convey individual copies of the object code with a copy of the 258 - written offer to provide the Corresponding Source. This 259 - alternative is allowed only occasionally and noncommercially, and 260 - only if you received the object code with such an offer, in accord 261 - with subsection 6b. 262 - 263 - d) Convey the object code by offering access from a designated 264 - place (gratis or for a charge), and offer equivalent access to the 265 - Corresponding Source in the same way through the same place at no 266 - further charge. You need not require recipients to copy the 267 - Corresponding Source along with the object code. If the place to 268 - copy the object code is a network server, the Corresponding Source 269 - may be on a different server (operated by you or a third party) 270 - that supports equivalent copying facilities, provided you maintain 271 - clear directions next to the object code saying where to find the 272 - Corresponding Source. Regardless of what server hosts the 273 - Corresponding Source, you remain obligated to ensure that it is 274 - available for as long as needed to satisfy these requirements. 275 - 276 - e) Convey the object code using peer-to-peer transmission, provided 277 - you inform other peers where the object code and Corresponding 278 - Source of the work are being offered to the general public at no 279 - charge under subsection 6d. 280 - 281 - A separable portion of the object code, whose source code is excluded 282 - from the Corresponding Source as a System Library, need not be 283 - included in conveying the object code work. 284 - 285 - A "User Product" is either (1) a "consumer product", which means any 286 - tangible personal property which is normally used for personal, family, 287 - or household purposes, or (2) anything designed or sold for incorporation 288 - into a dwelling. In determining whether a product is a consumer product, 289 - doubtful cases shall be resolved in favor of coverage. For a particular 290 - product received by a particular user, "normally used" refers to a 291 - typical or common use of that class of product, regardless of the status 292 - of the particular user or of the way in which the particular user 293 - actually uses, or expects or is expected to use, the product. A product 294 - is a consumer product regardless of whether the product has substantial 295 - commercial, industrial or non-consumer uses, unless such uses represent 296 - the only significant mode of use of the product. 297 - 298 - "Installation Information" for a User Product means any methods, 299 - procedures, authorization keys, or other information required to install 300 - and execute modified versions of a covered work in that User Product from 301 - a modified version of its Corresponding Source. The information must 302 - suffice to ensure that the continued functioning of the modified object 303 - code is in no case prevented or interfered with solely because 304 - modification has been made. 305 - 306 - If you convey an object code work under this section in, or with, or 307 - specifically for use in, a User Product, and the conveying occurs as 308 - part of a transaction in which the right of possession and use of the 309 - User Product is transferred to the recipient in perpetuity or for a 310 - fixed term (regardless of how the transaction is characterized), the 311 - Corresponding Source conveyed under this section must be accompanied 312 - by the Installation Information. But this requirement does not apply 313 - if neither you nor any third party retains the ability to install 314 - modified object code on the User Product (for example, the work has 315 - been installed in ROM). 316 - 317 - The requirement to provide Installation Information does not include a 318 - requirement to continue to provide support service, warranty, or updates 319 - for a work that has been modified or installed by the recipient, or for 320 - the User Product in which it has been modified or installed. Access to a 321 - network may be denied when the modification itself materially and 322 - adversely affects the operation of the network or violates the rules and 323 - protocols for communication across the network. 324 - 325 - Corresponding Source conveyed, and Installation Information provided, 326 - in accord with this section must be in a format that is publicly 327 - documented (and with an implementation available to the public in 328 - source code form), and must require no special password or key for 329 - unpacking, reading or copying. 330 - 331 - 7. Additional Terms. 332 - 333 - "Additional permissions" are terms that supplement the terms of this 334 - License by making exceptions from one or more of its conditions. 335 - Additional permissions that are applicable to the entire Program shall 336 - be treated as though they were included in this License, to the extent 337 - that they are valid under applicable law. If additional permissions 338 - apply only to part of the Program, that part may be used separately 339 - under those permissions, but the entire Program remains governed by 340 - this License without regard to the additional permissions. 341 - 342 - When you convey a copy of a covered work, you may at your option 343 - remove any additional permissions from that copy, or from any part of 344 - it. (Additional permissions may be written to require their own 345 - removal in certain cases when you modify the work.) You may place 346 - additional permissions on material, added by you to a covered work, 347 - for which you have or can give appropriate copyright permission. 348 - 349 - Notwithstanding any other provision of this License, for material you 350 - add to a covered work, you may (if authorized by the copyright holders of 351 - that material) supplement the terms of this License with terms: 352 - 353 - a) Disclaiming warranty or limiting liability differently from the 354 - terms of sections 15 and 16 of this License; or 355 - 356 - b) Requiring preservation of specified reasonable legal notices or 357 - author attributions in that material or in the Appropriate Legal 358 - Notices displayed by works containing it; or 359 - 360 - c) Prohibiting misrepresentation of the origin of that material, or 361 - requiring that modified versions of such material be marked in 362 - reasonable ways as different from the original version; or 363 - 364 - d) Limiting the use for publicity purposes of names of licensors or 365 - authors of the material; or 366 - 367 - e) Declining to grant rights under trademark law for use of some 368 - trade names, trademarks, or service marks; or 369 - 370 - f) Requiring indemnification of licensors and authors of that 371 - material by anyone who conveys the material (or modified versions of 372 - it) with contractual assumptions of liability to the recipient, for 373 - any liability that these contractual assumptions directly impose on 374 - those licensors and authors. 375 - 376 - All other non-permissive additional terms are considered "further 377 - restrictions" within the meaning of section 10. If the Program as you 378 - received it, or any part of it, contains a notice stating that it is 379 - governed by this License along with a term that is a further 380 - restriction, you may remove that term. If a license document contains 381 - a further restriction but permits relicensing or conveying under this 382 - License, you may add to a covered work material governed by the terms 383 - of that license document, provided that the further restriction does 384 - not survive such relicensing or conveying. 9 + This version of the GNU Lesser General Public License incorporates 10 + the terms and conditions of version 3 of the GNU General Public 11 + License, supplemented by the additional permissions listed below. 385 12 386 - If you add terms to a covered work in accord with this section, you 387 - must place, in the relevant source files, a statement of the 388 - additional terms that apply to those files, or a notice indicating 389 - where to find the applicable terms. 13 + 0. Additional Definitions. 390 14 391 - Additional terms, permissive or non-permissive, may be stated in the 392 - form of a separately written license, or stated as exceptions; 393 - the above requirements apply either way. 15 + As used herein, "this License" refers to version 3 of the GNU Lesser 16 + General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 + General Public License. 394 18 395 - 8. Termination. 19 + "The Library" refers to a covered work governed by this License, 20 + other than an Application or a Combined Work as defined below. 396 21 397 - You may not propagate or modify a covered work except as expressly 398 - provided under this License. Any attempt otherwise to propagate or 399 - modify it is void, and will automatically terminate your rights under 400 - this License (including any patent licenses granted under the third 401 - paragraph of section 11). 22 + An "Application" is any work that makes use of an interface provided 23 + by the Library, but which is not otherwise based on the Library. 24 + Defining a subclass of a class defined by the Library is deemed a mode 25 + of using an interface provided by the Library. 402 26 403 - However, if you cease all violation of this License, then your 404 - license from a particular copyright holder is reinstated (a) 405 - provisionally, unless and until the copyright holder explicitly and 406 - finally terminates your license, and (b) permanently, if the copyright 407 - holder fails to notify you of the violation by some reasonable means 408 - prior to 60 days after the cessation. 27 + A "Combined Work" is a work produced by combining or linking an 28 + Application with the Library. The particular version of the Library 29 + with which the Combined Work was made is also called the "Linked 30 + Version". 409 31 410 - Moreover, your license from a particular copyright holder is 411 - reinstated permanently if the copyright holder notifies you of the 412 - violation by some reasonable means, this is the first time you have 413 - received notice of violation of this License (for any work) from that 414 - copyright holder, and you cure the violation prior to 30 days after 415 - your receipt of the notice. 32 + The "Minimal Corresponding Source" for a Combined Work means the 33 + Corresponding Source for the Combined Work, excluding any source code 34 + for portions of the Combined Work that, considered in isolation, are 35 + based on the Application, and not on the Linked Version. 416 36 417 - Termination of your rights under this section does not terminate the 418 - licenses of parties who have received copies or rights from you under 419 - this License. If your rights have been terminated and not permanently 420 - reinstated, you do not qualify to receive new licenses for the same 421 - material under section 10. 37 + The "Corresponding Application Code" for a Combined Work means the 38 + object code and/or source code for the Application, including any data 39 + and utility programs needed for reproducing the Combined Work from the 40 + Application, but excluding the System Libraries of the Combined Work. 422 41 423 - 9. Acceptance Not Required for Having Copies. 42 + 1. Exception to Section 3 of the GNU GPL. 424 43 425 - You are not required to accept this License in order to receive or 426 - run a copy of the Program. Ancillary propagation of a covered work 427 - occurring solely as a consequence of using peer-to-peer transmission 428 - to receive a copy likewise does not require acceptance. However, 429 - nothing other than this License grants you permission to propagate or 430 - modify any covered work. These actions infringe copyright if you do 431 - not accept this License. Therefore, by modifying or propagating a 432 - covered work, you indicate your acceptance of this License to do so. 44 + You may convey a covered work under sections 3 and 4 of this License 45 + without being bound by section 3 of the GNU GPL. 433 46 434 - 10. Automatic Licensing of Downstream Recipients. 47 + 2. Conveying Modified Versions. 435 48 436 - Each time you convey a covered work, the recipient automatically 437 - receives a license from the original licensors, to run, modify and 438 - propagate that work, subject to this License. You are not responsible 439 - for enforcing compliance by third parties with this License. 49 + If you modify a copy of the Library, and, in your modifications, a 50 + facility refers to a function or data to be supplied by an Application 51 + that uses the facility (other than as an argument passed when the 52 + facility is invoked), then you may convey a copy of the modified 53 + version: 440 54 441 - An "entity transaction" is a transaction transferring control of an 442 - organization, or substantially all assets of one, or subdividing an 443 - organization, or merging organizations. If propagation of a covered 444 - work results from an entity transaction, each party to that 445 - transaction who receives a copy of the work also receives whatever 446 - licenses to the work the party's predecessor in interest had or could 447 - give under the previous paragraph, plus a right to possession of the 448 - Corresponding Source of the work from the predecessor in interest, if 449 - the predecessor has it or can get it with reasonable efforts. 55 + a) under this License, provided that you make a good faith effort to 56 + ensure that, in the event an Application does not supply the 57 + function or data, the facility still operates, and performs 58 + whatever part of its purpose remains meaningful, or 450 59 451 - You may not impose any further restrictions on the exercise of the 452 - rights granted or affirmed under this License. For example, you may 453 - not impose a license fee, royalty, or other charge for exercise of 454 - rights granted under this License, and you may not initiate litigation 455 - (including a cross-claim or counterclaim in a lawsuit) alleging that 456 - any patent claim is infringed by making, using, selling, offering for 457 - sale, or importing the Program or any portion of it. 60 + b) under the GNU GPL, with none of the additional permissions of 61 + this License applicable to that copy. 458 62 459 - 11. Patents. 63 + 3. Object Code Incorporating Material from Library Header Files. 460 64 461 - A "contributor" is a copyright holder who authorizes use under this 462 - License of the Program or a work on which the Program is based. The 463 - work thus licensed is called the contributor's "contributor version". 65 + The object code form of an Application may incorporate material from 66 + a header file that is part of the Library. You may convey such object 67 + code under terms of your choice, provided that, if the incorporated 68 + material is not limited to numerical parameters, data structure 69 + layouts and accessors, or small macros, inline functions and templates 70 + (ten or fewer lines in length), you do both of the following: 464 71 465 - A contributor's "essential patent claims" are all patent claims 466 - owned or controlled by the contributor, whether already acquired or 467 - hereafter acquired, that would be infringed by some manner, permitted 468 - by this License, of making, using, or selling its contributor version, 469 - but do not include claims that would be infringed only as a 470 - consequence of further modification of the contributor version. For 471 - purposes of this definition, "control" includes the right to grant 472 - patent sublicenses in a manner consistent with the requirements of 473 - this License. 72 + a) Give prominent notice with each copy of the object code that the 73 + Library is used in it and that the Library and its use are 74 + covered by this License. 474 75 475 - Each contributor grants you a non-exclusive, worldwide, royalty-free 476 - patent license under the contributor's essential patent claims, to 477 - make, use, sell, offer for sale, import and otherwise run, modify and 478 - propagate the contents of its contributor version. 76 + b) Accompany the object code with a copy of the GNU GPL and this license 77 + document. 479 78 480 - In the following three paragraphs, a "patent license" is any express 481 - agreement or commitment, however denominated, not to enforce a patent 482 - (such as an express permission to practice a patent or covenant not to 483 - sue for patent infringement). To "grant" such a patent license to a 484 - party means to make such an agreement or commitment not to enforce a 485 - patent against the party. 79 + 4. Combined Works. 486 80 487 - If you convey a covered work, knowingly relying on a patent license, 488 - and the Corresponding Source of the work is not available for anyone 489 - to copy, free of charge and under the terms of this License, through a 490 - publicly available network server or other readily accessible means, 491 - then you must either (1) cause the Corresponding Source to be so 492 - available, or (2) arrange to deprive yourself of the benefit of the 493 - patent license for this particular work, or (3) arrange, in a manner 494 - consistent with the requirements of this License, to extend the patent 495 - license to downstream recipients. "Knowingly relying" means you have 496 - actual knowledge that, but for the patent license, your conveying the 497 - covered work in a country, or your recipient's use of the covered work 498 - in a country, would infringe one or more identifiable patents in that 499 - country that you have reason to believe are valid. 81 + You may convey a Combined Work under terms of your choice that, 82 + taken together, effectively do not restrict modification of the 83 + portions of the Library contained in the Combined Work and reverse 84 + engineering for debugging such modifications, if you also do each of 85 + the following: 500 86 501 - If, pursuant to or in connection with a single transaction or 502 - arrangement, you convey, or propagate by procuring conveyance of, a 503 - covered work, and grant a patent license to some of the parties 504 - receiving the covered work authorizing them to use, propagate, modify 505 - or convey a specific copy of the covered work, then the patent license 506 - you grant is automatically extended to all recipients of the covered 507 - work and works based on it. 87 + a) Give prominent notice with each copy of the Combined Work that 88 + the Library is used in it and that the Library and its use are 89 + covered by this License. 508 90 509 - A patent license is "discriminatory" if it does not include within 510 - the scope of its coverage, prohibits the exercise of, or is 511 - conditioned on the non-exercise of one or more of the rights that are 512 - specifically granted under this License. You may not convey a covered 513 - work if you are a party to an arrangement with a third party that is 514 - in the business of distributing software, under which you make payment 515 - to the third party based on the extent of your activity of conveying 516 - the work, and under which the third party grants, to any of the 517 - parties who would receive the covered work from you, a discriminatory 518 - patent license (a) in connection with copies of the covered work 519 - conveyed by you (or copies made from those copies), or (b) primarily 520 - for and in connection with specific products or compilations that 521 - contain the covered work, unless you entered into that arrangement, 522 - or that patent license was granted, prior to 28 March 2007. 91 + b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 + document. 523 93 524 - Nothing in this License shall be construed as excluding or limiting 525 - any implied license or other defenses to infringement that may 526 - otherwise be available to you under applicable patent law. 527 - 528 - 12. No Surrender of Others' Freedom. 529 - 530 - If conditions are imposed on you (whether by court order, agreement or 531 - otherwise) that contradict the conditions of this License, they do not 532 - excuse you from the conditions of this License. If you cannot convey a 533 - covered work so as to satisfy simultaneously your obligations under this 534 - License and any other pertinent obligations, then as a consequence you may 535 - not convey it at all. For example, if you agree to terms that obligate you 536 - to collect a royalty for further conveying from those to whom you convey 537 - the Program, the only way you could satisfy both those terms and this 538 - License would be to refrain entirely from conveying the Program. 539 - 540 - 13. Remote Network Interaction; Use with the GNU General Public License. 541 - 542 - Notwithstanding any other provision of this License, if you modify the 543 - Program, your modified version must prominently offer all users 544 - interacting with it remotely through a computer network (if your version 545 - supports such interaction) an opportunity to receive the Corresponding 546 - Source of your version by providing access to the Corresponding Source 547 - from a network server at no charge, through some standard or customary 548 - means of facilitating copying of software. This Corresponding Source 549 - shall include the Corresponding Source for any work covered by version 3 550 - of the GNU General Public License that is incorporated pursuant to the 551 - following paragraph. 552 - 553 - Notwithstanding any other provision of this License, you have 554 - permission to link or combine any covered work with a work licensed 555 - under version 3 of the GNU General Public License into a single 556 - combined work, and to convey the resulting work. The terms of this 557 - License will continue to apply to the part which is the covered work, 558 - but the work with which it is combined will remain governed by version 559 - 3 of the GNU General Public License. 560 - 561 - 14. Revised Versions of this License. 562 - 563 - The Free Software Foundation may publish revised and/or new versions of 564 - the GNU Affero General Public License from time to time. Such new versions 565 - will be similar in spirit to the present version, but may differ in detail to 566 - address new problems or concerns. 94 + c) For a Combined Work that displays copyright notices during 95 + execution, include the copyright notice for the Library among 96 + these notices, as well as a reference directing the user to the 97 + copies of the GNU GPL and this license document. 567 98 568 - Each version is given a distinguishing version number. If the 569 - Program specifies that a certain numbered version of the GNU Affero General 570 - Public License "or any later version" applies to it, you have the 571 - option of following the terms and conditions either of that numbered 572 - version or of any later version published by the Free Software 573 - Foundation. If the Program does not specify a version number of the 574 - GNU Affero General Public License, you may choose any version ever published 575 - by the Free Software Foundation. 576 - 577 - If the Program specifies that a proxy can decide which future 578 - versions of the GNU Affero General Public License can be used, that proxy's 579 - public statement of acceptance of a version permanently authorizes you 580 - to choose that version for the Program. 581 - 582 - Later license versions may give you additional or different 583 - permissions. However, no additional obligations are imposed on any 584 - author or copyright holder as a result of your choosing to follow a 585 - later version. 586 - 587 - 15. Disclaimer of Warranty. 588 - 589 - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 - APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 - HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 - OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 - PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 - IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 - ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 - 598 - 16. Limitation of Liability. 599 - 600 - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 - WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 - THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 - GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 - USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 - DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 - PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 - EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 - SUCH DAMAGES. 609 - 610 - 17. Interpretation of Sections 15 and 16. 611 - 612 - If the disclaimer of warranty and limitation of liability provided 613 - above cannot be given local legal effect according to their terms, 614 - reviewing courts shall apply local law that most closely approximates 615 - an absolute waiver of all civil liability in connection with the 616 - Program, unless a warranty or assumption of liability accompanies a 617 - copy of the Program in return for a fee. 99 + d) Do one of the following: 618 100 619 - END OF TERMS AND CONDITIONS 101 + 0) Convey the Minimal Corresponding Source under the terms of this 102 + License, and the Corresponding Application Code in a form 103 + suitable for, and under terms that permit, the user to 104 + recombine or relink the Application with a modified version of 105 + the Linked Version to produce a modified Combined Work, in the 106 + manner specified by section 6 of the GNU GPL for conveying 107 + Corresponding Source. 620 108 621 - How to Apply These Terms to Your New Programs 109 + 1) Use a suitable shared library mechanism for linking with the 110 + Library. A suitable mechanism is one that (a) uses at run time 111 + a copy of the Library already present on the user's computer 112 + system, and (b) will operate properly with a modified version 113 + of the Library that is interface-compatible with the Linked 114 + Version. 622 115 623 - If you develop a new program, and you want it to be of the greatest 624 - possible use to the public, the best way to achieve this is to make it 625 - free software which everyone can redistribute and change under these terms. 116 + e) Provide Installation Information, but only if you would otherwise 117 + be required to provide such information under section 6 of the 118 + GNU GPL, and only to the extent that such information is 119 + necessary to install and execute a modified version of the 120 + Combined Work produced by recombining or relinking the 121 + Application with a modified version of the Linked Version. (If 122 + you use option 4d0, the Installation Information must accompany 123 + the Minimal Corresponding Source and Corresponding Application 124 + Code. If you use option 4d1, you must provide the Installation 125 + Information in the manner specified by section 6 of the GNU GPL 126 + for conveying Corresponding Source.) 626 127 627 - To do so, attach the following notices to the program. It is safest 628 - to attach them to the start of each source file to most effectively 629 - state the exclusion of warranty; and each file should have at least 630 - the "copyright" line and a pointer to where the full notice is found. 128 + 5. Combined Libraries. 631 129 632 - <one line to give the program's name and a brief idea of what it does.> 633 - Copyright (C) <year> <name of author> 130 + You may place library facilities that are a work based on the 131 + Library side by side in a single library together with other library 132 + facilities that are not Applications and are not covered by this 133 + License, and convey such a combined library under terms of your 134 + choice, if you do both of the following: 634 135 635 - This program is free software: you can redistribute it and/or modify 636 - it under the terms of the GNU Affero General Public License as published by 637 - the Free Software Foundation, either version 3 of the License, or 638 - (at your option) any later version. 136 + a) Accompany the combined library with a copy of the same work based 137 + on the Library, uncombined with any other library facilities, 138 + conveyed under the terms of this License. 639 139 640 - This program is distributed in the hope that it will be useful, 641 - but WITHOUT ANY WARRANTY; without even the implied warranty of 642 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 - GNU Affero General Public License for more details. 140 + b) Give prominent notice with the combined library that part of it 141 + is a work based on the Library, and explaining where to find the 142 + accompanying uncombined form of the same work. 644 143 645 - You should have received a copy of the GNU Affero General Public License 646 - along with this program. If not, see <https://www.gnu.org/licenses/>. 144 + 6. Revised Versions of the GNU Lesser General Public License. 647 145 648 - Also add information on how to contact you by electronic and paper mail. 146 + The Free Software Foundation may publish revised and/or new versions 147 + of the GNU Lesser General Public License from time to time. Such new 148 + versions will be similar in spirit to the present version, but may 149 + differ in detail to address new problems or concerns. 649 150 650 - If your software can interact with users remotely through a computer 651 - network, you should also make sure that it provides a way for users to 652 - get its source. For example, if your program is a web application, its 653 - interface could display a "Source" link that leads users to an archive 654 - of the code. There are many ways you could offer source, and different 655 - solutions will be better for different programs; see section 13 for the 656 - specific requirements. 151 + Each version is given a distinguishing version number. If the 152 + Library as you received it specifies that a certain numbered version 153 + of the GNU Lesser General Public License "or any later version" 154 + applies to it, you have the option of following the terms and 155 + conditions either of that published version or of any later version 156 + published by the Free Software Foundation. If the Library as you 157 + received it does not specify a version number of the GNU Lesser 158 + General Public License, you may choose any version of the GNU Lesser 159 + General Public License ever published by the Free Software Foundation. 657 160 658 - You should also get your employer (if you work as a programmer) or school, 659 - if any, to sign a "copyright disclaimer" for the program, if necessary. 660 - For more information on this, and how to apply and follow the GNU AGPL, see 661 - <https://www.gnu.org/licenses/>. 161 + If the Library as you received it specifies that a proxy can decide 162 + whether future versions of the GNU Lesser General Public License shall 163 + apply, that proxy's public statement of acceptance of any version is 164 + permanent authorization for you to choose that version for the 165 + Library.
+16 -5
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 - moonlight is licensed under the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.html) (`AGPL-3.0-or-later`). See [the documentation](https://moonlight-mod.github.io/) for more information. 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.
+141 -43
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 - const wpModules = fs.readdirSync(wpModulesDir); 139 - for (const wpModule of wpModules) { 140 - entryPoints.push( 141 - `packages/core-extensions/src/${ext}/webpackModules/${wpModule}` 142 - ); 219 + const wpModules = fs.opendirSync(wpModulesDir); 220 + for await (const wpModule of wpModules) { 221 + if (wpModule.isFile()) { 222 + entryPoints.push(`packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`); 223 + } else { 224 + for (const fileExt of ["ts", "tsx"]) { 225 + const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`; 226 + if (fs.existsSync(path)) { 227 + entryPoints.push({ 228 + in: path, 229 + out: `webpackModules/${wpModule.name}` 230 + }); 231 + } 232 + } 233 + } 143 234 } 144 235 } 145 236 ··· 156 247 } 157 248 }; 158 249 250 + const styleInput = `packages/core-extensions/src/${ext}/style.css`; 251 + const styleOutput = `dist/core-extensions/${ext}/style.css`; 252 + 159 253 const esbuildConfig = { 160 254 entryPoints, 161 255 outdir, 162 256 163 - format: "cjs", 257 + format: "iife", 258 + globalName: "module.exports", 164 259 platform: "node", 165 260 166 261 treeShaking: true, ··· 174 269 }, 175 270 logLevel: "silent", 176 271 plugins: [ 177 - ...(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) 178 277 ? [ 179 278 copyStaticFiles({ 180 - src: `./packages/core-extensions/src/${ext}/manifest.json`, 181 - dest: `./dist/core-extensions/${ext}/manifest.json` 279 + src: styleInput, 280 + dest: styleOutput 182 281 }) 183 282 ] 184 283 : []), ··· 198 297 199 298 const promises = []; 200 299 201 - for (const [name, entry] of Object.entries(config)) { 202 - promises.push(build(name, entry)); 203 - } 204 - 205 - const coreExtensions = fs.readdirSync("./packages/core-extensions/src"); 206 - for (const ext of coreExtensions) { 207 - 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 + } 208 308 209 - for (const fileExt of ["ts", "tsx"]) { 210 - for (const type of ["index", "node", "host"]) { 211 - if ( 212 - fs.existsSync( 213 - `./packages/core-extensions/src/${ext}/${type}.${fileExt}` 214 - ) 215 - ) { 216 - promises.push(buildExt(ext, type, !copiedManifest, fileExt)); 217 - 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 + } 218 316 } 219 317 } 220 318 }
-1
env.d.ts
··· 1 - /// <reference types="./packages/types/src/index" />
+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 + ];
+61
flake.lock
··· 1 + { 2 + "nodes": { 3 + "flake-utils": { 4 + "inputs": { 5 + "systems": "systems" 6 + }, 7 + "locked": { 8 + "lastModified": 1701680307, 9 + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", 10 + "owner": "numtide", 11 + "repo": "flake-utils", 12 + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", 13 + "type": "github" 14 + }, 15 + "original": { 16 + "owner": "numtide", 17 + "repo": "flake-utils", 18 + "type": "github" 19 + } 20 + }, 21 + "nixpkgs": { 22 + "locked": { 23 + "lastModified": 1744232761, 24 + "narHash": "sha256-gbl9hE39nQRpZaLjhWKmEu5ejtQsgI5TWYrIVVJn30U=", 25 + "owner": "NixOS", 26 + "repo": "nixpkgs", 27 + "rev": "f675531bc7e6657c10a18b565cfebd8aa9e24c14", 28 + "type": "github" 29 + }, 30 + "original": { 31 + "owner": "NixOS", 32 + "ref": "nixos-unstable", 33 + "repo": "nixpkgs", 34 + "type": "github" 35 + } 36 + }, 37 + "root": { 38 + "inputs": { 39 + "flake-utils": "flake-utils", 40 + "nixpkgs": "nixpkgs" 41 + } 42 + }, 43 + "systems": { 44 + "locked": { 45 + "lastModified": 1681028828, 46 + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 + "owner": "nix-systems", 48 + "repo": "default", 49 + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 + "type": "github" 51 + }, 52 + "original": { 53 + "owner": "nix-systems", 54 + "repo": "default", 55 + "type": "github" 56 + } 57 + } 58 + }, 59 + "root": "root", 60 + "version": 7 61 + }
+31
flake.nix
··· 1 + { 2 + description = "Yet another Discord mod"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 + flake-utils.url = "github:numtide/flake-utils"; 7 + }; 8 + 9 + outputs = { self, nixpkgs, flake-utils }: 10 + let overlay = import ./nix/overlay.nix { }; 11 + in flake-utils.lib.eachDefaultSystem (system: 12 + let 13 + pkgs = import nixpkgs { 14 + inherit system; 15 + config.allowUnfree = true; 16 + overlays = [ overlay ]; 17 + }; 18 + in { 19 + # Don't use these unless you're testing things 20 + packages.default = pkgs.moonlight-mod; 21 + packages.moonlight-mod = pkgs.moonlight-mod; 22 + 23 + packages.discord = pkgs.discord; 24 + packages.discord-ptb = pkgs.discord-ptb; 25 + packages.discord-canary = pkgs.discord-canary; 26 + packages.discord-development = pkgs.discord-development; 27 + }) // { 28 + overlays.default = overlay; 29 + homeModules.default = ./nix/home-manager.nix; 30 + }; 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 -15
package.json
··· 1 1 { 2 2 "name": "moonlight", 3 - "version": "1.0.7", 3 + "version": "1.3.14", 4 + "packageManager": "pnpm@10.7.1", 4 5 "description": "Yet another Discord mod", 6 + "license": "LGPL-3.0-or-later", 5 7 "homepage": "https://moonlight-mod.github.io/", 6 8 "repository": { 7 9 "type": "git", ··· 9 11 }, 10 12 "bugs": { 11 13 "url": "https://github.com/moonlight-mod/moonlight/issues" 14 + }, 15 + "engineStrict": true, 16 + "engines": { 17 + "node": ">=22", 18 + "pnpm": ">=10", 19 + "npm": "pnpm", 20 + "yarn": "pnpm" 12 21 }, 13 22 "scripts": { 14 23 "build": "node build.mjs", 15 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", 16 28 "lint": "eslint packages", 17 - "lint:fix": "eslint packages", 18 - "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", 19 31 "typecheck": "tsc --noEmit", 20 32 "check": "pnpm run lint && pnpm run typecheck", 21 - "prepare": "husky install" 33 + "prepare": "husky install", 34 + "updates": "pnpm taze -r" 22 35 }, 23 36 "devDependencies": { 24 - "@typescript-eslint/eslint-plugin": "^6.13.2", 25 - "@typescript-eslint/parser": "^6.13.2", 26 - "esbuild": "^0.19.3", 27 - "esbuild-copy-static-files": "^0.1.0", 28 - "eslint": "^8.55.0", 29 - "eslint-config-prettier": "^9.1.0", 30 - "eslint-plugin-prettier": "^5.0.1", 31 - "eslint-plugin-react": "^7.33.2", 32 - "husky": "^8.0.3", 33 - "prettier": "^3.1.0", 34 - "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" 35 46 } 36 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 }
+131 -86
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 - for (const file of fs.readdirSync(dir)) { 17 - if (file === "manifest.json") { 18 - ret.push(path.join(dir, file)); 19 - } 11 + if (await moonlightNodeSandboxed.fs.exists(dir)) { 12 + for (const file of await moonlightNodeSandboxed.fs.readdir(dir)) { 13 + const path = moonlightNodeSandboxed.fs.join(dir, file); 14 + if (file === "manifest.json") { 15 + ret.push(path); 16 + } 20 17 21 - if (fs.statSync(path.join(dir, file)).isDirectory()) { 22 - ret.push(...findManifests(path.join(dir, file))); 18 + if (!(await moonlightNodeSandboxed.fs.isFile(path))) { 19 + ret.push(...(await findManifests(path))); 20 + } 23 21 } 24 22 } 25 23 26 24 return ret; 27 25 } 28 26 29 - function loadDetectedExtensions( 27 + async function loadDetectedExtensions( 30 28 dir: string, 31 - type: ExtensionLoadSource 32 - ): DetectedExtension[] { 33 - const fs = requireImport("fs"); 34 - const path = requireImport("path"); 29 + type: ExtensionLoadSource, 30 + seen: Set<string> 31 + ): Promise<DetectedExtension[]> { 35 32 const ret: DetectedExtension[] = []; 36 33 37 - const manifests = findManifests(dir); 34 + const manifests = await findManifests(dir); 38 35 for (const manifestPath of manifests) { 39 - if (!fs.existsSync(manifestPath)) continue; 40 - 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"); 41 50 42 - const manifest: ExtensionManifest = JSON.parse( 43 - fs.readFileSync(manifestPath, "utf8") 44 - ); 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; 45 63 46 - const webPath = path.join(dir, "index.js"); 47 - const nodePath = path.join(dir, "node.js"); 48 - const hostPath = path.join(dir, "host.js"); 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 + } 49 69 50 - // if none exist (empty manifest) don't give a shit 51 - if ( 52 - !fs.existsSync(webPath) && 53 - !fs.existsSync(nodePath) && 54 - !fs.existsSync(hostPath) 55 - ) { 56 - continue; 70 + const wpModules: Record<string, string> = {}; 71 + const wpModulesPath = moonlightNodeSandboxed.fs.join(dir, "webpackModules"); 72 + if (await moonlightNodeSandboxed.fs.exists(wpModulesPath)) { 73 + const wpModulesFile = await moonlightNodeSandboxed.fs.readdir(wpModulesPath); 74 + 75 + for (const wpModuleFile of wpModulesFile) { 76 + if (wpModuleFile.endsWith(".js")) { 77 + wpModules[wpModuleFile.replace(".js", "")] = await moonlightNodeSandboxed.fs.readFileString( 78 + moonlightNodeSandboxed.fs.join(wpModulesPath, wpModuleFile) 79 + ); 80 + } 81 + } 82 + } 83 + 84 + const stylePath = moonlightNodeSandboxed.fs.join(dir, "style.css"); 85 + 86 + ret.push({ 87 + id: manifest.id, 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); 57 106 } 107 + } 58 108 59 - const web = fs.existsSync(webPath) 60 - ? fs.readFileSync(webPath, "utf8") 61 - : undefined; 109 + return ret; 110 + } 62 111 63 - let url: string | undefined = undefined; 64 - const urlPath = path.join(dir, constants.repoUrlFile); 65 - if (type === ExtensionLoadSource.Normal && fs.existsSync(urlPath)) { 66 - url = fs.readFileSync(urlPath, "utf8"); 67 - } 112 + async function getExtensionsNative(): Promise<DetectedExtension[]> { 113 + const config = await readConfig(); 114 + const res = []; 115 + const seen = new Set<string>(); 68 116 69 - const wpModules: Record<string, string> = {}; 70 - const wpModulesPath = path.join(dir, "webpackModules"); 71 - if (fs.existsSync(wpModulesPath)) { 72 - const wpModulesFile = fs.readdirSync(wpModulesPath); 117 + res.push(...(await loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core, seen))); 118 + 119 + for (const devSearchPath of config.devSearchPaths ?? []) { 120 + res.push(...(await loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer, seen))); 121 + } 122 + 123 + res.push(...(await loadDetectedExtensions(await getExtensionsPath(), ExtensionLoadSource.Normal, seen))); 124 + 125 + return res; 126 + } 127 + 128 + async function getExtensionsBrowser(): Promise<DetectedExtension[]> { 129 + const ret: DetectedExtension[] = []; 130 + const seen = new Set<string>(); 73 131 74 - for (const wpModuleFile of wpModulesFile) { 75 - if (wpModuleFile.endsWith(".js")) { 76 - wpModules[wpModuleFile.replace(".js", "")] = fs.readFileSync( 77 - path.join(wpModulesPath, wpModuleFile), 78 - "utf8" 79 - ); 80 - } 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]; 81 145 } 82 146 } 83 147 ··· 85 149 id: manifest.id, 86 150 manifest, 87 151 source: { 88 - type, 89 - url 152 + type: ExtensionLoadSource.Core 90 153 }, 91 154 scripts: { 92 155 web, 93 - webPath: web != null ? webPath : undefined, 94 156 webpackModules: wpModules, 95 - nodePath: fs.existsSync(nodePath) ? nodePath : undefined, 96 - hostPath: fs.existsSync(hostPath) ? hostPath : undefined 157 + style: coreExtensionsFs[`${ext}/style.css`] 97 158 } 98 159 }); 160 + seen.add(manifest.id); 99 161 } 100 162 101 - return ret; 102 - } 103 - 104 - function getExtensionsNative(): DetectedExtension[] { 105 - const config = readConfig(); 106 - const res = []; 107 - 108 - res.push( 109 - ...loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core) 110 - ); 111 - 112 - res.push( 113 - ...loadDetectedExtensions(getExtensionsPath(), ExtensionLoadSource.Normal) 114 - ); 115 - 116 - for (const devSearchPath of config.devSearchPaths ?? []) { 117 - res.push( 118 - ...loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer) 119 - ); 163 + if (await moonlightNodeSandboxed.fs.exists("/extensions")) { 164 + ret.push(...(await loadDetectedExtensions("/extensions", ExtensionLoadSource.Normal, seen))); 120 165 } 121 166 122 - return res; 167 + return ret; 123 168 } 124 169 125 - export function getExtensions(): DetectedExtension[] { 170 + export async function getExtensions(): Promise<DetectedExtension[]> { 126 171 webPreload: { 127 172 return moonlightNode.extensions; 128 173 } 129 174 130 - nodePreload: { 131 - return getExtensionsNative(); 175 + browser: { 176 + return await getExtensionsBrowser(); 132 177 } 133 178 134 - injector: { 135 - return getExtensionsNative(); 179 + nodeTarget: { 180 + return await getExtensionsNative(); 136 181 } 137 182 138 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 + }
+266 -120
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); 31 + moonlight.unpatched.add(patch); 23 32 } 24 33 25 34 export function registerWebpackModule(wp: IdentifiedWebpackModule) { 26 35 webpackModules.add(wp); 36 + if (wp.dependencies?.length) { 37 + moonlight.pendingModules.add(wp); 38 + } 39 + } 40 + 41 + export function onModuleLoad(module: string | string[], callback: (moduleId: string) => void): void { 42 + let moduleIds = module; 43 + 44 + if (typeof module === "string") { 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 + } 27 55 } 28 56 29 57 /* ··· 38 66 const moduleCache: Record<string, string> = {}; 39 67 const patched: Record<string, Array<string>> = {}; 40 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 + 41 103 function patchModules(entry: WebpackJsonpEntry[1]) { 104 + // Populate the module cache 42 105 for (const [id, func] of Object.entries(entry)) { 43 - let moduleString = Object.prototype.hasOwnProperty.call(moduleCache, id) 44 - ? moduleCache[id] 45 - : 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 + } 46 111 47 - for (const patch of patches) { 112 + for (const [id, func] of Object.entries(entry)) { 113 + if (func.__moonlight === true) continue; 114 + 115 + // Clone the module string so finds don't get messed up by other extensions 116 + const origModuleString = moduleCache[id]; 117 + let moduleString = origModuleString; 118 + const patchedStr = []; 119 + const mappedName = Object.entries(moonlight.moonmap.modules).find((m) => m[1] === id)?.[0]; 120 + let modified = false; 121 + let swappedModule = false; 122 + 123 + const exts = new Set<string>(); 124 + 125 + for (let i = 0; i < patches.length; i++) { 126 + const patch = patches[i]; 48 127 if (patch.prerequisite != null && !patch.prerequisite()) { 128 + moonlight.unpatched.delete(patch); 49 129 continue; 50 130 } 51 131 ··· 54 134 patch.find.lastIndex = 0; 55 135 } 56 136 57 - // indexOf is faster than includes by 0.25% lmao 58 - const match = 59 - typeof patch.find === "string" 60 - ? moduleString.indexOf(patch.find) !== -1 61 - : patch.find.test(moduleString); 137 + const match = testFind(origModuleString, patch.find) || patch.find === mappedName; 62 138 63 139 // Global regexes apply to all modules 64 - const shouldRemove = 65 - typeof patch.find === "string" ? true : !patch.find.global; 140 + const shouldRemove = typeof patch.find === "string" ? true : !patch.find.global; 66 141 142 + let replaced = moduleString; 143 + let hardFailed = false; 67 144 if (match) { 68 - moonlight.unpatched.delete(patch); 145 + // We ensured normal PatchReplace objects get turned into arrays on register 146 + const replaces = patch.replace as PatchReplace[]; 69 147 70 - // We ensured all arrays get turned into normal PatchReplace objects on register 71 - 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); 72 154 73 - if ( 74 - replace.type === undefined || 75 - replace.type === PatchReplaceType.Normal 76 - ) { 77 - // Add support for \i to match rspack's minified names 78 - if (typeof replace.match !== "string") { 79 - replace.match = new RegExp( 80 - replace.match.source.replace(/\\i/g, "[A-Za-z_$][\\w$]*"), 81 - replace.match.flags 82 - ); 83 - } 84 - // tsc fails to detect the overloads for this, so I'll just do this 85 - // Verbose, but it works 86 - let replaced; 87 - if (typeof replace.replacement === "string") { 88 - replaced = moduleString.replace(replace.match, replace.replacement); 89 - } else { 90 - replaced = moduleString.replace(replace.match, replace.replacement); 91 - } 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 + } 92 163 93 - if (replaced === moduleString) { 94 - logger.warn("Patch replacement failed", id, patch); 95 - 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; 96 181 } 182 + } 97 183 98 - // Store what extensions patched what modules for easier debugging 99 - patched[id] = patched[id] || []; 100 - patched[id].push(`${patch.ext}#${patch.id}`); 184 + if (!hardFailed) { 185 + moduleString = replaced; 186 + modified = true; 187 + exts.add(patch.ext); 188 + } 101 189 102 - // Webpack module arguments are minified, so we replace them with consistent names 103 - // We have to wrap it so things don't break, though 104 - const patchedStr = patched[id].sort().join(", "); 190 + if (isPatched) moonlight.unpatched.delete(patch); 191 + if (shouldRemove) patches.splice(i--, 1); 192 + } 193 + } 105 194 106 - const wrapped = 107 - `(${replaced}).apply(this, arguments)\n` + 108 - `// Patched by moonlight: ${patchedStr}\n` + 109 - `//# 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 + } 110 201 111 - try { 112 - const func = new Function( 113 - "module", 114 - "exports", 115 - "require", 116 - wrapped 117 - ) as WebpackModuleFunc; 118 - entry[id] = func; 119 - entry[id].__moonlight = true; 120 - moduleString = replaced; 121 - } catch (e) { 122 - logger.warn("Error constructing function for patch", e); 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; 123 208 } 124 - } else if (replace.type === PatchReplaceType.Module) { 125 - // Directly replace the module with a new one 126 - const newModule = replace.replacement(moduleString); 127 - entry[id] = newModule; 128 - entry[id].__moonlight = true; 129 - moduleString = 130 - newModule.toString().replace(/\n/g, "") + 131 - `//# sourceURL=Webpack-Module-${id}`; 132 - } 133 - 134 - if (shouldRemove) { 135 - patches.splice( 136 - patches.findIndex((p) => p.ext === patch.ext && p.id === patch.id), 137 - 1 138 - ); 139 209 } 140 210 } 211 + } catch (e) { 212 + logger.error("Failed to parse script for LunAST", id, e); 141 213 } 142 214 143 215 if (moonlightNode.config.patchAll === true) { 144 - if ( 145 - (typeof id !== "string" || !id.includes("_")) && 146 - !entry[id].__moonlight 147 - ) { 148 - const wrapped = 149 - `(${moduleString}).apply(this, arguments)\n` + 150 - `//# sourceURL=Webpack-Module-${id}`; 151 - entry[id] = new Function( 152 - "module", 153 - "exports", 154 - "require", 155 - wrapped 156 - ) 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; 157 219 entry[id].__moonlight = true; 158 220 } 159 221 } 160 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 + 161 236 moduleCache[id] = moduleString; 162 237 } 163 238 } ··· 169 244 */ 170 245 let chunkId = Number.MAX_SAFE_INTEGER; 171 246 247 + function depToString(x: ExplicitExtensionDependency) { 248 + return x.ext != null ? `${x.ext}_${x.id}` : x.id; 249 + } 250 + 172 251 function handleModuleDependencies() { 173 252 const modules = Array.from(webpackModules.values()); 174 253 175 - const dependencies: Dependency<string, IdentifiedWebpackModule>[] = 176 - modules.map((wp) => { 177 - return { 178 - id: `${wp.ext}_${wp.id}`, 179 - data: wp 180 - }; 181 - }); 254 + const dependencies: Dependency<string, IdentifiedWebpackModule>[] = modules.map((wp) => { 255 + return { 256 + id: depToString(wp), 257 + data: wp 258 + }; 259 + }); 182 260 183 261 const [sorted, _] = calculateDependencies(dependencies, { 184 262 fetchDep: (id) => { 185 - return modules.find((x) => id === `${x.ext}_${x.id}`) ?? null; 263 + return modules.find((x) => id === depToString(x)) ?? null; 186 264 }, 187 265 188 266 getDeps: (item) => { 189 267 const deps = item.data?.dependencies ?? []; 190 268 return ( 191 269 deps.filter( 192 - (dep) => !(dep instanceof RegExp || typeof dep === "string") 270 + (dep) => !(dep instanceof RegExp || typeof dep === "string") && dep.ext != null 193 271 ) as ExplicitExtensionDependency[] 194 - ).map((x) => `${x.ext}_${x.id}`); 272 + ).map(depToString); 195 273 } 196 274 }); 197 275 ··· 206 284 207 285 for (const [_modId, mod] of Object.entries(entry)) { 208 286 const modStr = mod.toString(); 209 - const wpModules = Array.from(webpackModules.values()); 210 - for (const wpModule of wpModules) { 211 - const id = wpModule.ext + "_" + wpModule.id; 287 + for (const wpModule of webpackModules) { 288 + const id = depToString(wpModule); 212 289 if (wpModule.dependencies) { 213 290 const deps = new Set(wpModule.dependencies); 214 291 215 292 // FIXME: This dependency resolution might fail if the things we want 216 293 // got injected earlier. If weird dependencies fail, this is likely why. 217 294 if (deps.size) { 218 - for (const dep of deps.values()) { 295 + for (const dep of deps) { 219 296 if (typeof dep === "string") { 220 297 if (modStr.includes(dep)) deps.delete(dep); 221 298 } else if (dep instanceof RegExp) { 222 299 if (dep.test(modStr)) deps.delete(dep); 223 300 } else if ( 224 - injectedWpModules.find( 225 - (x) => x.ext === dep.ext && x.id === dep.id 226 - ) 301 + dep.ext != null 302 + ? injectedWpModules.find((x) => x.ext === dep.ext && x.id === dep.id) 303 + : injectedWpModules.find((x) => x.id === dep.id) 227 304 ) { 228 305 deps.delete(dep); 229 306 } 230 307 } 231 308 309 + wpModule.dependencies = Array.from(deps); 232 310 if (deps.size !== 0) { 233 - // Update the deps that have passed 234 - webpackModules.delete(wpModule); 235 - wpModule.dependencies = Array.from(deps); 236 - webpackModules.add(wpModule); 237 311 continue; 238 312 } 239 - 240 - wpModule.dependencies = Array.from(deps); 241 313 } 242 314 } 243 315 244 316 webpackModules.delete(wpModule); 317 + moonlight.pendingModules.delete(wpModule); 245 318 injectedWpModules.push(wpModule); 246 319 247 320 inject = true; 248 321 249 - if (wpModule.run) modules[id] = wpModule.run; 250 - if (wpModule.entrypoint) entrypoints.push(id); 322 + if (wpModule.run) { 323 + modules[id] = wpModule.run; 324 + wpModule.run.__moonlight = true; 325 + // @ts-expect-error hacks 326 + wpModule.run.call = function (self, module, exports, require) { 327 + try { 328 + wpModule.run!.apply(self, [module, exports, require]); 329 + } catch (err) { 330 + logger.error(`Failed to run module "${id}":`, err); 331 + } 332 + }; 333 + if (wpModule.entrypoint) entrypoints.push(id); 334 + } 251 335 } 252 336 if (!webpackModules.size) break; 253 337 } 254 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 + } 351 + } 352 + 255 353 if (inject) { 256 354 logger.debug("Injecting modules:", modules, entrypoints); 257 355 window.webpackChunkdiscord_app.push([ 258 356 [--chunkId], 259 357 modules, 260 - (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 + }) 261 370 ]); 262 371 } 263 372 } ··· 268 377 } 269 378 } 270 379 380 + function moduleSourceGetter(id: string) { 381 + return moduleCache[id] ?? null; 382 + } 383 + 271 384 /* 272 385 Webpack modules are bundled into an array of arrays that hold each function. 273 386 Since we run code before Discord, we can create our own Webpack array and ··· 279 392 export async function installWebpackPatcher() { 280 393 await handleModuleDependencies(); 281 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 + 282 408 let realWebpackJsonp: WebpackJsonp | null = null; 283 409 Object.defineProperty(window, "webpackChunkdiscord_app", { 284 410 set: (jsonp: WebpackJsonp) => { ··· 290 416 const realPush = jsonp.push; 291 417 if (jsonp.push.__moonlight !== true) { 292 418 jsonp.push = (items) => { 419 + moonlight.events.dispatchEvent(WebEventType.ChunkLoad, { 420 + chunkId: items[0], 421 + modules: items[1], 422 + require: items[2] 423 + }); 424 + 293 425 patchModules(items[1]); 294 426 295 427 try { ··· 327 459 } 328 460 }); 329 461 330 - registerWebpackModule({ 331 - ext: "moonlight", 332 - id: "fix_rspack_init_modules", 333 - entrypoint: true, 334 - run: function (module, exports, require) { 335 - patchModules(require.m); 462 + Object.defineProperty(Function.prototype, "m", { 463 + configurable: true, 464 + set(modules: any) { 465 + const { stack } = new Error(); 466 + if (stack!.includes("/assets/") && !Array.isArray(modules)) { 467 + moonlight.events.dispatchEvent(WebEventType.ChunkLoad, { 468 + modules: modules 469 + }); 470 + patchModules(modules); 471 + 472 + if (!window.webpackChunkdiscord_app) window.webpackChunkdiscord_app = []; 473 + injectModules(modules); 474 + } 475 + 476 + Object.defineProperty(this, "m", { 477 + value: modules, 478 + configurable: true, 479 + enumerable: true, 480 + writable: true 481 + }); 336 482 } 337 483 }); 338 484 }
+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 -32
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 - ] 10 - }, 11 - 12 - flux: { 13 - dependencies: [ 14 - { ext: "spacepack", id: "spacepack" }, 15 - "useStateFromStores:function" 16 - ] 17 - }, 18 - 19 - fluxDispatcher: { 20 - dependencies: [ 21 - { ext: "spacepack", id: "spacepack" }, 22 - "isDispatching", 23 - "dispatch" 24 - ] 4 + stores: { 5 + dependencies: [{ id: "discord/packages/flux" }] 25 6 }, 26 - 27 - react: { 28 - dependencies: [ 29 - { ext: "spacepack", id: "spacepack" }, 30 - "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED", 31 - /\.?version(?:=|:)/, 32 - /\.?createElement(?:=|:)/ 33 - ] 7 + ErrorBoundary: { 8 + dependencies: [{ id: "react" }] 34 9 }, 35 - 36 - stores: { 37 - dependencies: [{ ext: "common", id: "flux" }] 10 + icons: { 11 + dependencies: [{ id: "react" }, { id: "discord/components/common/index" }] 38 12 } 39 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;
-26
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.default; 7 - const LegacyText = spacepack.findByCode(".selectable", ".colorStandard")[0] 8 - .exports.default; 9 - const Flex = spacepack.findByCode(".flex" + "GutterSmall,")[0].exports.default; 10 - const CardClasses = spacepack.findByCode("card", "cardHeader", "inModal")[0] 11 - .exports; 12 - const ControlClasses = spacepack.findByCode( 13 - "title", 14 - "titleDefault", 15 - "dividerDefault" 16 - )[0].exports; 17 - 18 - // We use CJS export here because merging the exports from Components is annoying as shit 19 - module.exports = { 20 - ...Components, 21 - MarkdownParser, 22 - LegacyText, 23 - Flex, 24 - CardClasses, 25 - ControlClasses 26 - };
-5
packages/core-extensions/src/common/webpackModules/flux.ts
··· 1 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 - 3 - module.exports = spacepack.findByCode( 4 - ["useStateFromStores", ":function"].join("") 5 - )[0].exports;
-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.default;
+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;
+31
packages/core-extensions/src/contextMenu/index.tsx
··· 1 + import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; 2 + 3 + export const patches: Patch[] = [ 4 + { 5 + find: "Menu API only allows Items and groups of Items as children.", 6 + replace: [ 7 + { 8 + match: /(?<=let{navId[^}]+?}=(.),.=).+?(?=,)/, 9 + replacement: (items, props) => `require("contextMenu_contextMenu")._patchMenu(${props},${items})` 10 + } 11 + ] 12 + }, 13 + { 14 + find: ".getContextMenu(", 15 + replace: [ 16 + { 17 + match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/, 18 + replacement: (render) => `require("contextMenu_contextMenu")._saveProps(this,${render})` 19 + } 20 + ] 21 + } 22 + ]; 23 + 24 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 25 + contextMenu: { 26 + dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."] 27 + }, 28 + evilMenu: { 29 + dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."] 30 + } 31 + };
+11
packages/core-extensions/src/contextMenu/manifest.json
··· 1 + { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 + "id": "contextMenu", 4 + "apiLevel": 2, 5 + "meta": { 6 + "name": "Context Menu", 7 + "tagline": "A library for patching and creating context menus", 8 + "authors": ["redstonekasi"], 9 + "tags": ["library"] 10 + } 11 + }
+88
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
··· 1 + import { InternalItem, Menu, MenuElement } from "@moonlight-mod/types/coreExtensions/contextMenu"; 2 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 3 + import parser from "@moonlight-mod/wp/contextMenu_evilMenu"; 4 + 5 + // NOTE: We originally had item as a function that returned this, but it didn't 6 + // quite know how to work out the type and thought it was a JSX element (it 7 + // *technically* was). This has less type safety, but a @ts-expect-error has 8 + // zero, so it's better than nothing. 9 + type ReturnType = MenuElement | MenuElement[]; 10 + 11 + type Patch = { 12 + navId: string; 13 + item: React.FC<any>; 14 + anchor: string | RegExp; 15 + before: boolean; 16 + }; 17 + 18 + function addItem<T = any>(navId: string, item: React.FC<T>, anchor: string | RegExp, before = false) { 19 + if (anchor instanceof RegExp && anchor.flags.includes("g")) 20 + throw new Error("anchor regular expression should not be global"); 21 + patches.push({ navId, item, anchor, before }); 22 + } 23 + 24 + const patches: Patch[] = []; 25 + function _patchMenu(props: React.ComponentProps<Menu>, items: InternalItem[]) { 26 + const matches = patches.filter((p) => p.navId === props.navId); 27 + if (!matches.length) return items; 28 + 29 + for (const patch of matches) { 30 + const idx = items.findIndex((i) => 31 + typeof patch.anchor === "string" ? i.key === patch.anchor : patch.anchor.test(i.key!) 32 + ); 33 + if (idx === -1) continue; 34 + items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps) as ReturnType)); 35 + } 36 + 37 + return items; 38 + } 39 + 40 + let menuProps: any; 41 + function _saveProps(self: any, el: any) { 42 + menuProps = el.props; 43 + 44 + const original = self.props.closeContextMenu; 45 + self.props.closeContextMenu = function (...args: any[]) { 46 + menuProps = undefined; 47 + return original?.apply(this, args); 48 + }; 49 + 50 + return el; 51 + } 52 + 53 + module.exports = { 54 + patches, 55 + addItem, 56 + _patchMenu, 57 + _saveProps 58 + }; 59 + 60 + // Unmangle Menu elements 61 + // spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString(); 62 + const code = 63 + spacepack.require.m[ 64 + spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id 65 + ].toString(); 66 + 67 + let MangledMenu; 68 + 69 + const typeRegex = /if\(.\.type===(.)\.(.+?)\).+?type:"(.+?)"/g; 70 + const typeMap: Record<string, string | undefined> = { 71 + checkbox: "MenuCheckboxItem", 72 + control: "MenuControlItem", 73 + groupstart: "MenuGroup", 74 + customitem: "MenuItem", 75 + radio: "MenuRadioItem", 76 + separator: "MenuSeparator" 77 + }; 78 + 79 + for (const [, modIdent, mangled, type] of code.matchAll(typeRegex)) { 80 + if (!MangledMenu) { 81 + const modId = code.match(new RegExp(`${modIdent}=.\\((\\d+?)\\)`))![1]; 82 + MangledMenu = spacepack.require(modId); 83 + } 84 + 85 + const prop = typeMap[type]; 86 + if (!prop) continue; 87 + module.exports[prop] = MangledMenu[mangled]; 88 + }
+18
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
··· 1 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 + 3 + // spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString(); 4 + let code = 5 + spacepack.require.m[ 6 + spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id 7 + ].toString(); 8 + 9 + const parserSym = code.match(/(?<=_patchMenu\(.,).+?(?=\()/)![0]; 10 + 11 + code = code.replace(/{(.):\(\)=>./, (orig, e) => `{${e}:()=>${parserSym}`); 12 + const mod = new Function("module", "exports", "require", `(${code}).apply(this, arguments)`); 13 + 14 + const exp: any = {}; 15 + mod({}, exp, require); 16 + 17 + const parser = spacepack.findFunctionByStrings(exp, "Menu API only allows Items and groups of Items as children.")!; 18 + module.exports = parser;
+19
packages/core-extensions/src/devToolsExtensions/host.ts
··· 1 + import { app, session } from "electron"; 2 + import { resolve } from "node:path"; 3 + import Logger from "@moonlight-mod/core/util/logger"; 4 + 5 + const logger = new Logger("DevTools Extensions"); 6 + 7 + app.whenReady().then(async () => { 8 + const paths = moonlightHost.getConfigOption<string[]>("devToolsExtensions", "paths") ?? []; 9 + 10 + for (const path of paths) { 11 + const resolved = resolve(path); 12 + 13 + try { 14 + await session.defaultSession.loadExtension(resolved); 15 + } catch (err) { 16 + logger.error(`Failed to load an extension in "${resolved}":`, err); 17 + } 18 + } 19 + });
+22
packages/core-extensions/src/devToolsExtensions/manifest.json
··· 1 + { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 + "id": "devToolsExtensions", 4 + "meta": { 5 + "name": "DevTools Extensions", 6 + "tagline": "Loads Chrome extensions into Electron DevTools", 7 + "authors": [ 8 + "Cynosphere" 9 + ], 10 + "tags": [ 11 + "development" 12 + ] 13 + }, 14 + "settings": { 15 + "paths": { 16 + "advice": "restart", 17 + "displayName": "Extension Paths", 18 + "type": "list" 19 + } 20 + }, 21 + "apiLevel": 2 22 + }
+4 -23
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 - ] 35 - }, 36 - function (details, callback) { 37 - callback({ cancel: true }); 38 - } 39 - ); 40 - });
+8 -18
packages/core-extensions/src/disableSentry/index.ts
··· 3 3 4 4 export const patches: Patch[] = [ 5 5 { 6 - find: "DSN:function", 6 + find: "profiledRootComponent:", 7 7 replace: { 8 8 type: PatchReplaceType.Normal, 9 - match: /default:function\(\){return .}/, 10 - replacement: 11 - 'default:function(){return require("disableSentry_stub").proxy()}' 12 - } 13 - }, 14 - { 15 - find: "window.DiscordSentry.addBreadcrumb", 16 - replace: { 17 - type: PatchReplaceType.Normal, 18 - match: /default:function\(\){return .}/, 19 - replacement: 20 - 'default:function(){return (...args)=>{moonlight.getLogger("disableSentry").debug("Sentry calling addBreadcrumb passthrough:", ...args);}}' 9 + match: /Z:\(\)=>\i/, 10 + replacement: 'Z:()=>require("disableSentry_stub").proxy()' 21 11 } 22 12 }, 23 13 { 24 - find: "initSentry:function", 14 + find: "this._sentryUtils=", 25 15 replace: { 26 16 type: PatchReplaceType.Normal, 27 - match: /initSentry:function\(\){return .}/, 28 - replacement: "default:function(){return ()=>{}}" 17 + match: /(?<=this._sentryUtils=)./, 18 + replacement: "undefined" 29 19 } 30 20 }, 31 21 { 32 22 find: "window.DiscordErrors=", 33 23 replace: { 34 24 type: PatchReplaceType.Normal, 35 - match: /uses_client_mods:\(0,.\.usesClientMods\)\(\)/, 36 - replacement: "uses_client_mods:false" 25 + match: /(?<=uses_client_mods:)./, 26 + replacement: "false" 37 27 } 38 28 } 39 29 ];
+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 }
+47 -3
packages/core-extensions/src/experiments/index.ts
··· 2 2 3 3 export const patches: Patch[] = [ 4 4 { 5 - find: "isStaffEnv:", 5 + find: "isStaffPersonal:", 6 + replace: { 7 + match: /&&null!=this\.personalConnectionId/, 8 + replacement: "||!0" 9 + } 10 + }, 11 + { 12 + find: '"scientist:triggered"', // Scientist? Triggered. 13 + replace: { 14 + match: ".personal_connection_id", 15 + replacement: ".personal_connection_id || true" 16 + } 17 + }, 18 + 19 + // Enable staff help menu 20 + { 21 + find: ".HEADER_BAR)", 6 22 replace: { 7 - match: /.\.isStaff\(\)/, 8 - replacement: "!0" 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}` 9 53 } 10 54 } 11 55 ];
+17 -1
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", 6 - "authors": ["NotNite"], 8 + "authors": ["NotNite", "Cynosphere"], 7 9 "tags": ["dangerZone"] 10 + }, 11 + "settings": { 12 + "devtools": { 13 + "advice": "reload", 14 + "displayName": "Enable staff help menu (DevTools)", 15 + "type": "boolean", 16 + "default": false 17 + }, 18 + "staffSettings": { 19 + "advice": "reload", 20 + "displayName": "Allow access to other staff settings elsewhere", 21 + "type": "boolean", 22 + "default": false 23 + } 8 24 } 9 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 + });
+83 -90
packages/core-extensions/src/moonbase/index.tsx
··· 1 - import { ExtensionWebExports, WebpackRequireType } from "@moonlight-mod/types"; 2 - import extensionsPage from "./ui/extensions"; 3 - import configPage from "./ui/config"; 1 + import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; 4 2 5 - import { CircleXIconSVG, DownloadIconSVG, TrashIconSVG } from "./types"; 6 - import ui from "./ui"; 7 - 8 - export const pageModules: (require: WebpackRequireType) => Record< 9 - string, 3 + export const patches: Patch[] = [ 10 4 { 11 - name: string; 12 - element: React.FunctionComponent; 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 + ] 13 31 } 14 - > = (require) => ({ 15 - extensions: { 16 - name: "Extensions", 17 - element: extensionsPage(require) 32 + ]; 33 + 34 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 35 + stores: { 36 + dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }] 18 37 }, 19 - config: { 20 - name: "Config", 21 - element: configPage(require) 22 - } 23 - }); 24 38 25 - export const webpackModules: ExtensionWebExports["webpackModules"] = { 26 - stores: { 39 + ui: { 27 40 dependencies: [ 28 - { ext: "common", id: "flux" }, 29 - { ext: "common", id: "fluxDispatcher" } 41 + { ext: "spacepack", id: "spacepack" }, 42 + { id: "react" }, 43 + { id: "discord/components/common/index" }, 44 + { ext: "moonbase", id: "stores" }, 45 + { ext: "moonbase", id: "ThemeDarkIcon" }, 46 + { id: "discord/modules/guild_settings/web/AppCard.css" }, 47 + { ext: "contextMenu", id: "contextMenu" }, 48 + { id: "discord/modules/modals/Modals" }, 49 + "Masks.PANEL_BUTTON", 50 + '"Missing channel in Channel.openChannelContextMenu"', 51 + ".forumOrHome]:" 30 52 ] 31 53 }, 32 54 33 - moonbase: { 55 + ThemeDarkIcon: { 56 + dependencies: [{ ext: "common", id: "icons" }, { id: "react" }] 57 + }, 58 + 59 + settings: { 34 60 dependencies: [ 35 61 { ext: "spacepack", id: "spacepack" }, 36 62 { ext: "settings", id: "settings" }, 37 - { ext: "common", id: "react" }, 38 - { ext: "common", id: "components" }, 63 + { id: "react" }, 64 + { ext: "moonbase", id: "ui" }, 65 + { ext: "contextMenu", id: "contextMenu" }, 66 + ':"USER_SETTINGS_MODAL_SET_SECTION"' 67 + ], 68 + entrypoint: true 69 + }, 70 + 71 + updates: { 72 + dependencies: [ 73 + { id: "react" }, 39 74 { ext: "moonbase", id: "stores" }, 40 - DownloadIconSVG, 41 - TrashIconSVG, 42 - CircleXIconSVG, 43 - "Masks.PANEL_BUTTON", 44 - "removeButtonContainer:", 45 - '"Missing channel in Channel.openChannelContextMenu"', 46 - ".default.HEADER_BAR" 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" } 47 83 ], 48 - entrypoint: true, 49 - run: (module, exports, require) => { 50 - const settings = require("settings_settings").Settings; 51 - const React = require("common_react"); 52 - const spacepack = require("spacepack_spacepack").spacepack; 53 - const { MoonbaseSettingsStore } = 54 - require("moonbase_stores") as typeof import("./webpackModules/stores"); 55 - 56 - const addSection = (name: string, element: React.FunctionComponent) => { 57 - settings.addSection(name, name, element, null, -2, { 58 - stores: [MoonbaseSettingsStore], 59 - element: () => { 60 - // Require it here because lazy loading SUX 61 - const SettingsNotice = 62 - spacepack.findByCode("onSaveButtonColor")[0].exports.default; 63 - return ( 64 - <SettingsNotice 65 - submitting={MoonbaseSettingsStore.submitting} 66 - onReset={() => { 67 - MoonbaseSettingsStore.reset(); 68 - }} 69 - onSave={() => { 70 - MoonbaseSettingsStore.writeConfig(); 71 - }} 72 - /> 73 - ); 74 - } 75 - }); 76 - }; 84 + entrypoint: true 85 + }, 77 86 78 - if (moonlight.getConfigOption<boolean>("moonbase", "sections")) { 79 - const pages = pageModules(require); 80 - 81 - const { Text } = require("common_components"); 82 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 87 + moonbase: { 88 + dependencies: [{ ext: "moonbase", id: "stores" }] 89 + }, 83 90 84 - settings.addHeader("Moonbase", -2); 85 - for (const page of Object.values(pages)) { 86 - addSection(page.name, () => ( 87 - <> 88 - <Text 89 - className={Margins.marginBottom20} 90 - variant="heading-lg/semibold" 91 - tag="h2" 92 - > 93 - Extensions 94 - </Text> 95 - <page.element /> 96 - </> 97 - )); 98 - } 99 - } else { 100 - addSection("Moonbase", ui(require)); 101 - } 102 - } 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 + ] 103 100 } 104 101 }; 105 - 106 - export const styles = [ 107 - ".moonbase-settings > :first-child { margin-top: 0px; }" 108 - ];
+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 + }
+27 -25
packages/core-extensions/src/moonbase/types.ts
··· 1 - import { DetectedExtension, ExtensionManifest } from "types/src"; 2 - 3 - export const DownloadIconSVG = 4 - "M12 2a1 1 0 0 1 1 1v10.59l3.3-3.3a1 1 0 1 1 1.4 1.42l-5 5a1 1 0 0 1-1.4 0l-5-5a1 1 0 1 1 1.4-1.42l3.3 3.3V3a1 1 0 0 1 1-1ZM3 20a1 1 0 1 0 0 2h18a1 1 0 1 0 0-2H3Z"; 5 - export const TrashIconSVG = 6 - "M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z"; 7 - export const CircleXIconSVG = 8 - "M7.02799 0.333252C3.346 0.333252 0.361328 3.31792 0.361328 6.99992C0.361328 10.6819 3.346 13.6666 7.02799 13.6666C10.71 13.6666 13.6947 10.6819 13.6947 6.99992C13.6947 3.31792 10.7093 0.333252 7.02799 0.333252ZM10.166 9.19525L9.22333 10.1379L7.02799 7.94325L4.83266 10.1379L3.89 9.19525L6.08466 6.99992L3.88933 4.80459L4.832 3.86259L7.02733 6.05792L9.22266 3.86259L10.1653 4.80459L7.97066 6.99992L10.166 9.19525Z"; 9 - export const DangerIconSVG = 10 - "M12 23a11 11 0 1 0 0-22 11 11 0 0 0 0 22Zm1.44-15.94L13.06 14a1.06 1.06 0 0 1-2.12 0l-.38-6.94a1 1 0 0 1 1-1.06h.88a1 1 0 0 1 1 1.06Zm-.19 10.69a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Z"; 11 - export const ChevronSmallDownIconSVG = 12 - "M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z"; 13 - export const ChevronSmallUpIconSVG = 14 - "M7.41 16.0001L12 11.4201L16.59 16.0001L18 14.5901L12 8.59006L6 14.5901L7.41 16.0001Z"; 15 - export const ArrowsUpDownIconSVG = 16 - "M3.81962 11.3333L3.81962 1.33325L5.52983 1.33325L5.52985 11.3333L7.46703 9.36658L8.66663 10.5916L4.67068 14.6666L0.666626 10.5916L1.86622 9.34158L3.81962 11.3333Z"; 1 + import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 2 + import { DetectedExtension, ExtensionManifest, MoonlightBranch } from "@moonlight-mod/types"; 17 3 18 4 export type MoonbaseNatives = { 19 - fetchRepositories( 20 - repos: string[] 21 - ): Promise<Record<string, RepositoryManifest[]>>; 22 - installExtension( 23 - manifest: RepositoryManifest, 24 - url: string, 25 - repo: string 26 - ): 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>; 27 10 deleteExtension(id: string): Promise<void>; 28 - getExtensionConfig(id: string, key: string): any; 29 11 }; 30 12 31 13 export type RepositoryManifest = ExtensionManifest & { ··· 40 22 41 23 export type MoonbaseExtension = { 42 24 id: string; 25 + uniqueId: number; 43 26 manifest: ExtensionManifest | RepositoryManifest; 44 27 source: DetectedExtension["source"]; 45 28 state: ExtensionState; 29 + compat: ExtensionCompat; 30 + hasUpdate: boolean; 31 + changelog?: string; 32 + settingsOverride?: ExtensionManifest["settings"]; 46 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 + }
-157
packages/core-extensions/src/moonbase/ui/config/index.tsx
··· 1 - import { LogLevel, WebpackRequireType } from "@moonlight-mod/types"; 2 - import { CircleXIconSVG } from "../../types"; 3 - 4 - const logLevels = Object.values(LogLevel).filter( 5 - (v) => typeof v === "string" 6 - ) as string[]; 7 - 8 - export default (require: WebpackRequireType) => { 9 - const React = require("common_react"); 10 - const spacepack = require("spacepack_spacepack").spacepack; 11 - const CommonComponents = require("common_components"); 12 - const { 13 - FormDivider, 14 - FormItem, 15 - FormText, 16 - FormSwitch, 17 - TextInput, 18 - Flex, 19 - Button, 20 - SingleSelect 21 - } = CommonComponents; 22 - 23 - const { MoonbaseSettingsStore } = 24 - require("moonbase_stores") as typeof import("../../webpackModules/stores"); 25 - 26 - const FormClasses = spacepack.findByCode("dividerDefault:")[0].exports; 27 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 28 - 29 - const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0] 30 - .exports; 31 - const CircleXIcon = spacepack.findByCode(CircleXIconSVG)[0].exports.default; 32 - function RemoveEntryButton({ onClick }: { onClick: () => void }) { 33 - const { Tooltip, Clickable } = CommonComponents; 34 - return ( 35 - <div className={RemoveButtonClasses.removeButtonContainer}> 36 - <Tooltip text="Remove entry" position="top"> 37 - {(props: any) => ( 38 - <Clickable 39 - {...props} 40 - className={RemoveButtonClasses.removeButton} 41 - onClick={onClick} 42 - > 43 - <CircleXIcon width={24} height={24} /> 44 - </Clickable> 45 - )} 46 - </Tooltip> 47 - </div> 48 - ); 49 - } 50 - 51 - function ArrayFormItem({ 52 - config 53 - }: { 54 - config: "repositories" | "devSearchPaths"; 55 - }) { 56 - const items = MoonbaseSettingsStore.getConfigOption(config) ?? []; 57 - return ( 58 - <Flex 59 - style={{ 60 - gap: "20px" 61 - }} 62 - direction={Flex.Direction.VERTICAL} 63 - > 64 - {items.map((val, i) => ( 65 - <div 66 - key={i} 67 - style={{ 68 - display: "grid", 69 - height: "32px", 70 - gap: "8px", 71 - gridTemplateColumns: "1fr 32px", 72 - alignItems: "center" 73 - }} 74 - > 75 - <TextInput 76 - size={TextInput.Sizes.DEFAULT} 77 - value={val} 78 - onChange={(newVal: string) => { 79 - items[i] = newVal; 80 - MoonbaseSettingsStore.setConfigOption(config, items); 81 - }} 82 - /> 83 - <RemoveEntryButton 84 - onClick={() => { 85 - items.splice(i, 1); 86 - MoonbaseSettingsStore.setConfigOption(config, items); 87 - }} 88 - /> 89 - </div> 90 - ))} 91 - 92 - <Button 93 - look={Button.Looks.FILLED} 94 - color={Button.Colors.GREEN} 95 - size={Button.Sizes.SMALL} 96 - style={{ 97 - marginTop: "10px" 98 - }} 99 - onClick={() => { 100 - items.push(""); 101 - MoonbaseSettingsStore.setConfigOption(config, items); 102 - }} 103 - > 104 - Add new entry 105 - </Button> 106 - </Flex> 107 - ); 108 - } 109 - 110 - return function ConfigPage() { 111 - return ( 112 - <> 113 - <FormItem title="Repositories"> 114 - <FormText className={Margins.marginBottom4}> 115 - A list of remote repositories to display extensions from 116 - </FormText> 117 - <ArrayFormItem config="repositories" /> 118 - </FormItem> 119 - <FormDivider className={FormClasses.dividerDefault} /> 120 - <FormItem 121 - title="Extension search paths" 122 - className={Margins.marginTop20} 123 - > 124 - <FormText className={Margins.marginBottom4}> 125 - A list of local directories to search for built extensions 126 - </FormText> 127 - <ArrayFormItem config="devSearchPaths" /> 128 - </FormItem> 129 - <FormDivider className={FormClasses.dividerDefault} /> 130 - <FormSwitch 131 - className={Margins.marginTop20} 132 - value={MoonbaseSettingsStore.getConfigOption("patchAll")} 133 - onChange={(value: boolean) => { 134 - MoonbaseSettingsStore.setConfigOption("patchAll", value); 135 - }} 136 - note="Wraps every webpack module in a function, separating them in DevTools" 137 - > 138 - Patch all 139 - </FormSwitch> 140 - <FormItem title="Log level"> 141 - <SingleSelect 142 - autofocus={false} 143 - clearable={false} 144 - value={MoonbaseSettingsStore.getConfigOption("loggerLevel")} 145 - options={logLevels.map((o) => ({ 146 - value: o.toLowerCase(), 147 - label: o[0] + o.slice(1).toLowerCase() 148 - }))} 149 - onChange={(v) => 150 - MoonbaseSettingsStore.setConfigOption("loggerLevel", v) 151 - } 152 - /> 153 - </FormItem> 154 - </> 155 - ); 156 - }; 157 - };
-223
packages/core-extensions/src/moonbase/ui/extensions/card.tsx
··· 1 - import WebpackRequire from "@moonlight-mod/types/discord/require"; 2 - import { 3 - DangerIconSVG, 4 - DownloadIconSVG, 5 - ExtensionState, 6 - TrashIconSVG 7 - } from "../../types"; 8 - import { ExtensionLoadSource } from "@moonlight-mod/types"; 9 - import info from "./info"; 10 - import settings from "./settings"; 11 - 12 - export enum ExtensionPage { 13 - Info, 14 - Description, 15 - Settings 16 - } 17 - 18 - export default (require: typeof WebpackRequire) => { 19 - const React = require("common_react"); 20 - const spacepack = require("spacepack_spacepack").spacepack; 21 - const CommonComponents = require("common_components"); 22 - const Flux = require("common_flux"); 23 - 24 - const { ExtensionInfo } = info(require); 25 - const Settings = settings(require); 26 - const { MoonbaseSettingsStore } = 27 - require("moonbase_stores") as typeof import("../../webpackModules/stores"); 28 - 29 - const UserProfileClasses = spacepack.findByCode( 30 - "tabBarContainer", 31 - "topSection" 32 - )[0].exports; 33 - 34 - const DownloadIcon = 35 - spacepack.findByCode(DownloadIconSVG)[0].exports.DownloadIcon; 36 - const TrashIcon = spacepack.findByCode(TrashIconSVG)[0].exports.default; 37 - const DangerIcon = 38 - spacepack.findByCode(DangerIconSVG)[0].exports.CircleExclamationPointIcon; 39 - 40 - const PanelButton = 41 - spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.default; 42 - 43 - return function ExtensionCard({ id }: { id: string }) { 44 - const [tab, setTab] = React.useState(ExtensionPage.Info); 45 - const [restartNeeded, setRestartNeeded] = React.useState(false); 46 - 47 - const { ext, enabled, busy, update } = Flux.useStateFromStores( 48 - [MoonbaseSettingsStore], 49 - () => { 50 - return { 51 - ext: MoonbaseSettingsStore.getExtension(id), 52 - enabled: MoonbaseSettingsStore.getExtensionEnabled(id), 53 - busy: MoonbaseSettingsStore.busy, 54 - update: MoonbaseSettingsStore.getExtensionUpdate(id) 55 - }; 56 - } 57 - ); 58 - 59 - // Why it work like that :sob: 60 - if (ext == null) return <></>; 61 - 62 - const { 63 - Card, 64 - CardClasses, 65 - Flex, 66 - Text, 67 - MarkdownParser, 68 - Switch, 69 - TabBar, 70 - Button 71 - } = CommonComponents; 72 - 73 - const tagline = ext.manifest?.meta?.tagline; 74 - const settings = ext.manifest?.settings; 75 - const description = ext.manifest?.meta?.description; 76 - 77 - return ( 78 - <Card editable={true} className={CardClasses.card}> 79 - <div className={CardClasses.cardHeader}> 80 - <Flex direction={Flex.Direction.VERTICAL}> 81 - <Flex direction={Flex.Direction.HORIZONTAL}> 82 - <Text variant="text-md/semibold"> 83 - {ext.manifest?.meta?.name ?? ext.id} 84 - </Text> 85 - </Flex> 86 - 87 - {tagline != null && ( 88 - <Text variant="text-sm/normal"> 89 - {MarkdownParser.parse(tagline)} 90 - </Text> 91 - )} 92 - </Flex> 93 - 94 - <Flex 95 - direction={Flex.Direction.HORIZONTAL} 96 - align={Flex.Align.END} 97 - justify={Flex.Justify.END} 98 - > 99 - {ext.state === ExtensionState.NotDownloaded ? ( 100 - <Button 101 - color={Button.Colors.BRAND} 102 - submitting={busy} 103 - onClick={() => { 104 - MoonbaseSettingsStore.installExtension(id); 105 - }} 106 - > 107 - Install 108 - </Button> 109 - ) : ( 110 - <div 111 - // too lazy to learn how <Flex /> works lmao 112 - style={{ 113 - display: "flex", 114 - alignItems: "center", 115 - gap: "1rem" 116 - }} 117 - > 118 - {ext.source.type === ExtensionLoadSource.Normal && ( 119 - <PanelButton 120 - icon={TrashIcon} 121 - tooltipText="Delete" 122 - onClick={() => { 123 - MoonbaseSettingsStore.deleteExtension(id); 124 - }} 125 - /> 126 - )} 127 - 128 - {update !== null && ( 129 - <PanelButton 130 - icon={DownloadIcon} 131 - tooltipText="Update" 132 - onClick={() => { 133 - MoonbaseSettingsStore.installExtension(id); 134 - }} 135 - /> 136 - )} 137 - 138 - {restartNeeded && ( 139 - <PanelButton 140 - icon={() => ( 141 - <DangerIcon 142 - color={CommonComponents.tokens.colors.STATUS_DANGER} 143 - /> 144 - )} 145 - onClick={() => window.location.reload()} 146 - tooltipText="You will need to reload/restart your client for this extension to work properly." 147 - /> 148 - )} 149 - 150 - <Switch 151 - checked={enabled} 152 - onChange={() => { 153 - setRestartNeeded(true); 154 - MoonbaseSettingsStore.setExtensionEnabled(id, !enabled); 155 - }} 156 - /> 157 - </div> 158 - )} 159 - </Flex> 160 - </div> 161 - 162 - <div className={UserProfileClasses.body}> 163 - {(description != null || settings != null) && ( 164 - <div 165 - className={UserProfileClasses.tabBarContainer} 166 - style={{ 167 - padding: "0 10px" 168 - }} 169 - > 170 - <TabBar 171 - selectedItem={tab} 172 - type="top" 173 - onItemSelect={setTab} 174 - className={UserProfileClasses.tabBar} 175 - > 176 - <TabBar.Item 177 - className={UserProfileClasses.tabBarItem} 178 - id={ExtensionPage.Info} 179 - > 180 - Info 181 - </TabBar.Item> 182 - 183 - {description != null && ( 184 - <TabBar.Item 185 - className={UserProfileClasses.tabBarItem} 186 - id={ExtensionPage.Description} 187 - > 188 - Description 189 - </TabBar.Item> 190 - )} 191 - 192 - {settings != null && ( 193 - <TabBar.Item 194 - className={UserProfileClasses.tabBarItem} 195 - id={ExtensionPage.Settings} 196 - > 197 - Settings 198 - </TabBar.Item> 199 - )} 200 - </TabBar> 201 - </div> 202 - )} 203 - 204 - <Flex 205 - justify={Flex.Justify.START} 206 - wrap={Flex.Wrap.WRAP} 207 - style={{ 208 - padding: "16px 16px" 209 - }} 210 - > 211 - {tab === ExtensionPage.Info && <ExtensionInfo ext={ext} />} 212 - {tab === ExtensionPage.Description && ( 213 - <Text variant="text-md/normal"> 214 - {MarkdownParser.parse(description ?? "*No description*")} 215 - </Text> 216 - )} 217 - {tab === ExtensionPage.Settings && <Settings ext={ext} />} 218 - </Flex> 219 - </div> 220 - </Card> 221 - ); 222 - }; 223 - };
-373
packages/core-extensions/src/moonbase/ui/extensions/filterBar.tsx
··· 1 - import { WebpackRequireType } from "@moonlight-mod/types"; 2 - import { tagNames } from "./info"; 3 - import { 4 - ArrowsUpDownIconSVG, 5 - ChevronSmallDownIconSVG, 6 - ChevronSmallUpIconSVG 7 - } from "../../types"; 8 - 9 - export enum Filter { 10 - Core = 1 << 0, 11 - Normal = 1 << 1, 12 - Developer = 1 << 2, 13 - Enabled = 1 << 3, 14 - Disabled = 1 << 4, 15 - Installed = 1 << 5, 16 - Repository = 1 << 6 17 - } 18 - export const defaultFilter = ~(~0 << 7); 19 - 20 - export default async (require: WebpackRequireType) => { 21 - const spacepack = require("spacepack_spacepack").spacepack; 22 - const React = require("common_react"); 23 - const Flux = require("common_flux"); 24 - const { WindowStore } = require("common_stores"); 25 - 26 - const { 27 - Button, 28 - Text, 29 - Heading, 30 - Popout, 31 - Dialog 32 - } = require("common_components"); 33 - 34 - const channelModule = 35 - require.m[ 36 - spacepack.findByCode( 37 - '"Missing channel in Channel.openChannelContextMenu"' 38 - )[0].id 39 - ].toString(); 40 - const moduleId = channelModule.match(/webpackId:"(.+?)"/)![1]; 41 - await require.el(moduleId); 42 - 43 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 44 - const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0] 45 - .exports; 46 - const FilterDialogClasses = spacepack.findByCode( 47 - "countContainer:", 48 - "tagContainer:" 49 - )[0].exports; 50 - const FilterBarClasses = spacepack.findByCode("tagsButtonWithCount:")[0] 51 - .exports; 52 - 53 - const TagItem = spacepack.findByCode("IncreasedActivityForumTagPill:")[0] 54 - .exports.default; 55 - 56 - const ChevronSmallDownIcon = spacepack.findByCode(ChevronSmallDownIconSVG)[0] 57 - .exports.default; 58 - const ChevronSmallUpIcon = spacepack.findByCode(ChevronSmallUpIconSVG)[0] 59 - .exports.default; 60 - const ArrowsUpDownIcon = 61 - spacepack.findByCode(ArrowsUpDownIconSVG)[0].exports.default; 62 - 63 - function toggleTag( 64 - selectedTags: Set<string>, 65 - setSelectedTags: (tags: Set<string>) => void, 66 - tag: string 67 - ) { 68 - const newState = new Set(selectedTags); 69 - if (newState.has(tag)) newState.delete(tag); 70 - else newState.add(tag); 71 - setSelectedTags(newState); 72 - } 73 - 74 - function FilterButtonPopout({ 75 - filter, 76 - setFilter, 77 - closePopout 78 - }: { 79 - filter: Filter; 80 - setFilter: (filter: Filter) => void; 81 - closePopout: () => void; 82 - }) { 83 - const { 84 - Menu, 85 - MenuItem, 86 - MenuGroup, 87 - MenuCheckboxItem 88 - } = require("common_components"); 89 - 90 - const toggleFilter = (set: Filter) => 91 - setFilter(filter & set ? filter & ~set : filter | set); 92 - 93 - return ( 94 - <div className={SortMenuClasses.container}> 95 - <Menu navId="sort-filter" hideScrollbar={true} onClose={closePopout}> 96 - <MenuGroup label="Type"> 97 - <MenuCheckboxItem 98 - id="t-core" 99 - label="Core" 100 - checked={filter & Filter.Core} 101 - action={() => toggleFilter(Filter.Core)} 102 - /> 103 - <MenuCheckboxItem 104 - id="t-normal" 105 - label="Normal" 106 - checked={filter & Filter.Normal} 107 - action={() => toggleFilter(Filter.Normal)} 108 - /> 109 - <MenuCheckboxItem 110 - id="t-developer" 111 - label="Developer" 112 - checked={filter & Filter.Developer} 113 - action={() => toggleFilter(Filter.Developer)} 114 - /> 115 - </MenuGroup> 116 - <MenuGroup label="State"> 117 - <MenuCheckboxItem 118 - id="s-enabled" 119 - label="Enabled" 120 - checked={filter & Filter.Enabled} 121 - action={() => toggleFilter(Filter.Enabled)} 122 - /> 123 - <MenuCheckboxItem 124 - id="s-disabled" 125 - label="Disabled" 126 - checked={filter & Filter.Disabled} 127 - action={() => toggleFilter(Filter.Disabled)} 128 - /> 129 - </MenuGroup> 130 - <MenuGroup label="Location"> 131 - <MenuCheckboxItem 132 - id="l-installed" 133 - label="Installed" 134 - checked={filter & Filter.Installed} 135 - action={() => toggleFilter(Filter.Installed)} 136 - /> 137 - <MenuCheckboxItem 138 - id="l-repository" 139 - label="Repository" 140 - checked={filter & Filter.Repository} 141 - action={() => toggleFilter(Filter.Repository)} 142 - /> 143 - </MenuGroup> 144 - <MenuGroup> 145 - <MenuItem 146 - id="reset-all" 147 - className={SortMenuClasses.clearText} 148 - label={ 149 - <Text variant="text-sm/medium" color="none"> 150 - Reset to default 151 - </Text> 152 - } 153 - action={() => { 154 - setFilter(defaultFilter); 155 - closePopout(); 156 - }} 157 - /> 158 - </MenuGroup> 159 - </Menu> 160 - </div> 161 - ); 162 - } 163 - 164 - function TagButtonPopout({ 165 - selectedTags, 166 - setSelectedTags, 167 - setPopoutRef, 168 - closePopout 169 - }: any) { 170 - return ( 171 - <Dialog ref={setPopoutRef} className={FilterDialogClasses.container}> 172 - <div className={FilterDialogClasses.header}> 173 - <div className={FilterDialogClasses.headerLeft}> 174 - <Heading 175 - color="interactive-normal" 176 - variant="text-xs/bold" 177 - className={FilterDialogClasses.headerText} 178 - > 179 - Select tags 180 - </Heading> 181 - <div className={FilterDialogClasses.countContainer}> 182 - <Text 183 - className={FilterDialogClasses.countText} 184 - color="none" 185 - variant="text-xs/medium" 186 - > 187 - {selectedTags.size} 188 - </Text> 189 - </div> 190 - </div> 191 - </div> 192 - <div className={FilterDialogClasses.tagContainer}> 193 - {Object.keys(tagNames).map((tag) => ( 194 - <TagItem 195 - key={tag} 196 - className={FilterDialogClasses.tag} 197 - tag={{ name: tagNames[tag as keyof typeof tagNames] }} 198 - onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 199 - selected={selectedTags.has(tag)} 200 - /> 201 - ))} 202 - </div> 203 - <div className={FilterDialogClasses.separator} /> 204 - <Button 205 - look={Button.Looks.LINK} 206 - size={Button.Sizes.MIN} 207 - color={Button.Colors.CUSTOM} 208 - className={FilterDialogClasses.clear} 209 - onClick={() => { 210 - setSelectedTags(new Set()); 211 - closePopout(); 212 - }} 213 - > 214 - <Text variant="text-sm/medium" color="text-link"> 215 - Clear all 216 - </Text> 217 - </Button> 218 - </Dialog> 219 - ); 220 - } 221 - 222 - return function FilterBar({ 223 - filter, 224 - setFilter, 225 - selectedTags, 226 - setSelectedTags 227 - }: { 228 - filter: Filter; 229 - setFilter: (filter: Filter) => void; 230 - selectedTags: Set<string>; 231 - setSelectedTags: (tags: Set<string>) => void; 232 - }) { 233 - const windowSize = Flux.useStateFromStores([WindowStore], () => 234 - WindowStore.windowSize() 235 - ); 236 - 237 - const tagsContainer = React.useRef<HTMLDivElement>(null); 238 - const tagListInner = React.useRef<HTMLDivElement>(null); 239 - const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0); 240 - React.useLayoutEffect(() => { 241 - if (tagsContainer.current === null || tagListInner.current === null) 242 - return; 243 - const { left: containerX, top: containerY } = 244 - tagsContainer.current.getBoundingClientRect(); 245 - let offset = 0; 246 - for (const child of tagListInner.current.children) { 247 - const { 248 - right: childX, 249 - top: childY, 250 - height 251 - } = child.getBoundingClientRect(); 252 - if (childY - containerY > height) break; 253 - const newOffset = childX - containerX; 254 - if (newOffset > offset) { 255 - offset = newOffset; 256 - } 257 - } 258 - setTagsButtonOffset(offset); 259 - }, [windowSize]); 260 - 261 - return ( 262 - <div 263 - ref={tagsContainer} 264 - style={{ 265 - paddingTop: "12px" 266 - }} 267 - className={`${FilterBarClasses.tagsContainer} ${Margins.marginBottom8}`} 268 - > 269 - <Popout 270 - renderPopout={({ closePopout }: any) => ( 271 - <FilterButtonPopout 272 - filter={filter} 273 - setFilter={setFilter} 274 - closePopout={closePopout} 275 - /> 276 - )} 277 - position="bottom" 278 - align="left" 279 - > 280 - {(props: any, { isShown }: { isShown: boolean }) => ( 281 - <Button 282 - {...props} 283 - size={Button.Sizes.MIN} 284 - color={Button.Colors.CUSTOM} 285 - className={FilterBarClasses.sortDropdown} 286 - innerClassName={FilterBarClasses.sortDropdownInner} 287 - > 288 - <ArrowsUpDownIcon /> 289 - <Text 290 - className={FilterBarClasses.sortDropdownText} 291 - variant="text-sm/medium" 292 - color="interactive-normal" 293 - > 294 - Sort & filter 295 - </Text> 296 - {isShown ? ( 297 - <ChevronSmallUpIcon size={20} /> 298 - ) : ( 299 - <ChevronSmallDownIcon size={20} /> 300 - )} 301 - </Button> 302 - )} 303 - </Popout> 304 - <div className={FilterBarClasses.divider} /> 305 - <div className={FilterBarClasses.tagList}> 306 - <div ref={tagListInner} className={FilterBarClasses.tagListInner}> 307 - {Object.keys(tagNames).map((tag) => ( 308 - <TagItem 309 - key={tag} 310 - className={FilterBarClasses.tag} 311 - tag={{ name: tagNames[tag as keyof typeof tagNames] }} 312 - onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 313 - selected={selectedTags.has(tag)} 314 - /> 315 - ))} 316 - </div> 317 - </div> 318 - <Popout 319 - renderPopout={({ setPopoutRef, closePopout }: any) => ( 320 - <TagButtonPopout 321 - selectedTags={selectedTags} 322 - setSelectedTags={setSelectedTags} 323 - setPopoutRef={setPopoutRef} 324 - closePopout={closePopout} 325 - /> 326 - )} 327 - position="bottom" 328 - align="right" 329 - > 330 - {(props: any, { isShown }: { isShown: boolean }) => ( 331 - <Button 332 - {...props} 333 - size={Button.Sizes.MIN} 334 - color={Button.Colors.CUSTOM} 335 - style={{ 336 - left: tagsButtonOffset 337 - }} 338 - // TODO: Use Discord's class name utility 339 - className={`${FilterBarClasses.tagsButton} ${ 340 - selectedTags.size > 0 341 - ? FilterBarClasses.tagsButtonWithCount 342 - : "" 343 - }`} 344 - innerClassName={FilterBarClasses.tagsButtonInner} 345 - > 346 - {selectedTags.size > 0 ? ( 347 - <div 348 - style={{ boxSizing: "content-box" }} 349 - className={FilterBarClasses.countContainer} 350 - > 351 - <Text 352 - className={FilterBarClasses.countText} 353 - color="none" 354 - variant="text-xs/medium" 355 - > 356 - {selectedTags.size} 357 - </Text> 358 - </div> 359 - ) : ( 360 - <>All</> 361 - )} 362 - {isShown ? ( 363 - <ChevronSmallUpIcon size={20} /> 364 - ) : ( 365 - <ChevronSmallDownIcon size={20} /> 366 - )} 367 - </Button> 368 - )} 369 - </Popout> 370 - </div> 371 - ); 372 - }; 373 - };
-118
packages/core-extensions/src/moonbase/ui/extensions/index.tsx
··· 1 - import { 2 - ExtensionLoadSource, 3 - ExtensionTag, 4 - WebpackRequireType 5 - } from "@moonlight-mod/types"; 6 - import { ExtensionState } from "../../types"; 7 - import filterBar, { Filter, defaultFilter } from "./filterBar"; 8 - import card from "./card"; 9 - 10 - export default (require: WebpackRequireType) => { 11 - const React = require("common_react"); 12 - const spacepack = require("spacepack_spacepack").spacepack; 13 - const Flux = require("common_flux"); 14 - 15 - const { MoonbaseSettingsStore } = 16 - require("moonbase_stores") as typeof import("../../webpackModules/stores"); 17 - 18 - const ExtensionCard = card(require); 19 - const FilterBar = React.lazy(() => 20 - filterBar(require).then((c) => ({ default: c })) 21 - ); 22 - 23 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 24 - const SearchBar = spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0] 25 - .exports.default; 26 - 27 - return function ExtensionsPage() { 28 - const { extensions, savedFilter } = Flux.useStateFromStoresObject( 29 - [MoonbaseSettingsStore], 30 - () => { 31 - return { 32 - extensions: MoonbaseSettingsStore.extensions, 33 - savedFilter: MoonbaseSettingsStore.getExtensionConfig( 34 - "moonbase", 35 - "filter" 36 - ) 37 - }; 38 - } 39 - ); 40 - 41 - const [query, setQuery] = React.useState(""); 42 - 43 - let filter: Filter, setFilter: (filter: Filter) => void; 44 - if (moonlight.getConfigOption<boolean>("moonbase", "saveFilter")) { 45 - filter = savedFilter ?? defaultFilter; 46 - setFilter = (filter) => 47 - MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter); 48 - } else { 49 - const state = React.useState(defaultFilter); 50 - filter = state[0]; 51 - setFilter = state[1]; 52 - } 53 - const [selectedTags, setSelectedTags] = React.useState(new Set<string>()); 54 - const sorted = Object.values(extensions).sort((a, b) => { 55 - const aName = a.manifest.meta?.name ?? a.id; 56 - const bName = b.manifest.meta?.name ?? b.id; 57 - return aName.localeCompare(bName); 58 - }); 59 - 60 - const filtered = sorted.filter( 61 - (ext) => 62 - (ext.manifest.meta?.name?.toLowerCase().includes(query) || 63 - ext.manifest.meta?.tagline?.toLowerCase().includes(query) || 64 - ext.manifest.meta?.description?.toLowerCase().includes(query)) && 65 - [...selectedTags.values()].every( 66 - (tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag) 67 - ) && 68 - // This seems very bad, sorry 69 - !( 70 - (!(filter & Filter.Core) && 71 - ext.source.type === ExtensionLoadSource.Core) || 72 - (!(filter & Filter.Normal) && 73 - ext.source.type === ExtensionLoadSource.Normal) || 74 - (!(filter & Filter.Developer) && 75 - ext.source.type === ExtensionLoadSource.Developer) || 76 - (!(filter & Filter.Enabled) && 77 - MoonbaseSettingsStore.getExtensionEnabled(ext.id)) || 78 - (!(filter & Filter.Disabled) && 79 - !MoonbaseSettingsStore.getExtensionEnabled(ext.id)) || 80 - (!(filter & Filter.Installed) && 81 - ext.state !== ExtensionState.NotDownloaded) || 82 - (!(filter & Filter.Repository) && 83 - ext.state === ExtensionState.NotDownloaded) 84 - ) 85 - ); 86 - 87 - return ( 88 - <> 89 - <SearchBar 90 - size={SearchBar.Sizes.MEDIUM} 91 - query={query} 92 - onChange={(v: string) => setQuery(v.toLowerCase())} 93 - onClear={() => setQuery("")} 94 - autoFocus={true} 95 - autoComplete="off" 96 - inputProps={{ 97 - autoCapitalize: "none", 98 - autoCorrect: "off", 99 - spellCheck: "false" 100 - }} 101 - /> 102 - <React.Suspense 103 - fallback={<div className={Margins.marginBottom20}></div>} 104 - > 105 - <FilterBar 106 - filter={filter} 107 - setFilter={setFilter} 108 - selectedTags={selectedTags} 109 - setSelectedTags={setSelectedTags} 110 - /> 111 - </React.Suspense> 112 - {filtered.map((ext) => ( 113 - <ExtensionCard id={ext.id} key={ext.id} /> 114 - ))} 115 - </> 116 - ); 117 - }; 118 - };
-209
packages/core-extensions/src/moonbase/ui/extensions/info.tsx
··· 1 - import WebpackRequire from "@moonlight-mod/types/discord/require"; 2 - import { ExtensionTag } from "@moonlight-mod/types"; 3 - import { MoonbaseExtension } from "../../types"; 4 - 5 - type Dependency = { 6 - id: string; 7 - type: DependencyType; 8 - }; 9 - 10 - enum DependencyType { 11 - Dependency = "dependency", 12 - Optional = "optional", 13 - Incompatible = "incompatible" 14 - } 15 - 16 - export const tagNames: Record<ExtensionTag, string> = { 17 - [ExtensionTag.Accessibility]: "Accessibility", 18 - [ExtensionTag.Appearance]: "Appearance", 19 - [ExtensionTag.Chat]: "Chat", 20 - [ExtensionTag.Commands]: "Commands", 21 - [ExtensionTag.ContextMenu]: "Context Menu", 22 - [ExtensionTag.DangerZone]: "Danger Zone", 23 - [ExtensionTag.Development]: "Development", 24 - [ExtensionTag.Fixes]: "Fixes", 25 - [ExtensionTag.Fun]: "Fun", 26 - [ExtensionTag.Markdown]: "Markdown", 27 - [ExtensionTag.Voice]: "Voice", 28 - [ExtensionTag.Privacy]: "Privacy", 29 - [ExtensionTag.Profiles]: "Profiles", 30 - [ExtensionTag.QualityOfLife]: "Quality of Life", 31 - [ExtensionTag.Library]: "Library" 32 - }; 33 - 34 - export default (require: typeof WebpackRequire) => { 35 - const React = require("common_react"); 36 - const spacepack = require("spacepack_spacepack").spacepack; 37 - 38 - const CommonComponents = require("common_components"); 39 - const UserInfoClasses = spacepack.findByCode( 40 - "infoScroller", 41 - "userInfoSection", 42 - "userInfoSectionHeader" 43 - )[0].exports; 44 - 45 - const { MoonbaseSettingsStore } = 46 - require("moonbase_stores") as typeof import("../../webpackModules/stores"); 47 - 48 - function InfoSection({ 49 - title, 50 - children 51 - }: { 52 - title: string; 53 - children: React.ReactNode; 54 - }) { 55 - return ( 56 - <div 57 - style={{ 58 - marginRight: "1em" 59 - }} 60 - > 61 - <CommonComponents.Text 62 - variant="eyebrow" 63 - className={UserInfoClasses.userInfoSectionHeader} 64 - > 65 - {title} 66 - </CommonComponents.Text> 67 - 68 - <CommonComponents.Text variant="text-sm/normal"> 69 - {children} 70 - </CommonComponents.Text> 71 - </div> 72 - ); 73 - } 74 - 75 - function Badge({ 76 - color, 77 - children 78 - }: { 79 - color: string; 80 - children: React.ReactNode; 81 - }) { 82 - return ( 83 - <span 84 - style={{ 85 - borderRadius: ".1875rem", 86 - padding: "0 0.275rem", 87 - marginRight: "0.4em", 88 - backgroundColor: color, 89 - color: "#fff" 90 - }} 91 - > 92 - {children} 93 - </span> 94 - ); 95 - } 96 - 97 - function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) { 98 - const authors = ext.manifest?.meta?.authors; 99 - const tags = ext.manifest?.meta?.tags; 100 - const version = ext.manifest?.version; 101 - 102 - const dependencies: Dependency[] = []; 103 - if (ext.manifest.dependencies != null) { 104 - dependencies.push( 105 - ...ext.manifest.dependencies.map((dep) => ({ 106 - id: dep, 107 - type: DependencyType.Dependency 108 - })) 109 - ); 110 - } 111 - 112 - if (ext.manifest.suggested != null) { 113 - dependencies.push( 114 - ...ext.manifest.suggested.map((dep) => ({ 115 - id: dep, 116 - type: DependencyType.Optional 117 - })) 118 - ); 119 - } 120 - 121 - if (ext.manifest.incompatible != null) { 122 - dependencies.push( 123 - ...ext.manifest.incompatible.map((dep) => ({ 124 - id: dep, 125 - type: DependencyType.Incompatible 126 - })) 127 - ); 128 - } 129 - 130 - return ( 131 - <> 132 - {authors != null && ( 133 - <InfoSection title="Authors"> 134 - {authors.map((author, i) => { 135 - const comma = i !== authors.length - 1 ? ", " : ""; 136 - if (typeof author === "string") { 137 - return ( 138 - <span key={i}> 139 - {author} 140 - {comma} 141 - </span> 142 - ); 143 - } else { 144 - // TODO: resolve IDs 145 - return ( 146 - <span key={i}> 147 - {author.name} 148 - {comma} 149 - </span> 150 - ); 151 - } 152 - })} 153 - </InfoSection> 154 - )} 155 - 156 - {tags != null && ( 157 - <InfoSection title="Tags"> 158 - {tags.map((tag, i) => { 159 - const name = tagNames[tag]; 160 - 161 - return ( 162 - <Badge 163 - key={i} 164 - color={ 165 - tag === ExtensionTag.DangerZone 166 - ? "var(--red-400)" 167 - : "var(--brand-500)" 168 - } 169 - > 170 - {name} 171 - </Badge> 172 - ); 173 - })} 174 - </InfoSection> 175 - )} 176 - 177 - {dependencies.length > 0 && ( 178 - <InfoSection title="Dependencies"> 179 - {dependencies.map((dep) => { 180 - const colors = { 181 - [DependencyType.Dependency]: "var(--brand-500)", 182 - [DependencyType.Optional]: "var(--orange-400)", 183 - [DependencyType.Incompatible]: "var(--red-400)" 184 - }; 185 - const color = colors[dep.type]; 186 - const name = MoonbaseSettingsStore.getExtensionName(dep.id); 187 - return ( 188 - <Badge color={color} key={dep.id}> 189 - {name} 190 - </Badge> 191 - ); 192 - })} 193 - </InfoSection> 194 - )} 195 - 196 - {version != null && ( 197 - <InfoSection title="Version"> 198 - <span>{version}</span> 199 - </InfoSection> 200 - )} 201 - </> 202 - ); 203 - } 204 - 205 - return { 206 - InfoSection, 207 - ExtensionInfo 208 - }; 209 - };
-396
packages/core-extensions/src/moonbase/ui/extensions/settings.tsx
··· 1 - import { 2 - ExtensionSettingType, 3 - ExtensionSettingsManifest, 4 - MultiSelectSettingType, 5 - NumberSettingType, 6 - SelectOption, 7 - SelectSettingType 8 - } from "@moonlight-mod/types/config"; 9 - import WebpackRequire from "@moonlight-mod/types/discord/require"; 10 - import { CircleXIconSVG, ExtensionState, MoonbaseExtension } from "../../types"; 11 - 12 - type SettingsProps = { 13 - ext: MoonbaseExtension; 14 - name: string; 15 - setting: ExtensionSettingsManifest; 16 - disabled: boolean; 17 - }; 18 - 19 - type SettingsComponent = React.ComponentType<SettingsProps>; 20 - 21 - export default (require: typeof WebpackRequire) => { 22 - const React = require("common_react"); 23 - const CommonComponents = require("common_components"); 24 - const Flux = require("common_flux"); 25 - const spacepack = require("spacepack_spacepack").spacepack; 26 - 27 - const { MoonbaseSettingsStore } = 28 - require("moonbase_stores") as typeof import("../../webpackModules/stores"); 29 - 30 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 31 - 32 - function useConfigEntry<T>(id: string, name: string) { 33 - return Flux.useStateFromStores( 34 - [MoonbaseSettingsStore], 35 - () => { 36 - return { 37 - value: MoonbaseSettingsStore.getExtensionConfig<T>(id, name), 38 - displayName: MoonbaseSettingsStore.getExtensionConfigName(id, name), 39 - description: MoonbaseSettingsStore.getExtensionConfigDescription( 40 - id, 41 - name 42 - ) 43 - }; 44 - }, 45 - [id, name] 46 - ); 47 - } 48 - 49 - function Boolean({ ext, name, setting, disabled }: SettingsProps) { 50 - const { FormSwitch } = CommonComponents; 51 - const { value, displayName, description } = useConfigEntry<boolean>( 52 - ext.id, 53 - name 54 - ); 55 - 56 - return ( 57 - <FormSwitch 58 - value={value ?? false} 59 - hideBorder={true} 60 - disabled={disabled} 61 - onChange={(value: boolean) => { 62 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 63 - }} 64 - note={description} 65 - className={`${Margins.marginReset} ${Margins.marginTop20}`} 66 - > 67 - {displayName} 68 - </FormSwitch> 69 - ); 70 - } 71 - 72 - function Number({ ext, name, setting, disabled }: SettingsProps) { 73 - const { FormItem, FormText, Slider } = CommonComponents; 74 - const { value, displayName, description } = useConfigEntry<number>( 75 - ext.id, 76 - name 77 - ); 78 - 79 - const castedSetting = setting as NumberSettingType; 80 - const min = castedSetting.min ?? 0; 81 - const max = castedSetting.max ?? 100; 82 - 83 - return ( 84 - <FormItem className={Margins.marginTop20} title={displayName}> 85 - {description && <FormText>{description}</FormText>} 86 - <Slider 87 - initialValue={value ?? 0} 88 - disabled={disabled} 89 - minValue={castedSetting.min ?? 0} 90 - maxValue={castedSetting.max ?? 100} 91 - onValueChange={(value: number) => { 92 - const rounded = Math.max(min, Math.min(max, Math.round(value))); 93 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded); 94 - }} 95 - /> 96 - </FormItem> 97 - ); 98 - } 99 - 100 - function String({ ext, name, setting, disabled }: SettingsProps) { 101 - const { FormItem, FormText, TextInput } = CommonComponents; 102 - const { value, displayName, description } = useConfigEntry<string>( 103 - ext.id, 104 - name 105 - ); 106 - 107 - return ( 108 - <FormItem className={Margins.marginTop20} title={displayName}> 109 - {description && ( 110 - <FormText className={Margins.marginBottom8}>{description}</FormText> 111 - )} 112 - <TextInput 113 - value={value ?? ""} 114 - onChange={(value: string) => { 115 - if (disabled) return; 116 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 117 - }} 118 - /> 119 - </FormItem> 120 - ); 121 - } 122 - 123 - function Select({ ext, name, setting, disabled }: SettingsProps) { 124 - const { FormItem, FormText, SingleSelect } = CommonComponents; 125 - const { value, displayName, description } = useConfigEntry<string>( 126 - ext.id, 127 - name 128 - ); 129 - 130 - const castedSetting = setting as SelectSettingType; 131 - const options = castedSetting.options; 132 - 133 - return ( 134 - <FormItem className={Margins.marginTop20} title={displayName}> 135 - {description && ( 136 - <FormText className={Margins.marginBottom8}>{description}</FormText> 137 - )} 138 - <SingleSelect 139 - autofocus={false} 140 - clearable={false} 141 - value={value ?? ""} 142 - options={options.map((o: SelectOption) => 143 - typeof o === "string" ? { value: o, label: o } : o 144 - )} 145 - onChange={(value: string) => { 146 - if (disabled) return; 147 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 148 - }} 149 - /> 150 - </FormItem> 151 - ); 152 - } 153 - 154 - function MultiSelect({ ext, name, setting, disabled }: SettingsProps) { 155 - const { FormItem, FormText, Select, useVariableSelect, multiSelect } = 156 - CommonComponents; 157 - const { value, displayName, description } = useConfigEntry< 158 - string | string[] 159 - >(ext.id, name); 160 - 161 - const castedSetting = setting as MultiSelectSettingType; 162 - const options = castedSetting.options; 163 - 164 - return ( 165 - <FormItem className={Margins.marginTop20} title={displayName}> 166 - {description && ( 167 - <FormText className={Margins.marginBottom8}>{description}</FormText> 168 - )} 169 - <Select 170 - autofocus={false} 171 - clearable={false} 172 - closeOnSelect={false} 173 - options={options.map((o: SelectOption) => 174 - typeof o === "string" ? { value: o, label: o } : o 175 - )} 176 - {...useVariableSelect({ 177 - onSelectInteraction: multiSelect, 178 - value: new Set(Array.isArray(value) ? value : [value]), 179 - onChange: (value: string) => { 180 - if (disabled) return; 181 - MoonbaseSettingsStore.setExtensionConfig( 182 - ext.id, 183 - name, 184 - Array.from(value) 185 - ); 186 - } 187 - })} 188 - /> 189 - </FormItem> 190 - ); 191 - } 192 - 193 - const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0] 194 - .exports; 195 - const CircleXIcon = spacepack.findByCode(CircleXIconSVG)[0].exports.default; 196 - function RemoveEntryButton({ 197 - onClick, 198 - disabled 199 - }: { 200 - onClick: () => void; 201 - disabled: boolean; 202 - }) { 203 - const { Tooltip, Clickable } = CommonComponents; 204 - return ( 205 - <div className={RemoveButtonClasses.removeButtonContainer}> 206 - <Tooltip text="Remove entry" position="top"> 207 - {(props: any) => ( 208 - <Clickable 209 - {...props} 210 - className={RemoveButtonClasses.removeButton} 211 - onClick={onClick} 212 - > 213 - <CircleXIcon width={16} height={16} /> 214 - </Clickable> 215 - )} 216 - </Tooltip> 217 - </div> 218 - ); 219 - } 220 - 221 - function List({ ext, name, setting, disabled }: SettingsProps) { 222 - const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents; 223 - const { value, displayName, description } = useConfigEntry<string[]>( 224 - ext.id, 225 - name 226 - ); 227 - 228 - const entries = value ?? []; 229 - const updateConfig = () => 230 - MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries); 231 - 232 - return ( 233 - <FormItem className={Margins.marginTop20} title={displayName}> 234 - {description && ( 235 - <FormText className={Margins.marginBottom4}>{description}</FormText> 236 - )} 237 - <Flex direction={Flex.Direction.VERTICAL}> 238 - {entries.map((val, i) => ( 239 - // FIXME: stylesheets 240 - <div 241 - key={i} 242 - style={{ 243 - display: "grid", 244 - height: "32px", 245 - gap: "8px", 246 - gridTemplateColumns: "1fr 32px", 247 - alignItems: "center" 248 - }} 249 - > 250 - <TextInput 251 - size={TextInput.Sizes.MINI} 252 - value={val} 253 - disabled={disabled} 254 - onChange={(newVal: string) => { 255 - entries[i] = newVal; 256 - updateConfig(); 257 - }} 258 - /> 259 - <RemoveEntryButton 260 - disabled={disabled} 261 - onClick={() => { 262 - entries.splice(i, 1); 263 - updateConfig(); 264 - }} 265 - /> 266 - </div> 267 - ))} 268 - 269 - <Button 270 - look={Button.Looks.FILLED} 271 - color={Button.Colors.GREEN} 272 - size={Button.Sizes.SMALL} 273 - disabled={disabled} 274 - className={Margins.marginTop8} 275 - onClick={() => { 276 - entries.push(""); 277 - updateConfig(); 278 - }} 279 - > 280 - Add new entry 281 - </Button> 282 - </Flex> 283 - </FormItem> 284 - ); 285 - } 286 - 287 - function Dictionary({ ext, name, setting, disabled }: SettingsProps) { 288 - const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents; 289 - const { value, displayName, description } = useConfigEntry< 290 - Record<string, string> 291 - >(ext.id, name); 292 - 293 - const entries = Object.entries(value ?? {}); 294 - const updateConfig = () => 295 - MoonbaseSettingsStore.setExtensionConfig( 296 - ext.id, 297 - name, 298 - Object.fromEntries(entries) 299 - ); 300 - 301 - return ( 302 - <FormItem className={Margins.marginTop20} title={displayName}> 303 - {description && ( 304 - <FormText className={Margins.marginBottom4}>{description}</FormText> 305 - )} 306 - <Flex direction={Flex.Direction.VERTICAL}> 307 - {entries.map(([key, val], i) => ( 308 - // FIXME: stylesheets 309 - <div 310 - key={i} 311 - style={{ 312 - display: "grid", 313 - height: "32px", 314 - gap: "8px", 315 - gridTemplateColumns: "1fr 1fr 32px", 316 - alignItems: "center" 317 - }} 318 - > 319 - <TextInput 320 - size={TextInput.Sizes.MINI} 321 - value={key} 322 - disabled={disabled} 323 - onChange={(newKey: string) => { 324 - entries[i][0] = newKey; 325 - updateConfig(); 326 - }} 327 - /> 328 - <TextInput 329 - size={TextInput.Sizes.MINI} 330 - value={val} 331 - disabled={disabled} 332 - onChange={(newValue: string) => { 333 - entries[i][1] = newValue; 334 - updateConfig(); 335 - }} 336 - /> 337 - <RemoveEntryButton 338 - disabled={disabled} 339 - onClick={() => { 340 - entries.splice(i, 1); 341 - updateConfig(); 342 - }} 343 - /> 344 - </div> 345 - ))} 346 - 347 - <Button 348 - look={Button.Looks.FILLED} 349 - color={Button.Colors.GREEN} 350 - size={Button.Sizes.SMALL} 351 - className={Margins.marginTop8} 352 - disabled={disabled} 353 - onClick={() => { 354 - entries.push([`entry-${entries.length}`, ""]); 355 - updateConfig(); 356 - }} 357 - > 358 - Add new entry 359 - </Button> 360 - </Flex> 361 - </FormItem> 362 - ); 363 - } 364 - 365 - function Setting({ ext, name, setting, disabled }: SettingsProps) { 366 - const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = { 367 - [ExtensionSettingType.Boolean]: Boolean, 368 - [ExtensionSettingType.Number]: Number, 369 - [ExtensionSettingType.String]: String, 370 - [ExtensionSettingType.Select]: Select, 371 - [ExtensionSettingType.MultiSelect]: MultiSelect, 372 - [ExtensionSettingType.List]: List, 373 - [ExtensionSettingType.Dictionary]: Dictionary 374 - }; 375 - const element = elements[setting.type]; 376 - if (element == null) return <></>; 377 - return React.createElement(element, { ext, name, setting, disabled }); 378 - } 379 - 380 - return function Settings({ ext }: { ext: MoonbaseExtension }) { 381 - const { Flex } = CommonComponents; 382 - return ( 383 - <Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}> 384 - {Object.entries(ext.manifest.settings!).map(([name, setting]) => ( 385 - <Setting 386 - ext={ext} 387 - key={name} 388 - name={name} 389 - setting={setting} 390 - disabled={ext.state === ExtensionState.NotDownloaded} 391 - /> 392 - ))} 393 - </Flex> 394 - ); 395 - }; 396 - };
-54
packages/core-extensions/src/moonbase/ui/index.tsx
··· 1 - import { WebpackRequireType } from "@moonlight-mod/types"; 2 - import { pageModules } from ".."; 3 - 4 - export default (require: WebpackRequireType) => { 5 - const React = require("common_react"); 6 - const spacepack = require("spacepack_spacepack").spacepack; 7 - 8 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 9 - 10 - const { Divider } = spacepack.findByCode(".default.HEADER_BAR")[0].exports 11 - .default; 12 - const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0] 13 - .exports; 14 - const TabBarClasses = spacepack.findByCode("nowPlayingColumn:")[0].exports; 15 - 16 - const pages = pageModules(require); 17 - 18 - return function Moonbase() { 19 - const { Text, TabBar } = require("common_components"); 20 - 21 - const [selectedTab, setSelectedTab] = React.useState(Object.keys(pages)[0]); 22 - 23 - return ( 24 - <> 25 - <div 26 - className={`${TitleBarClasses.children} ${Margins.marginBottom20}`} 27 - > 28 - <Text 29 - className={TitleBarClasses.titleWrapper} 30 - variant="heading-lg/semibold" 31 - tag="h2" 32 - > 33 - Moonbase 34 - </Text> 35 - <Divider /> 36 - <TabBar 37 - selectedItem={selectedTab} 38 - onItemSelect={setSelectedTab} 39 - type="top-pill" 40 - className={TabBarClasses.tabBar} 41 - > 42 - {Object.entries(pages).map(([id, page]) => ( 43 - <TabBar.Item key={id} id={id} className={TabBarClasses.item}> 44 - {page.name} 45 - </TabBar.Item> 46 - ))} 47 - </TabBar> 48 - </div> 49 - 50 - {React.createElement(pages[selectedTab].element)} 51 - </> 52 - ); 53 - }; 54 - };
+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;
+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 + }
+405 -122
packages/core-extensions/src/moonbase/webpackModules/stores.ts
··· 1 - import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; 1 + import { Config, ExtensionEnvironment, ExtensionLoadSource, ExtensionSettingsAdvice } from "@moonlight-mod/types"; 2 2 import { 3 3 ExtensionState, 4 4 MoonbaseExtension, 5 5 MoonbaseNatives, 6 - RepositoryManifest 6 + RepositoryManifest, 7 + RestartAdvice, 8 + UpdateState 7 9 } from "../types"; 8 - import Flux from "@moonlight-mod/wp/common_flux"; 9 - import Dispatcher from "@moonlight-mod/wp/common_fluxDispatcher"; 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"; 10 19 11 - const natives: MoonbaseNatives = moonlight.getNatives("moonbase"); 12 20 const logger = moonlight.getLogger("moonbase"); 13 21 14 - class MoonbaseSettingsStore extends Flux.Store<any> { 15 - 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; 16 28 private config: Config; 29 + private extensionIndex: number; 30 + private configComponents: Record<string, Record<string, CustomComponent>> = {}; 17 31 18 32 modified: boolean; 19 33 submitting: boolean; 20 34 installing: boolean; 21 35 22 - extensions: { [id: string]: MoonbaseExtension }; 23 - updates: { [id: string]: { version: string; download: string } }; 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 + 45 + extensions: { [id: number]: MoonbaseExtension }; 46 + updates: { 47 + [id: number]: { 48 + version: string; 49 + download: string; 50 + updateManifest: RepositoryManifest; 51 + }; 52 + }; 24 53 25 54 constructor() { 26 55 super(Dispatcher); 27 56 28 - this.origConfig = moonlightNode.config; 29 - this.config = this.clone(this.origConfig); 57 + this.initialConfig = moonlightNode.config; 58 + this.savedConfig = moonlightNode.config; 59 + this.config = this.clone(this.savedConfig); 60 + this.extensionIndex = 0; 30 61 31 62 this.modified = false; 32 63 this.submitting = false; 33 64 this.installing = false; 34 65 66 + this.newVersion = null; 67 + this.shouldShowNotice = false; 68 + 35 69 this.extensions = {}; 36 70 this.updates = {}; 37 71 for (const ext of moonlightNode.extensions) { 38 - const existingExtension = this.extensions[ext.id]; 39 - if (existingExtension != null) continue; 40 - 41 - this.extensions[ext.id] = { 72 + const uniqueId = this.extensionIndex++; 73 + this.extensions[uniqueId] = { 42 74 ...ext, 43 - state: moonlight.enabledExtensions.has(ext.id) 44 - ? ExtensionState.Enabled 45 - : ExtensionState.Disabled 75 + uniqueId, 76 + state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled : ExtensionState.Disabled, 77 + compat: checkExtensionCompat(ext.manifest), 78 + hasUpdate: false 46 79 }; 47 80 } 48 81 49 - natives.fetchRepositories(this.config.repositories).then((ret) => { 50 - for (const [repo, exts] of Object.entries(ret)) { 51 - try { 52 - for (const ext of exts) { 53 - try { 54 - const existingExtension = this.extensions[ext.id]; 55 - if (existingExtension !== undefined) { 56 - if (this.hasUpdate(repo, ext, existingExtension)) { 57 - this.updates[ext.id] = { 58 - version: ext.version!, 59 - download: ext.download 60 - }; 61 - } 82 + // This is async but we're calling it without 83 + this.checkUpdates(); 62 84 63 - this.extensions[ext.id].manifest = ext; 64 - this.extensions[ext.id].source = { 65 - type: ExtensionLoadSource.Normal, 66 - url: repo 67 - }; 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 + } 68 94 69 - continue; 70 - } 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 = {}; 71 111 72 - this.extensions[ext.id] = { 73 - id: ext.id, 74 - manifest: ext, 75 - source: { type: ExtensionLoadSource.Normal, url: repo }, 76 - state: ExtensionState.NotDownloaded 77 - }; 78 - } catch (e) { 79 - logger.error(`Error processing extension ${ext.id}`, e); 80 - } 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 + }; 124 + 125 + // Don't present incompatible updates 126 + if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue; 127 + 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 + }; 135 + 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; 81 144 } 82 - } catch (e) { 83 - logger.error(`Error processing repository ${repo}`, e); 145 + } else { 146 + this.extensions[uniqueId] = extensionData; 84 147 } 85 148 } 149 + } 150 + } 86 151 87 - this.emitChange(); 88 - }); 152 + private async checkMoonlightUpdates() { 153 + this.newVersion = this.getExtensionConfigRaw("moonbase", "updateChecking", true) 154 + ? await natives!.checkForMoonlightUpdate() 155 + : null; 89 156 } 90 157 91 - // this logic sucks so bad lol 92 - private hasUpdate( 93 - repo: string, 94 - repoExt: RepositoryManifest, 95 - existing: MoonbaseExtension 96 - ) { 97 - return ( 98 - existing.source.type === ExtensionLoadSource.Normal && 99 - existing.source.url != null && 100 - existing.source.url === repo && 101 - repoExt.version != null && 102 - existing.manifest.version !== repoExt.version 103 - ); 158 + private getExisting(ext: MoonbaseExtension) { 159 + return Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 160 + } 161 + 162 + private hasUpdate(ext: MoonbaseExtension) { 163 + const existing = Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 164 + if (existing == null) return false; 165 + 166 + return existing.manifest.version !== ext.manifest.version && existing.state !== ExtensionState.NotDownloaded; 104 167 } 105 168 106 169 // Jank 107 170 private isModified() { 108 - const orig = JSON.stringify(this.origConfig); 171 + const orig = JSON.stringify(this.savedConfig); 109 172 const curr = JSON.stringify(this.config); 110 173 return orig !== curr; 111 174 } ··· 114 177 return this.submitting || this.installing; 115 178 } 116 179 180 + // Required for the settings store contract 117 181 showNotice() { 118 182 return this.modified; 119 183 } 120 184 121 - getExtension(id: string) { 122 - return this.extensions[id]; 185 + getExtension(uniqueId: number) { 186 + return this.extensions[uniqueId]; 123 187 } 124 188 125 - getExtensionName(id: string) { 126 - return Object.prototype.hasOwnProperty.call(this.extensions, id) 127 - ? this.extensions[id].manifest.meta?.name ?? id 128 - : id; 189 + getExtensionUniqueId(id: string) { 190 + return Object.values(this.extensions).find((ext) => ext.id === id)?.uniqueId; 191 + } 192 + 193 + getExtensionConflicting(uniqueId: number) { 194 + const ext = this.getExtension(uniqueId); 195 + if (ext.state !== ExtensionState.NotDownloaded) return false; 196 + return Object.values(this.extensions).some( 197 + (e) => e.id === ext.id && e.uniqueId !== uniqueId && e.state !== ExtensionState.NotDownloaded 198 + ); 199 + } 200 + 201 + getExtensionName(uniqueId: number) { 202 + const ext = this.getExtension(uniqueId); 203 + return ext.manifest.meta?.name ?? ext.id; 129 204 } 130 205 131 - getExtensionUpdate(id: string) { 132 - return Object.prototype.hasOwnProperty.call(this.updates, id) 133 - ? this.updates[id] 134 - : null; 206 + getExtensionUpdate(uniqueId: number) { 207 + return this.updates[uniqueId]?.version; 135 208 } 136 209 137 - getExtensionEnabled(id: string) { 138 - const val = this.config.extensions[id]; 210 + getExtensionEnabled(uniqueId: number) { 211 + const ext = this.getExtension(uniqueId); 212 + if (ext.state === ExtensionState.NotDownloaded) return false; 213 + const val = this.config.extensions[ext.id]; 139 214 if (val == null) return false; 140 215 return typeof val === "boolean" ? val : val.enabled; 141 216 } 142 217 143 - getExtensionConfig<T>(id: string, key: string): T | undefined { 144 - const defaultValue = this.extensions[id].manifest.settings?.[key]?.default; 145 - const clonedDefaultValue = this.clone(defaultValue); 218 + getExtensionConfig<T>(uniqueId: number, key: string): T | undefined { 219 + const ext = this.getExtension(uniqueId); 220 + const settings = ext.settingsOverride ?? ext.manifest.settings; 221 + return getConfigOption(ext.id, key, this.config, settings); 222 + } 223 + 224 + getExtensionConfigRaw<T>(id: string, key: string, defaultValue: T | undefined): T | undefined { 146 225 const cfg = this.config.extensions[id]; 147 - 148 - if (cfg == null || typeof cfg === "boolean") return clonedDefaultValue; 149 - return cfg.config?.[key] ?? clonedDefaultValue; 226 + if (cfg == null || typeof cfg === "boolean") return defaultValue; 227 + return cfg.config?.[key] ?? defaultValue; 150 228 } 151 229 152 - getExtensionConfigName(id: string, key: string) { 153 - return this.extensions[id].manifest.settings?.[key]?.displayName ?? key; 230 + getExtensionConfigName(uniqueId: number, key: string) { 231 + const ext = this.getExtension(uniqueId); 232 + const settings = ext.settingsOverride ?? ext.manifest.settings; 233 + return settings?.[key]?.displayName ?? key; 154 234 } 155 235 156 - getExtensionConfigDescription(id: string, key: string) { 157 - return this.extensions[id].manifest.settings?.[key]?.description; 236 + getExtensionConfigDescription(uniqueId: number, key: string) { 237 + const ext = this.getExtension(uniqueId); 238 + const settings = ext.settingsOverride ?? ext.manifest.settings; 239 + return settings?.[key]?.description; 158 240 } 159 241 160 242 setExtensionConfig(id: string, key: string, value: any) { 161 - const oldConfig = this.config.extensions[id]; 162 - const newConfig = 163 - typeof oldConfig === "boolean" 164 - ? { 165 - enabled: oldConfig, 166 - config: { [key]: value } 167 - } 168 - : { 169 - ...oldConfig, 170 - config: { ...(oldConfig?.config ?? {}), [key]: value } 171 - }; 172 - 173 - this.config.extensions[id] = newConfig; 243 + setConfigOption(this.config, id, key, value); 174 244 this.modified = this.isModified(); 175 245 this.emitChange(); 176 246 } 177 247 178 - setExtensionEnabled(id: string, enabled: boolean) { 179 - let val = this.config.extensions[id]; 248 + setExtensionEnabled(uniqueId: number, enabled: boolean) { 249 + const ext = this.getExtension(uniqueId); 250 + let val = this.config.extensions[ext.id]; 180 251 181 252 if (val == null) { 182 - this.config.extensions[id] = { enabled }; 253 + this.config.extensions[ext.id] = enabled; 183 254 this.modified = this.isModified(); 184 255 this.emitChange(); 185 256 return; ··· 191 262 val.enabled = enabled; 192 263 } 193 264 194 - this.config.extensions[id] = val; 265 + this.config.extensions[ext.id] = val; 195 266 this.modified = this.isModified(); 196 267 this.emitChange(); 197 268 } 198 269 199 - async installExtension(id: string) { 200 - const ext = this.getExtension(id); 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 + 287 + async installExtension(uniqueId: number) { 288 + const ext = this.getExtension(uniqueId); 201 289 if (!("download" in ext.manifest)) { 202 290 throw new Error("Extension has no download URL"); 203 291 } 204 292 205 293 this.installing = true; 206 294 try { 207 - const url = this.updates[id]?.download ?? ext.manifest.download; 208 - 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!); 209 298 if (ext.state === ExtensionState.NotDownloaded) { 210 - this.extensions[id].state = ExtensionState.Disabled; 299 + this.extensions[uniqueId].state = ExtensionState.Disabled; 300 + } 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; 211 308 } 212 309 213 - delete this.updates[id]; 310 + delete this.updates[uniqueId]; 214 311 } catch (e) { 215 312 logger.error("Error installing extension:", e); 216 313 } 217 314 218 315 this.installing = false; 316 + this.restartAdvice = this.#computeRestartAdvice(); 219 317 this.emitChange(); 220 318 } 221 319 222 - async deleteExtension(id: string) { 223 - const ext = this.getExtension(id); 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 + 360 + async deleteExtension(uniqueId: number) { 361 + const ext = this.getExtension(uniqueId); 224 362 if (ext == null) return; 225 363 226 364 this.installing = true; 227 365 try { 228 - await natives.deleteExtension(ext.id); 229 - this.extensions[id].state = ExtensionState.NotDownloaded; 366 + await natives!.deleteExtension(ext.id); 367 + this.extensions[uniqueId].state = ExtensionState.NotDownloaded; 230 368 } catch (e) { 231 369 logger.error("Error deleting extension:", e); 232 370 } 233 371 234 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 + 235 389 this.emitChange(); 236 390 } 237 391 ··· 245 399 this.emitChange(); 246 400 } 247 401 248 - writeConfig() { 249 - this.submitting = true; 402 + tryGetExtensionName(id: string) { 403 + const uniqueId = this.getExtensionUniqueId(id); 404 + return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id; 405 + } 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 + } 250 509 510 + return returnedAdvice; 511 + } 512 + 513 + async writeConfig() { 251 514 try { 252 - moonlightNode.writeConfig(this.config); 253 - this.origConfig = this.clone(this.config); 254 - } catch (e) { 255 - 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(); 256 523 } 524 + } 257 525 258 - this.submitting = false; 526 + private async processConfigChanged() { 527 + this.savedConfig = this.clone(this.config); 528 + this.restartAdvice = this.#computeRestartAdvice(); 259 529 this.modified = false; 530 + 531 + const modifiedRepos = diff(this.savedConfig.repositories, this.config.repositories); 532 + if (modifiedRepos.length !== 0) await this.checkUpdates(); 533 + 260 534 this.emitChange(); 261 535 } 262 536 263 537 reset() { 264 538 this.submitting = false; 265 539 this.modified = false; 266 - this.config = this.clone(this.origConfig); 540 + this.config = this.clone(this.savedConfig); 267 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 + } 268 551 } 269 552 270 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 + }
+157
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
··· 1 + import { LogLevel } from "@moonlight-mod/types"; 2 + 3 + const logLevels = Object.values(LogLevel).filter((v) => typeof v === "string") as string[]; 4 + 5 + import React from "@moonlight-mod/wp/react"; 6 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 7 + import { 8 + FormDivider, 9 + FormItem, 10 + FormText, 11 + FormSwitch, 12 + TextInput, 13 + Button, 14 + SingleSelect, 15 + Tooltip, 16 + Clickable 17 + } from "@moonlight-mod/wp/discord/components/common/index"; 18 + import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 19 + import { CircleXIcon } from "@moonlight-mod/wp/discord/components/common/index"; 20 + import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css"; 21 + import FormSwitchClasses from "@moonlight-mod/wp/discord/components/common/FormSwitch.css"; 22 + 23 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 24 + 25 + let GuildSettingsRoleEditClasses: any; 26 + spacepack 27 + .lazyLoad( 28 + "renderArtisanalHack", 29 + /\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"GuildSettings"/, 30 + /webpackId:(\d+),name:"GuildSettings"/ 31 + ) 32 + .then( 33 + () => 34 + (GuildSettingsRoleEditClasses = spacepack.require( 35 + "discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css" 36 + )) 37 + ); 38 + 39 + function RemoveEntryButton({ onClick }: { onClick: () => void }) { 40 + return ( 41 + <div className={GuildSettingsRoleEditClasses.removeButtonContainer}> 42 + <Tooltip text="Remove entry" position="top"> 43 + {(props: any) => ( 44 + <Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}> 45 + <CircleXIcon width={24} height={24} /> 46 + </Clickable> 47 + )} 48 + </Tooltip> 49 + </div> 50 + ); 51 + } 52 + 53 + function ArrayFormItem({ config }: { config: "repositories" | "devSearchPaths" }) { 54 + const items = MoonbaseSettingsStore.getConfigOption(config) ?? []; 55 + return ( 56 + <Flex 57 + style={{ 58 + gap: "20px" 59 + }} 60 + direction={Flex.Direction.VERTICAL} 61 + > 62 + {items.map((val, i) => ( 63 + <div 64 + key={i} 65 + style={{ 66 + display: "grid", 67 + height: "32px", 68 + gap: "8px", 69 + gridTemplateColumns: "1fr 32px", 70 + alignItems: "center" 71 + }} 72 + > 73 + <TextInput 74 + size={TextInput.Sizes.DEFAULT} 75 + value={val} 76 + onChange={(newVal: string) => { 77 + items[i] = newVal; 78 + MoonbaseSettingsStore.setConfigOption(config, items); 79 + }} 80 + /> 81 + <RemoveEntryButton 82 + onClick={() => { 83 + items.splice(i, 1); 84 + MoonbaseSettingsStore.setConfigOption(config, items); 85 + }} 86 + /> 87 + </div> 88 + ))} 89 + 90 + <Button 91 + look={Button.Looks.FILLED} 92 + color={Button.Colors.GREEN} 93 + size={Button.Sizes.SMALL} 94 + style={{ 95 + marginTop: "10px" 96 + }} 97 + onClick={() => { 98 + items.push(""); 99 + MoonbaseSettingsStore.setConfigOption(config, items); 100 + }} 101 + > 102 + Add new entry 103 + </Button> 104 + </Flex> 105 + ); 106 + } 107 + 108 + export default function ConfigPage() { 109 + return ( 110 + <> 111 + <FormSwitch 112 + className={Margins.marginTop20} 113 + value={MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "updateChecking", true) ?? true} 114 + onChange={(value: boolean) => { 115 + MoonbaseSettingsStore.setExtensionConfig("moonbase", "updateChecking", value); 116 + }} 117 + note="Checks for updates to moonlight" 118 + > 119 + Automatic update checking 120 + </FormSwitch> 121 + <FormItem title="Repositories"> 122 + <FormText className={Margins.marginBottom4}>A list of remote repositories to display extensions from</FormText> 123 + <ArrayFormItem config="repositories" /> 124 + </FormItem> 125 + <FormDivider className={FormSwitchClasses.dividerDefault} /> 126 + <FormItem title="Extension search paths" className={Margins.marginTop20}> 127 + <FormText className={Margins.marginBottom4}> 128 + A list of local directories to search for built extensions 129 + </FormText> 130 + <ArrayFormItem config="devSearchPaths" /> 131 + </FormItem> 132 + <FormDivider className={FormSwitchClasses.dividerDefault} /> 133 + <FormSwitch 134 + className={Margins.marginTop20} 135 + value={MoonbaseSettingsStore.getConfigOption("patchAll") ?? false} 136 + onChange={(value: boolean) => { 137 + MoonbaseSettingsStore.setConfigOption("patchAll", value); 138 + }} 139 + note="Wraps every webpack module in a function, separating them in DevTools" 140 + > 141 + Patch all 142 + </FormSwitch> 143 + <FormItem title="Log level"> 144 + <SingleSelect 145 + autofocus={false} 146 + clearable={false} 147 + value={MoonbaseSettingsStore.getConfigOption("loggerLevel")} 148 + options={logLevels.map((o) => ({ 149 + value: o.toLowerCase(), 150 + label: o[0] + o.slice(1).toLowerCase() 151 + }))} 152 + onChange={(v) => MoonbaseSettingsStore.setConfigOption("loggerLevel", v)} 153 + /> 154 + </FormItem> 155 + </> 156 + ); 157 + }
+335
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
··· 1 + import { ExtensionState } from "../../../types"; 2 + import { constants, ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types"; 3 + 4 + import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 5 + import { 6 + ScienceIcon, 7 + DownloadIcon, 8 + TrashIcon, 9 + AngleBracketsIcon, 10 + Tooltip, 11 + Card, 12 + Text, 13 + FormSwitch, 14 + TabBar, 15 + Button, 16 + ChannelListIcon, 17 + HeartIcon, 18 + WindowTopOutlineIcon, 19 + WarningIcon 20 + } from "@moonlight-mod/wp/discord/components/common/index"; 21 + import React from "@moonlight-mod/wp/react"; 22 + import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 23 + import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 24 + import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils"; 25 + import AppCardClasses from "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCard.css"; 26 + import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton"; 27 + import DiscoveryClasses from "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css"; 28 + import MarkupClasses from "@moonlight-mod/wp/discord/modules/messages/web/Markup.css"; 29 + import BuildOverrideClasses from "@moonlight-mod/wp/discord/modules/build_overrides/web/BuildOverride.css"; 30 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 31 + import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary"; 32 + import ExtensionInfo from "./info"; 33 + import Settings from "./settings"; 34 + import { doGenericExtensionPopup, doMissingExtensionPopup } from "./popup"; 35 + 36 + export enum ExtensionPage { 37 + Info, 38 + Description, 39 + Changelog, 40 + Settings 41 + } 42 + 43 + const COMPAT_TEXT_MAP: Record<ExtensionCompat, string> = { 44 + [ExtensionCompat.Compatible]: "huh?", 45 + [ExtensionCompat.InvalidApiLevel]: "Incompatible API level", 46 + [ExtensionCompat.InvalidEnvironment]: "Incompatible platform" 47 + }; 48 + const CONFLICTING_TEXT = "This extension is already installed from another source."; 49 + 50 + function PanelLinkButton({ icon, tooltip, link }: { icon: React.ReactNode; tooltip: string; link: string }) { 51 + return ( 52 + <PanelButton 53 + icon={icon} 54 + tooltipText={tooltip} 55 + onClick={() => { 56 + window.open(link); 57 + }} 58 + /> 59 + ); 60 + } 61 + 62 + export default function ExtensionCard({ uniqueId, selectTag }: { uniqueId: number; selectTag: (tag: string) => void }) { 63 + const { ext, enabled, busy, update, conflicting } = useStateFromStores([MoonbaseSettingsStore], () => { 64 + return { 65 + ext: MoonbaseSettingsStore.getExtension(uniqueId), 66 + enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId), 67 + busy: MoonbaseSettingsStore.busy, 68 + update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId), 69 + conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId) 70 + }; 71 + }); 72 + 73 + const [tab, setTab] = React.useState( 74 + update != null && ext?.changelog != null ? ExtensionPage.Changelog : ExtensionPage.Info 75 + ); 76 + 77 + const tagline = ext.manifest?.meta?.tagline; 78 + const settings = ext.settingsOverride ?? ext.manifest?.settings; 79 + const description = ext.manifest?.meta?.description; 80 + const changelog = ext.changelog; 81 + const linkButtons = [ 82 + ext?.manifest?.meta?.source && ( 83 + <PanelLinkButton icon={<AngleBracketsIcon />} tooltip="View source" link={ext.manifest.meta.source} /> 84 + ), 85 + ext?.source?.url && <PanelLinkButton icon={<ChannelListIcon />} tooltip="View repository" link={ext.source.url} />, 86 + ext?.manifest?.meta?.donate && ( 87 + <PanelLinkButton icon={<HeartIcon />} tooltip="Donate" link={ext.manifest.meta.donate} /> 88 + ) 89 + ].filter((x) => x != null); 90 + 91 + const enabledDependants = useStateFromStores([MoonbaseSettingsStore], () => 92 + Object.keys(MoonbaseSettingsStore.extensions) 93 + .filter((uniqueId) => { 94 + const potentialDependant = MoonbaseSettingsStore.getExtension(parseInt(uniqueId)); 95 + 96 + return ( 97 + potentialDependant.manifest.dependencies?.includes(ext?.id) && 98 + MoonbaseSettingsStore.getExtensionEnabled(parseInt(uniqueId)) 99 + ); 100 + }) 101 + .map((a) => MoonbaseSettingsStore.getExtension(parseInt(a))) 102 + ); 103 + const implicitlyEnabled = enabledDependants.length > 0; 104 + 105 + const hasDuplicateEntry = useStateFromStores([MoonbaseSettingsStore], () => 106 + Object.entries(MoonbaseSettingsStore.extensions).some( 107 + ([otherUniqueId, otherExt]) => 108 + otherExt != null && otherExt?.id === ext?.id && parseInt(otherUniqueId) !== uniqueId 109 + ) 110 + ); 111 + 112 + return ext == null ? ( 113 + <></> 114 + ) : ( 115 + <Card editable={true} className={AppCardClasses.card}> 116 + <div className={AppCardClasses.cardHeader}> 117 + <Flex direction={Flex.Direction.VERTICAL}> 118 + <Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER}> 119 + <Text variant="text-md/semibold">{ext.manifest?.meta?.name ?? ext.id}</Text> 120 + {ext.source.type === ExtensionLoadSource.Developer && ( 121 + <Tooltip text="This is a local extension" position="top"> 122 + {(props: any) => <ScienceIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />} 123 + </Tooltip> 124 + )} 125 + 126 + {hasDuplicateEntry && ext?.source?.url && ( 127 + <Tooltip text={`This extension is from the following repository: ${ext.source.url}`} position="top"> 128 + {(props: any) => <WindowTopOutlineIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />} 129 + </Tooltip> 130 + )} 131 + 132 + {ext.manifest?.meta?.deprecated && ( 133 + <Tooltip text="This extension is deprecated" position="top"> 134 + {(props: any) => <WarningIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />} 135 + </Tooltip> 136 + )} 137 + </Flex> 138 + 139 + {tagline != null && <Text variant="text-sm/normal">{MarkupUtils.parse(tagline)}</Text>} 140 + </Flex> 141 + 142 + <Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.END} justify={Flex.Justify.END}> 143 + <div 144 + // too lazy to learn how <Flex /> works lmao 145 + style={{ 146 + display: "flex", 147 + alignItems: "center", 148 + gap: "1rem" 149 + }} 150 + > 151 + {ext.state === ExtensionState.NotDownloaded ? ( 152 + <Tooltip 153 + text={conflicting ? CONFLICTING_TEXT : COMPAT_TEXT_MAP[ext.compat]} 154 + shouldShow={conflicting || ext.compat !== ExtensionCompat.Compatible} 155 + > 156 + {(props: any) => ( 157 + <Button 158 + {...props} 159 + color={Button.Colors.BRAND} 160 + submitting={busy} 161 + disabled={ext.compat !== ExtensionCompat.Compatible || conflicting} 162 + onClick={async () => { 163 + await MoonbaseSettingsStore.installExtension(uniqueId); 164 + const deps = await MoonbaseSettingsStore.getDependencies(uniqueId); 165 + if (deps != null) { 166 + await doMissingExtensionPopup(deps); 167 + } 168 + 169 + // Don't auto enable dangerous extensions 170 + if (!ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) { 171 + MoonbaseSettingsStore.setExtensionEnabled(uniqueId, true); 172 + } 173 + }} 174 + > 175 + Install 176 + </Button> 177 + )} 178 + </Tooltip> 179 + ) : ( 180 + <> 181 + {ext.source.type === ExtensionLoadSource.Normal && ( 182 + <PanelButton 183 + icon={TrashIcon} 184 + tooltipText="Delete" 185 + onClick={() => { 186 + MoonbaseSettingsStore.deleteExtension(uniqueId); 187 + }} 188 + /> 189 + )} 190 + 191 + {update != null && ( 192 + <PanelButton 193 + icon={DownloadIcon} 194 + tooltipText="Update" 195 + onClick={() => { 196 + MoonbaseSettingsStore.installExtension(uniqueId); 197 + }} 198 + /> 199 + )} 200 + 201 + <FormSwitch 202 + value={ext.compat === ExtensionCompat.Compatible && (enabled || implicitlyEnabled)} 203 + disabled={implicitlyEnabled || ext.compat !== ExtensionCompat.Compatible} 204 + hideBorder={true} 205 + style={{ marginBottom: "0px" }} 206 + // @ts-expect-error fix type later 207 + tooltipNote={ 208 + ext.compat !== ExtensionCompat.Compatible ? ( 209 + COMPAT_TEXT_MAP[ext.compat] 210 + ) : implicitlyEnabled ? ( 211 + <div style={{ display: "flex", flexDirection: "column" }}> 212 + <div>{`This extension is a dependency of the following enabled extension${ 213 + enabledDependants.length > 1 ? "s" : "" 214 + }:`}</div> 215 + {enabledDependants.map((dep) => ( 216 + <div>{"โ€ข " + (dep.manifest.meta?.name ?? dep.id)}</div> 217 + ))} 218 + </div> 219 + ) : undefined 220 + } 221 + onChange={() => { 222 + const toggle = () => { 223 + MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled); 224 + }; 225 + 226 + if (enabled && constants.builtinExtensions.includes(ext.id)) { 227 + doGenericExtensionPopup( 228 + "Built in extension", 229 + "This extension is enabled by default. Disabling it might have consequences. Are you sure you want to disable it?", 230 + uniqueId, 231 + toggle 232 + ); 233 + } else if (!enabled && ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) { 234 + doGenericExtensionPopup( 235 + "Dangerous extension", 236 + "This extension is marked as dangerous. Enabling it might have consequences. Are you sure you want to enable it?", 237 + uniqueId, 238 + toggle 239 + ); 240 + } else { 241 + toggle(); 242 + } 243 + }} 244 + /> 245 + </> 246 + )} 247 + </div> 248 + </Flex> 249 + </div> 250 + 251 + <div> 252 + {(description != null || changelog != null || settings != null || linkButtons.length > 0) && ( 253 + <Flex> 254 + <TabBar 255 + selectedItem={tab} 256 + type="top" 257 + onItemSelect={setTab} 258 + className={DiscoveryClasses.tabBar} 259 + style={{ 260 + padding: "0 20px" 261 + }} 262 + > 263 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Info}> 264 + Info 265 + </TabBar.Item> 266 + 267 + {description != null && ( 268 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Description}> 269 + Description 270 + </TabBar.Item> 271 + )} 272 + 273 + {changelog != null && ( 274 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Changelog}> 275 + Changelog 276 + </TabBar.Item> 277 + )} 278 + 279 + {settings != null && ( 280 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Settings}> 281 + Settings 282 + </TabBar.Item> 283 + )} 284 + </TabBar> 285 + 286 + <Flex 287 + align={Flex.Align.CENTER} 288 + justify={Flex.Justify.END} 289 + direction={Flex.Direction.HORIZONTAL} 290 + grow={1} 291 + className="moonbase-link-buttons" 292 + > 293 + {linkButtons.length > 0 && linkButtons} 294 + </Flex> 295 + </Flex> 296 + )} 297 + 298 + <Flex 299 + justify={Flex.Justify.START} 300 + wrap={Flex.Wrap.WRAP} 301 + style={{ 302 + padding: "16px 16px", 303 + // This looks wonky in the settings tab 304 + rowGap: tab === ExtensionPage.Info ? "16px" : undefined 305 + }} 306 + > 307 + {tab === ExtensionPage.Info && <ExtensionInfo ext={ext} selectTag={selectTag} />} 308 + {tab === ExtensionPage.Description && ( 309 + <Text variant="text-md/normal" className={MarkupClasses.markup} style={{ width: "100%" }}> 310 + {MarkupUtils.parse(description ?? "*No description*", true, { 311 + allowHeading: true, 312 + allowLinks: true, 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 + })} 324 + </Text> 325 + )} 326 + {tab === ExtensionPage.Settings && ( 327 + <ErrorBoundary> 328 + <Settings ext={ext} /> 329 + </ErrorBoundary> 330 + )} 331 + </Flex> 332 + </div> 333 + </Card> 334 + ); 335 + }
+356
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
··· 1 + import { tagNames } from "./info"; 2 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 3 + import * as React from "@moonlight-mod/wp/react"; 4 + import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 5 + import { WindowStore } from "@moonlight-mod/wp/common_stores"; 6 + import { 7 + Button, 8 + Text, 9 + Heading, 10 + Popout, 11 + Dialog, 12 + Menu, 13 + ChevronSmallDownIcon, 14 + ChevronSmallUpIcon, 15 + ArrowsUpDownIcon, 16 + RetryIcon, 17 + Tooltip 18 + } from "@moonlight-mod/wp/discord/components/common/index"; 19 + import { MenuGroup, MenuCheckboxItem, MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu"; 20 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 21 + import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css"; 22 + import TagItem from "@moonlight-mod/wp/discord/modules/forums/web/Tag"; 23 + 24 + export enum Filter { 25 + Core = 1 << 0, 26 + Normal = 1 << 1, 27 + Developer = 1 << 2, 28 + Enabled = 1 << 3, 29 + Disabled = 1 << 4, 30 + Installed = 1 << 5, 31 + Repository = 1 << 6, 32 + Incompatible = 1 << 7, 33 + Deprecated = 1 << 8 34 + } 35 + export const defaultFilter = 127 as Filter; 36 + 37 + let HeaderClasses: any; 38 + let ForumsClasses: any; 39 + let SortMenuClasses: any; 40 + spacepack 41 + .lazyLoad('"Missing channel in Channel.openChannelContextMenu"', /e\("(\d+)"\)/g, /webpackId:(\d+?),/) 42 + .then(() => { 43 + ForumsClasses = spacepack.require("discord/modules/forums/web/Forums.css"); 44 + HeaderClasses = spacepack.require("discord/modules/forums/web/Header.css"); 45 + SortMenuClasses = spacepack.require("discord/modules/forums/web/SortMenu.css"); 46 + }); 47 + 48 + function toggleTag(selectedTags: Set<string>, setSelectedTags: (tags: Set<string>) => void, tag: string) { 49 + const newState = new Set(selectedTags); 50 + if (newState.has(tag)) newState.delete(tag); 51 + else newState.add(tag); 52 + setSelectedTags(newState); 53 + } 54 + 55 + function FilterButtonPopout({ 56 + filter, 57 + setFilter, 58 + closePopout 59 + }: { 60 + filter: Filter; 61 + setFilter: (filter: Filter) => void; 62 + closePopout: () => void; 63 + }) { 64 + const toggleFilter = (set: Filter) => setFilter(filter & set ? filter & ~set : filter | set); 65 + 66 + return ( 67 + <div className={SortMenuClasses.container}> 68 + <Menu navId="sort-filter" hideScroller={true} onClose={closePopout}> 69 + <MenuGroup label="Type"> 70 + <MenuCheckboxItem 71 + id="t-core" 72 + label="Core" 73 + checked={(filter & Filter.Core) === Filter.Core} 74 + action={() => toggleFilter(Filter.Core)} 75 + /> 76 + <MenuCheckboxItem 77 + id="t-normal" 78 + label="Normal" 79 + checked={(filter & Filter.Normal) === Filter.Normal} 80 + action={() => toggleFilter(Filter.Normal)} 81 + /> 82 + <MenuCheckboxItem 83 + id="t-developer" 84 + label="Developer" 85 + checked={(filter & Filter.Developer) === Filter.Developer} 86 + action={() => toggleFilter(Filter.Developer)} 87 + /> 88 + </MenuGroup> 89 + <MenuGroup label="State"> 90 + <MenuCheckboxItem 91 + id="s-enabled" 92 + label="Enabled" 93 + checked={(filter & Filter.Enabled) === Filter.Enabled} 94 + action={() => toggleFilter(Filter.Enabled)} 95 + /> 96 + <MenuCheckboxItem 97 + id="s-disabled" 98 + label="Disabled" 99 + checked={(filter & Filter.Disabled) === Filter.Disabled} 100 + action={() => toggleFilter(Filter.Disabled)} 101 + /> 102 + </MenuGroup> 103 + <MenuGroup label="Location"> 104 + <MenuCheckboxItem 105 + id="l-installed" 106 + label="Installed" 107 + checked={(filter & Filter.Installed) === Filter.Installed} 108 + action={() => toggleFilter(Filter.Installed)} 109 + /> 110 + <MenuCheckboxItem 111 + id="l-repository" 112 + label="Repository" 113 + checked={(filter & Filter.Repository) === Filter.Repository} 114 + action={() => toggleFilter(Filter.Repository)} 115 + /> 116 + </MenuGroup> 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 + /> 130 + <MenuItem 131 + id="reset-all" 132 + className={SortMenuClasses.clearText} 133 + label="Reset to default" 134 + action={() => { 135 + setFilter(defaultFilter); 136 + closePopout(); 137 + }} 138 + /> 139 + </MenuGroup> 140 + </Menu> 141 + </div> 142 + ); 143 + } 144 + 145 + function TagButtonPopout({ selectedTags, setSelectedTags, setPopoutRef, closePopout }: any) { 146 + return ( 147 + <Dialog ref={setPopoutRef} className={HeaderClasses.container}> 148 + <div className={HeaderClasses.header}> 149 + <div className={HeaderClasses.headerLeft}> 150 + <Heading color="interactive-normal" variant="text-xs/bold" className={HeaderClasses.headerText}> 151 + Select tags 152 + </Heading> 153 + <div className={HeaderClasses.countContainer}> 154 + <Text className={HeaderClasses.countText} color="none" variant="text-xs/medium"> 155 + {selectedTags.size} 156 + </Text> 157 + </div> 158 + </div> 159 + </div> 160 + <div className={HeaderClasses.tagContainer}> 161 + {Object.keys(tagNames).map((tag) => ( 162 + <TagItem 163 + key={tag} 164 + className={HeaderClasses.tag} 165 + tag={{ name: tagNames[tag as keyof typeof tagNames], id: tagNames[tag as keyof typeof tagNames] }} 166 + onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 167 + selected={selectedTags.has(tag)} 168 + /> 169 + ))} 170 + </div> 171 + <div className={HeaderClasses.separator} /> 172 + <Button 173 + look={Button.Looks.LINK} 174 + size={Button.Sizes.MIN} 175 + color={Button.Colors.CUSTOM} 176 + className={HeaderClasses.clear} 177 + onClick={() => { 178 + setSelectedTags(new Set()); 179 + closePopout(); 180 + }} 181 + > 182 + <Text variant="text-sm/medium" color="text-link"> 183 + Clear all 184 + </Text> 185 + </Button> 186 + </Dialog> 187 + ); 188 + } 189 + 190 + export default function FilterBar({ 191 + filter, 192 + setFilter, 193 + selectedTags, 194 + setSelectedTags 195 + }: { 196 + filter: Filter; 197 + setFilter: (filter: Filter) => void; 198 + selectedTags: Set<string>; 199 + setSelectedTags: (tags: Set<string>) => void; 200 + }) { 201 + const windowSize = useStateFromStores([WindowStore], () => WindowStore.windowSize()); 202 + 203 + const tagsContainer = React.useRef<HTMLDivElement>(null); 204 + const tagListInner = React.useRef<HTMLDivElement>(null); 205 + const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0); 206 + const [checkingUpdates, setCheckingUpdates] = React.useState(false); 207 + 208 + React.useLayoutEffect(() => { 209 + if (tagsContainer.current === null || tagListInner.current === null) return; 210 + const { left: containerX, top: containerY } = tagsContainer.current.getBoundingClientRect(); 211 + let offset = 0; 212 + for (const child of tagListInner.current.children) { 213 + const { right: childX, top: childY, height } = child.getBoundingClientRect(); 214 + if (childY - containerY > height) break; 215 + const newOffset = childX - containerX; 216 + if (newOffset > offset) { 217 + offset = newOffset; 218 + } 219 + } 220 + setTagsButtonOffset(offset); 221 + }, [windowSize, tagsContainer.current, tagListInner.current, tagListInner.current?.getBoundingClientRect()?.width]); 222 + 223 + return ( 224 + <div 225 + ref={tagsContainer} 226 + style={{ 227 + paddingTop: "12px" 228 + }} 229 + className={`${ForumsClasses.tagsContainer} ${Margins.marginBottom8}`} 230 + > 231 + <Tooltip text="Refresh updates" position="top"> 232 + {(props: any) => ( 233 + <Button 234 + {...props} 235 + size={Button.Sizes.MIN} 236 + color={Button.Colors.CUSTOM} 237 + className={`${ForumsClasses.sortDropdown} moonbase-retry-button`} 238 + innerClassName={ForumsClasses.sortDropdownInner} 239 + onClick={() => { 240 + (async () => { 241 + try { 242 + setCheckingUpdates(true); 243 + await MoonbaseSettingsStore.checkUpdates(); 244 + } finally { 245 + // artificial delay because the spin is fun 246 + await new Promise((r) => setTimeout(r, 500)); 247 + setCheckingUpdates(false); 248 + } 249 + })(); 250 + }} 251 + > 252 + <RetryIcon size={"custom"} width={16} className={checkingUpdates ? "moonbase-speen" : ""} /> 253 + </Button> 254 + )} 255 + </Tooltip> 256 + <Popout 257 + renderPopout={({ closePopout }: any) => ( 258 + <FilterButtonPopout filter={filter} setFilter={setFilter} closePopout={closePopout} /> 259 + )} 260 + position="bottom" 261 + align="left" 262 + > 263 + {(props: any, { isShown }: { isShown: boolean }) => ( 264 + <Button 265 + {...props} 266 + size={Button.Sizes.MIN} 267 + color={Button.Colors.CUSTOM} 268 + className={ForumsClasses.sortDropdown} 269 + innerClassName={ForumsClasses.sortDropdownInner} 270 + > 271 + <ArrowsUpDownIcon size="xs" /> 272 + <Text className={ForumsClasses.sortDropdownText} variant="text-sm/medium" color="interactive-normal"> 273 + Sort & filter 274 + </Text> 275 + {isShown ? ( 276 + <ChevronSmallUpIcon size={"custom"} width={20} /> 277 + ) : ( 278 + <ChevronSmallDownIcon size={"custom"} width={20} /> 279 + )} 280 + </Button> 281 + )} 282 + </Popout> 283 + <div className={ForumsClasses.divider} /> 284 + <div className={ForumsClasses.tagList}> 285 + <div ref={tagListInner} className={ForumsClasses.tagListInner}> 286 + {Object.keys(tagNames).map((tag) => ( 287 + <TagItem 288 + key={tag} 289 + className={ForumsClasses.tag} 290 + tag={{ name: tagNames[tag as keyof typeof tagNames], id: tag }} 291 + onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 292 + selected={selectedTags.has(tag)} 293 + /> 294 + ))} 295 + </div> 296 + </div> 297 + <Popout 298 + renderPopout={({ setPopoutRef, closePopout }: any) => ( 299 + <TagButtonPopout 300 + selectedTags={selectedTags} 301 + setSelectedTags={setSelectedTags} 302 + setPopoutRef={setPopoutRef} 303 + closePopout={closePopout} 304 + /> 305 + )} 306 + position="bottom" 307 + align="right" 308 + > 309 + {(props: any, { isShown }: { isShown: boolean }) => ( 310 + <Button 311 + {...props} 312 + size={Button.Sizes.MIN} 313 + color={Button.Colors.CUSTOM} 314 + style={{ 315 + left: tagsButtonOffset 316 + }} 317 + // TODO: Use Discord's class name utility 318 + className={`${ForumsClasses.tagsButton} ${selectedTags.size > 0 ? ForumsClasses.tagsButtonWithCount : ""}`} 319 + innerClassName={ForumsClasses.tagsButtonInner} 320 + > 321 + {selectedTags.size > 0 ? ( 322 + <div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}> 323 + <Text className={ForumsClasses.countText} color="none" variant="text-xs/medium"> 324 + {selectedTags.size} 325 + </Text> 326 + </div> 327 + ) : ( 328 + <>All</> 329 + )} 330 + {isShown ? ( 331 + <ChevronSmallUpIcon size={"custom"} width={20} /> 332 + ) : ( 333 + <ChevronSmallDownIcon size={"custom"} width={20} /> 334 + )} 335 + </Button> 336 + )} 337 + </Popout> 338 + <Button 339 + size={Button.Sizes.MIN} 340 + color={Button.Colors.CUSTOM} 341 + className={`${ForumsClasses.tagsButton} ${ForumsClasses.tagsButtonPlaceholder}`} 342 + innerClassName={ForumsClasses.tagsButtonInner} 343 + > 344 + {selectedTags.size > 0 ? ( 345 + <div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}> 346 + <Text className={ForumsClasses.countText} color="none" variant="text-xs/medium"> 347 + {selectedTags.size} 348 + </Text> 349 + </div> 350 + ) : null} 351 + 352 + <ChevronSmallUpIcon size={"custom"} width={20} /> 353 + </Button> 354 + </div> 355 + ); 356 + }
+162
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
··· 1 + import { ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types"; 2 + import { ExtensionState } from "../../../types"; 3 + import FilterBar, { Filter, defaultFilter } from "./filterBar"; 4 + import ExtensionCard from "./card"; 5 + 6 + import React from "@moonlight-mod/wp/react"; 7 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 8 + import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; 9 + import { 10 + FormDivider, 11 + CircleInformationIcon, 12 + XSmallIcon, 13 + Button 14 + } from "@moonlight-mod/wp/discord/components/common/index"; 15 + import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton"; 16 + 17 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 18 + import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary"; 19 + import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 20 + import HelpMessage from "../HelpMessage"; 21 + 22 + const SearchBar = spacepack.require("discord/uikit/search/SearchBar").default; 23 + 24 + const validTags: string[] = Object.values(ExtensionTag); 25 + 26 + export default function ExtensionsPage() { 27 + const { extensions, savedFilter } = useStateFromStoresObject([MoonbaseSettingsStore], () => { 28 + return { 29 + extensions: MoonbaseSettingsStore.extensions, 30 + savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw<number>("moonbase", "filter", defaultFilter) 31 + }; 32 + }); 33 + 34 + const [query, setQuery] = React.useState(""); 35 + const [hitUpdateAll, setHitUpdateAll] = React.useState(false); 36 + 37 + const filterState = React.useState(defaultFilter); 38 + 39 + let filter: Filter, setFilter: (filter: Filter) => void; 40 + if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "saveFilter", false)) { 41 + filter = savedFilter ?? defaultFilter; 42 + setFilter = (filter) => MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter); 43 + } else { 44 + filter = filterState[0]; 45 + setFilter = filterState[1]; 46 + } 47 + 48 + const [selectedTags, setSelectedTags] = React.useState(new Set<string>()); 49 + const selectTag = React.useCallback( 50 + (tag: string) => { 51 + const newState = new Set(selectedTags); 52 + if (validTags.includes(tag)) newState.add(tag); 53 + setSelectedTags(newState); 54 + }, 55 + [selectedTags] 56 + ); 57 + 58 + const sorted = Object.values(extensions).sort((a, b) => { 59 + const aName = a.manifest.meta?.name ?? a.id; 60 + const bName = b.manifest.meta?.name ?? b.id; 61 + return aName.localeCompare(bName); 62 + }); 63 + 64 + const filtered = sorted.filter( 65 + (ext) => 66 + (query === "" || 67 + ext.manifest.id?.toLowerCase().includes(query) || 68 + ext.manifest.meta?.name?.toLowerCase().includes(query) || 69 + ext.manifest.meta?.tagline?.toLowerCase().includes(query) || 70 + (ext.manifest?.settings != null && 71 + Object.entries(ext.manifest.settings).some(([key, setting]) => 72 + (setting.displayName ?? key).toLowerCase().includes(query) 73 + )) || 74 + (ext.manifest?.meta?.authors != null && 75 + ext.manifest.meta.authors.some((author) => 76 + (typeof author === "string" ? author : author.name).toLowerCase().includes(query) 77 + )) || 78 + ext.manifest.meta?.description?.toLowerCase().includes(query)) && 79 + [...selectedTags.values()].every((tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)) && 80 + // This seems very bad, sorry 81 + !( 82 + (!(filter & Filter.Core) && ext.source.type === ExtensionLoadSource.Core) || 83 + (!(filter & Filter.Normal) && ext.source.type === ExtensionLoadSource.Normal) || 84 + (!(filter & Filter.Developer) && ext.source.type === ExtensionLoadSource.Developer) || 85 + (!(filter & Filter.Enabled) && MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 86 + (!(filter & Filter.Disabled) && !MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 87 + (!(filter & Filter.Installed) && ext.state !== ExtensionState.NotDownloaded) || 88 + (!(filter & Filter.Repository) && ext.state === ExtensionState.NotDownloaded) 89 + ) && 90 + (filter & Filter.Incompatible || 91 + ext.compat === ExtensionCompat.Compatible || 92 + (ext.compat === ExtensionCompat.InvalidApiLevel && ext.hasUpdate)) && 93 + (filter & Filter.Deprecated || 94 + ext.manifest?.meta?.deprecated !== true || 95 + ext.state !== ExtensionState.NotDownloaded) 96 + ); 97 + 98 + // Prioritize extensions with updates 99 + const filteredWithUpdates = filtered.filter((ext) => ext!.hasUpdate); 100 + const filteredWithoutUpdates = filtered.filter((ext) => !ext!.hasUpdate); 101 + 102 + return ( 103 + <> 104 + <SearchBar 105 + size={SearchBar.Sizes.MEDIUM} 106 + query={query} 107 + onChange={(v: string) => setQuery(v.toLowerCase())} 108 + onClear={() => setQuery("")} 109 + autoFocus={true} 110 + autoComplete="off" 111 + inputProps={{ 112 + autoCapitalize: "none", 113 + autoCorrect: "off", 114 + spellCheck: "false" 115 + }} 116 + /> 117 + <FilterBar filter={filter} setFilter={setFilter} selectedTags={selectedTags} setSelectedTags={setSelectedTags} /> 118 + 119 + {filteredWithUpdates.length > 0 && ( 120 + <HelpMessage 121 + icon={CircleInformationIcon} 122 + text="Extension updates are available" 123 + className="moonbase-extension-update-section" 124 + > 125 + <div className="moonbase-help-message-buttons"> 126 + <Button 127 + color={Button.Colors.BRAND} 128 + size={Button.Sizes.TINY} 129 + disabled={hitUpdateAll} 130 + onClick={() => { 131 + setHitUpdateAll(true); 132 + MoonbaseSettingsStore.updateAllExtensions(); 133 + }} 134 + > 135 + Update all 136 + </Button> 137 + <PanelButton 138 + icon={XSmallIcon} 139 + onClick={() => { 140 + MoonbaseSettingsStore.dismissAllExtensionUpdates(); 141 + }} 142 + /> 143 + </div> 144 + </HelpMessage> 145 + )} 146 + 147 + {filteredWithUpdates.map((ext) => ( 148 + <ErrorBoundary> 149 + <ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} /> 150 + </ErrorBoundary> 151 + ))} 152 + {filteredWithUpdates.length > 0 && filteredWithoutUpdates.length > 0 && ( 153 + <FormDivider className="moonbase-update-divider" /> 154 + )} 155 + {filteredWithoutUpdates.map((ext) => ( 156 + <ErrorBoundary> 157 + <ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} /> 158 + </ErrorBoundary> 159 + ))} 160 + </> 161 + ); 162 + }
+205
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx
··· 1 + import { ExtensionTag } from "@moonlight-mod/types"; 2 + import { MoonbaseExtension } from "../../../types"; 3 + 4 + import React from "@moonlight-mod/wp/react"; 5 + import { Text } from "@moonlight-mod/wp/discord/components/common/index"; 6 + 7 + type Dependency = { 8 + id: string; 9 + type: DependencyType; 10 + }; 11 + 12 + enum DependencyType { 13 + Dependency = "dependency", 14 + Optional = "optional", 15 + Incompatible = "incompatible" 16 + } 17 + 18 + export const tagNames: Record<ExtensionTag, string> = { 19 + [ExtensionTag.Accessibility]: "Accessibility", 20 + [ExtensionTag.Appearance]: "Appearance", 21 + [ExtensionTag.Chat]: "Chat", 22 + [ExtensionTag.Commands]: "Commands", 23 + [ExtensionTag.ContextMenu]: "Context Menu", 24 + [ExtensionTag.DangerZone]: "Danger Zone", 25 + [ExtensionTag.Development]: "Development", 26 + [ExtensionTag.Fixes]: "Fixes", 27 + [ExtensionTag.Fun]: "Fun", 28 + [ExtensionTag.Markdown]: "Markdown", 29 + [ExtensionTag.Voice]: "Voice", 30 + [ExtensionTag.Privacy]: "Privacy", 31 + [ExtensionTag.Profiles]: "Profiles", 32 + [ExtensionTag.QualityOfLife]: "Quality of Life", 33 + [ExtensionTag.Library]: "Library" 34 + }; 35 + 36 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 37 + 38 + function InfoSection({ title, children }: { title: string; children: React.ReactNode }) { 39 + return ( 40 + <div 41 + style={{ 42 + marginRight: "1em" 43 + }} 44 + > 45 + <Text variant="eyebrow" className="moonlight-card-info-header"> 46 + {title} 47 + </Text> 48 + 49 + <Text variant="text-sm/normal">{children}</Text> 50 + </div> 51 + ); 52 + } 53 + 54 + function Badge({ 55 + color, 56 + children, 57 + style = {}, 58 + onClick 59 + }: { 60 + color: string; 61 + children: React.ReactNode; 62 + style?: React.CSSProperties; 63 + onClick?: () => void; 64 + }) { 65 + if (onClick) style.cursor ??= "pointer"; 66 + return ( 67 + <span 68 + className="moonlight-card-badge" 69 + style={ 70 + { 71 + "--badge-color": color, 72 + ...style 73 + } as React.CSSProperties 74 + } 75 + onClick={onClick} 76 + > 77 + {children} 78 + </span> 79 + ); 80 + } 81 + 82 + export default function ExtensionInfo({ 83 + ext, 84 + selectTag 85 + }: { 86 + ext: MoonbaseExtension; 87 + selectTag: (tag: string) => void; 88 + }) { 89 + const authors = ext.manifest?.meta?.authors; 90 + const tags = ext.manifest?.meta?.tags; 91 + const version = ext.manifest?.version; 92 + 93 + const dependencies: Dependency[] = []; 94 + const incompatible: Dependency[] = []; 95 + 96 + if (ext.manifest.dependencies != null) { 97 + dependencies.push( 98 + ...ext.manifest.dependencies.map((dep) => ({ 99 + id: dep, 100 + type: DependencyType.Dependency 101 + })) 102 + ); 103 + } 104 + 105 + if (ext.manifest.suggested != null) { 106 + dependencies.push( 107 + ...ext.manifest.suggested.map((dep) => ({ 108 + id: dep, 109 + type: DependencyType.Optional 110 + })) 111 + ); 112 + } 113 + 114 + if (ext.manifest.incompatible != null) { 115 + incompatible.push( 116 + ...ext.manifest.incompatible.map((dep) => ({ 117 + id: dep, 118 + type: DependencyType.Incompatible 119 + })) 120 + ); 121 + } 122 + 123 + return ( 124 + <> 125 + {authors != null && ( 126 + <InfoSection title="Authors"> 127 + {authors.map((author, i) => { 128 + const comma = i !== authors.length - 1 ? ", " : ""; 129 + if (typeof author === "string") { 130 + return ( 131 + <span key={i}> 132 + {author} 133 + {comma} 134 + </span> 135 + ); 136 + } else { 137 + // TODO: resolve IDs 138 + return ( 139 + <span key={i}> 140 + {author.name} 141 + {comma} 142 + </span> 143 + ); 144 + } 145 + })} 146 + </InfoSection> 147 + )} 148 + 149 + {tags != null && ( 150 + <InfoSection title="Tags"> 151 + {tags.map((tag, i) => { 152 + const name = tagNames[tag]; 153 + let color = "var(--bg-mod-strong)"; 154 + let style; 155 + if (tag === ExtensionTag.DangerZone) { 156 + color = "var(--red-460)"; 157 + style = { color: "var(--primary-230)" }; 158 + } 159 + 160 + return ( 161 + <Badge key={i} color={color} style={style} onClick={() => selectTag(tag)}> 162 + {name} 163 + </Badge> 164 + ); 165 + })} 166 + </InfoSection> 167 + )} 168 + 169 + {dependencies.length > 0 && ( 170 + <InfoSection title="Dependencies"> 171 + {dependencies.map((dep) => { 172 + const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id); 173 + 174 + // TODO: figure out a decent way to distinguish suggested 175 + return ( 176 + <Badge color="var(--bg-mod-strong)" key={dep.id}> 177 + {name} 178 + </Badge> 179 + ); 180 + })} 181 + </InfoSection> 182 + )} 183 + 184 + {incompatible.length > 0 && ( 185 + <InfoSection title="Incompatible"> 186 + {incompatible.map((dep) => { 187 + const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id); 188 + 189 + return ( 190 + <Badge color="var(--bg-mod-strong)" key={dep.id}> 191 + {name} 192 + </Badge> 193 + ); 194 + })} 195 + </InfoSection> 196 + )} 197 + 198 + {version != null && ( 199 + <InfoSection title="Version"> 200 + <span>{version}</span> 201 + </InfoSection> 202 + )} 203 + </> 204 + ); 205 + }
+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 + }
+418
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
··· 1 + import { 2 + ExtensionSettingType, 3 + ExtensionSettingsManifest, 4 + MultiSelectSettingType, 5 + NumberSettingType, 6 + SelectOption, 7 + SelectSettingType 8 + } from "@moonlight-mod/types/config"; 9 + 10 + import { ExtensionState, MoonbaseExtension } from "../../../types"; 11 + 12 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 13 + import React from "@moonlight-mod/wp/react"; 14 + import { 15 + FormSwitch, 16 + FormItem, 17 + FormText, 18 + TextInput, 19 + Slider, 20 + TextArea, 21 + Tooltip, 22 + Clickable, 23 + CircleXIcon, 24 + Text, 25 + SingleSelect, 26 + Button, 27 + useVariableSelect, 28 + multiSelect, 29 + Select as DiscordSelect, 30 + NumberInputStepper 31 + } from "@moonlight-mod/wp/discord/components/common/index"; 32 + import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 33 + import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; 34 + import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils"; 35 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 36 + import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary"; 37 + 38 + let GuildSettingsRoleEditClasses: any; 39 + spacepack 40 + .lazyLoad( 41 + "renderArtisanalHack", 42 + /\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"GuildSettings"/, 43 + /webpackId:(\d+),name:"GuildSettings"/ 44 + ) 45 + .then( 46 + () => 47 + (GuildSettingsRoleEditClasses = spacepack.require( 48 + "discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css" 49 + )) 50 + ); 51 + 52 + type SettingsProps = { 53 + ext: MoonbaseExtension; 54 + name: string; 55 + setting: ExtensionSettingsManifest; 56 + disabled: boolean; 57 + }; 58 + type SettingsComponent = React.ComponentType<SettingsProps>; 59 + 60 + const Margins = spacepack.require("discord/styles/shared/Margins.css"); 61 + 62 + function markdownify(str: string) { 63 + return MarkupUtils.parse(str, true, { 64 + hideSimpleEmbedContent: true, 65 + allowLinks: true 66 + }); 67 + } 68 + 69 + function useConfigEntry<T>(uniqueId: number, name: string) { 70 + return useStateFromStores( 71 + [MoonbaseSettingsStore], 72 + () => { 73 + return { 74 + value: MoonbaseSettingsStore.getExtensionConfig<T>(uniqueId, name), 75 + displayName: MoonbaseSettingsStore.getExtensionConfigName(uniqueId, name), 76 + description: MoonbaseSettingsStore.getExtensionConfigDescription(uniqueId, name) 77 + }; 78 + }, 79 + [uniqueId, name] 80 + ); 81 + } 82 + 83 + function Boolean({ ext, name, setting, disabled }: SettingsProps) { 84 + const { value, displayName, description } = useConfigEntry<boolean>(ext.uniqueId, name); 85 + 86 + return ( 87 + <FormSwitch 88 + value={value ?? false} 89 + hideBorder={true} 90 + disabled={disabled} 91 + onChange={(value: boolean) => { 92 + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 93 + }} 94 + note={description != null ? markdownify(description) : undefined} 95 + className={`${Margins.marginReset} ${Margins.marginTop20}`} 96 + > 97 + {displayName} 98 + </FormSwitch> 99 + ); 100 + } 101 + 102 + function Number({ ext, name, setting, disabled }: SettingsProps) { 103 + const { value, displayName, description } = useConfigEntry<number>(ext.uniqueId, name); 104 + 105 + const castedSetting = setting as NumberSettingType; 106 + const min = castedSetting.min; 107 + const max = castedSetting.max; 108 + 109 + const onChange = (value: number) => { 110 + const rounded = min == null || max == null ? Math.round(value) : Math.max(min, Math.min(max, Math.round(value))); 111 + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded); 112 + }; 113 + 114 + return ( 115 + <FormItem className={Margins.marginTop20} title={displayName}> 116 + {min == null || max == null ? ( 117 + <Flex justify={Flex.Justify.BETWEEN} direction={Flex.Direction.HORIZONTAL}> 118 + {description && <FormText>{markdownify(description)}</FormText>} 119 + <NumberInputStepper value={value ?? 0} onChange={onChange} /> 120 + </Flex> 121 + ) : ( 122 + <> 123 + {description && <FormText>{markdownify(description)}</FormText>} 124 + <Slider 125 + initialValue={value ?? 0} 126 + disabled={disabled} 127 + minValue={min} 128 + maxValue={max} 129 + onValueChange={onChange} 130 + onValueRender={(value: number) => `${Math.round(value)}`} 131 + /> 132 + </> 133 + )} 134 + </FormItem> 135 + ); 136 + } 137 + 138 + function String({ ext, name, setting, disabled }: SettingsProps) { 139 + const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name); 140 + 141 + return ( 142 + <FormItem className={Margins.marginTop20} title={displayName}> 143 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 144 + <TextInput 145 + value={value ?? ""} 146 + disabled={disabled} 147 + onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} 148 + /> 149 + </FormItem> 150 + ); 151 + } 152 + 153 + function MultilineString({ ext, name, setting, disabled }: SettingsProps) { 154 + const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name); 155 + 156 + return ( 157 + <FormItem className={Margins.marginTop20} title={displayName}> 158 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 159 + <TextArea 160 + rows={5} 161 + value={value ?? ""} 162 + disabled={disabled} 163 + className={"moonbase-resizeable"} 164 + onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} 165 + /> 166 + </FormItem> 167 + ); 168 + } 169 + 170 + function Select({ ext, name, setting, disabled }: SettingsProps) { 171 + const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name); 172 + 173 + const castedSetting = setting as SelectSettingType; 174 + const options = castedSetting.options; 175 + 176 + return ( 177 + <FormItem className={Margins.marginTop20} title={displayName}> 178 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 179 + <SingleSelect 180 + autofocus={false} 181 + clearable={false} 182 + value={value ?? ""} 183 + options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))} 184 + onChange={(value: string) => { 185 + if (disabled) return; 186 + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); 187 + }} 188 + /> 189 + </FormItem> 190 + ); 191 + } 192 + 193 + function MultiSelect({ ext, name, setting, disabled }: SettingsProps) { 194 + const { value, displayName, description } = useConfigEntry<string | string[]>(ext.uniqueId, name); 195 + 196 + const castedSetting = setting as MultiSelectSettingType; 197 + const options = castedSetting.options; 198 + 199 + return ( 200 + <FormItem className={Margins.marginTop20} title={displayName}> 201 + {description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>} 202 + <DiscordSelect 203 + autofocus={false} 204 + clearable={false} 205 + closeOnSelect={false} 206 + options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))} 207 + {...useVariableSelect({ 208 + onSelectInteraction: multiSelect, 209 + value: value == null ? new Set() : new Set(Array.isArray(value) ? value : [value]), 210 + onChange: (value: string) => { 211 + if (disabled) return; 212 + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Array.from(value)); 213 + } 214 + })} 215 + /> 216 + </FormItem> 217 + ); 218 + } 219 + 220 + function RemoveEntryButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) { 221 + return ( 222 + <div className={GuildSettingsRoleEditClasses.removeButtonContainer}> 223 + <Tooltip text="Remove entry" position="top"> 224 + {(props: any) => ( 225 + <Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}> 226 + <CircleXIcon width={16} height={16} /> 227 + </Clickable> 228 + )} 229 + </Tooltip> 230 + </div> 231 + ); 232 + } 233 + 234 + function List({ ext, name, setting, disabled }: SettingsProps) { 235 + const { value, displayName, description } = useConfigEntry<string[]>(ext.uniqueId, name); 236 + 237 + const entries = value ?? []; 238 + const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries); 239 + 240 + return ( 241 + <FormItem className={Margins.marginTop20} title={displayName}> 242 + {description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>} 243 + <Flex direction={Flex.Direction.VERTICAL}> 244 + {entries.map((val, i) => ( 245 + // FIXME: stylesheets 246 + <div 247 + key={i} 248 + style={{ 249 + display: "grid", 250 + height: "32px", 251 + gap: "8px", 252 + gridTemplateColumns: "1fr 32px", 253 + alignItems: "center" 254 + }} 255 + > 256 + <TextInput 257 + size={TextInput.Sizes.MINI} 258 + value={val} 259 + disabled={disabled} 260 + onChange={(newVal: string) => { 261 + entries[i] = newVal; 262 + updateConfig(); 263 + }} 264 + /> 265 + <RemoveEntryButton 266 + disabled={disabled} 267 + onClick={() => { 268 + entries.splice(i, 1); 269 + updateConfig(); 270 + }} 271 + /> 272 + </div> 273 + ))} 274 + 275 + <Button 276 + look={Button.Looks.FILLED} 277 + color={Button.Colors.GREEN} 278 + size={Button.Sizes.SMALL} 279 + disabled={disabled} 280 + className={Margins.marginTop8} 281 + onClick={() => { 282 + entries.push(""); 283 + updateConfig(); 284 + }} 285 + > 286 + Add new entry 287 + </Button> 288 + </Flex> 289 + </FormItem> 290 + ); 291 + } 292 + 293 + function Dictionary({ ext, name, setting, disabled }: SettingsProps) { 294 + const { value, displayName, description } = useConfigEntry<Record<string, string>>(ext.uniqueId, name); 295 + 296 + const entries = Object.entries(value ?? {}); 297 + const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Object.fromEntries(entries)); 298 + 299 + return ( 300 + <FormItem className={Margins.marginTop20} title={displayName}> 301 + {description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>} 302 + <Flex direction={Flex.Direction.VERTICAL}> 303 + {entries.map(([key, val], i) => ( 304 + // FIXME: stylesheets 305 + <div 306 + key={i} 307 + style={{ 308 + display: "grid", 309 + height: "32px", 310 + gap: "8px", 311 + gridTemplateColumns: "1fr 1fr 32px", 312 + alignItems: "center" 313 + }} 314 + > 315 + <TextInput 316 + size={TextInput.Sizes.MINI} 317 + value={key} 318 + disabled={disabled} 319 + onChange={(newKey: string) => { 320 + entries[i][0] = newKey; 321 + updateConfig(); 322 + }} 323 + /> 324 + <TextInput 325 + size={TextInput.Sizes.MINI} 326 + value={val} 327 + disabled={disabled} 328 + onChange={(newValue: string) => { 329 + entries[i][1] = newValue; 330 + updateConfig(); 331 + }} 332 + /> 333 + <RemoveEntryButton 334 + disabled={disabled} 335 + onClick={() => { 336 + entries.splice(i, 1); 337 + updateConfig(); 338 + }} 339 + /> 340 + </div> 341 + ))} 342 + 343 + <Button 344 + look={Button.Looks.FILLED} 345 + color={Button.Colors.GREEN} 346 + size={Button.Sizes.SMALL} 347 + className={Margins.marginTop8} 348 + disabled={disabled} 349 + onClick={() => { 350 + entries.push([`entry-${entries.length}`, ""]); 351 + updateConfig(); 352 + }} 353 + > 354 + Add new entry 355 + </Button> 356 + </Flex> 357 + </FormItem> 358 + ); 359 + } 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 + 387 + function Setting({ ext, name, setting, disabled }: SettingsProps) { 388 + const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = { 389 + [ExtensionSettingType.Boolean]: Boolean, 390 + [ExtensionSettingType.Number]: Number, 391 + [ExtensionSettingType.String]: String, 392 + [ExtensionSettingType.MultilineString]: MultilineString, 393 + [ExtensionSettingType.Select]: Select, 394 + [ExtensionSettingType.MultiSelect]: MultiSelect, 395 + [ExtensionSettingType.List]: List, 396 + [ExtensionSettingType.Dictionary]: Dictionary, 397 + [ExtensionSettingType.Custom]: Custom 398 + }; 399 + const element = elements[setting.type]; 400 + if (element == null) return <></>; 401 + return React.createElement(element, { ext, name, setting, disabled }); 402 + } 403 + 404 + export default function Settings({ ext }: { ext: MoonbaseExtension }) { 405 + return ( 406 + <Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}> 407 + {Object.entries(ext.settingsOverride ?? ext.manifest.settings!).map(([name, setting]) => ( 408 + <Setting 409 + ext={ext} 410 + key={name} 411 + name={name} 412 + setting={setting} 413 + disabled={ext.state === ExtensionState.NotDownloaded} 414 + /> 415 + ))} 416 + </Flex> 417 + ); 418 + }
+85
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
··· 1 + import React from "@moonlight-mod/wp/react"; 2 + import { Text, TabBar } from "@moonlight-mod/wp/discord/components/common/index"; 3 + import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; 4 + import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores"; 5 + 6 + import ExtensionsPage from "./extensions"; 7 + import ConfigPage from "./config"; 8 + import AboutPage from "./about"; 9 + import Update from "./update"; 10 + import RestartAdviceMessage from "./RestartAdvice"; 11 + import { Divider } from "@moonlight-mod/wp/discord/components/common/BaseHeaderBar"; 12 + import HeaderBarClasses from "@moonlight-mod/wp/discord/components/common/HeaderBar.css"; 13 + import PeoplePageClasses from "@moonlight-mod/wp/discord/modules/people/web/PeoplePage.css"; 14 + import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators"; 15 + import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css"; 16 + 17 + export const pages: { 18 + id: string; 19 + name: string; 20 + element: React.FunctionComponent; 21 + }[] = [ 22 + { 23 + id: "extensions", 24 + name: "Extensions", 25 + element: ExtensionsPage 26 + }, 27 + { 28 + id: "config", 29 + name: "Config", 30 + element: ConfigPage 31 + }, 32 + { 33 + id: "about", 34 + name: "About", 35 + element: AboutPage 36 + } 37 + ]; 38 + 39 + export function Moonbase(props: { initialTab?: number } = {}) { 40 + const subsection = useStateFromStores([UserSettingsModalStore], () => UserSettingsModalStore.getSubsection() ?? 0); 41 + const setSubsection = React.useCallback( 42 + (to: string) => { 43 + if (subsection !== to) UserSettingsModalActionCreators.setSection("moonbase", to); 44 + }, 45 + [subsection] 46 + ); 47 + 48 + React.useEffect( 49 + () => () => { 50 + // Normally there's an onSettingsClose prop you can set but we don't expose it and I don't care enough to add support for it right now 51 + UserSettingsModalActionCreators.clearSubsection("moonbase"); 52 + }, 53 + [] 54 + ); 55 + 56 + return ( 57 + <> 58 + <div className={`${HeaderBarClasses.children} ${Margins.marginBottom20}`}> 59 + <Text className={HeaderBarClasses.titleWrapper} variant="heading-lg/semibold" tag="h2"> 60 + Moonbase 61 + </Text> 62 + <Divider /> 63 + <TabBar 64 + selectedItem={subsection} 65 + onItemSelect={setSubsection} 66 + type="top-pill" 67 + className={PeoplePageClasses.tabBar} 68 + > 69 + {pages.map((page, i) => ( 70 + <TabBar.Item key={page.id} id={i} className={PeoplePageClasses.item}> 71 + {page.name} 72 + </TabBar.Item> 73 + ))} 74 + </TabBar> 75 + </div> 76 + 77 + <RestartAdviceMessage /> 78 + <Update /> 79 + 80 + {React.createElement(pages[subsection].element)} 81 + </> 82 + ); 83 + } 84 + 85 + export { RestartAdviceMessage, Update };
+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);
+12
packages/core-extensions/src/moonbase/wp.d.ts
··· 1 + declare module "@moonlight-mod/wp/moonbase_ui" { 2 + export * from "core-extensions/src/moonbase/webpackModules/ui"; 3 + } 4 + 5 + declare module "@moonlight-mod/wp/moonbase_stores" { 6 + export * from "core-extensions/src/moonbase/webpackModules/stores"; 7 + } 8 + 9 + declare module "@moonlight-mod/wp/moonbase_ThemeDarkIcon" { 10 + import ThemeDarkIcon from "core-extensions/src/moonbase/webpackModules/ThemeDarkIcon"; 11 + export = ThemeDarkIcon; 12 + }
+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(){", 5 + find: "hideToken:()=>", 6 6 replace: { 7 - match: /hideToken\(\)\{.+?},/, 8 - replacement: `hideToken(){},` 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 -43
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: /\.then\(function\(\){var.+?"MODULE_NOT_FOUND",.\}\)/, 14 - replacement: ".then(()=>(()=>{}))" 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 - ['.displayName="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\(0,.{1,2}\.default\)\(\)\.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 - '.AnalyticEvents.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 - '.AnalyticEvents.APP_NATIVE_CRASH,"Storage"', 47 - 'console.log("AppCrashedFatalReport: getLastCrash not supported.");' 64 + '"Unable to determine render window for element"', 65 + /console\.warn\("Unable to determine render window for element",\i\),/ 48 66 ], 49 67 [ 50 - '"[NATIVE INFO] ', 51 - /new\(0,.{1,2}\.default\)\(\)\.log\("\[NATIVE INFO] .+?\)\),/ 68 + '"Unable to determine render window for element"', 69 + /console\.warn\('Unable to find element constructor "'\.concat\(\i,'" in'\),\i\),/ 52 70 ], 53 - ['"Spellchecker"', /.\.info\("Switching to ".+?"\(unavailable\)"\);?/g], 54 71 [ 55 - 'throw new Error("Messages are still loading.");', 56 - /console\.warn\("Unsupported Locale",.\);/ 72 + '"[PostMessageTransport] Protocol error: event data should be an Array!"', 73 + /void console\.warn\("\[PostMessageTransport] Protocol error: event data should be an Array!"\)/ 57 74 ], 58 - ["_dispatchWithDevtools=", /.\.has\(.\.type\)&&.\.log\(.+?\);/], 59 - ["_dispatchWithDevtools=", /.\.totalTime>100&&.\.log\(.+?\);0;/], 60 75 [ 61 - '"NativeDispatchUtils"', 62 - /null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/ 63 - ], 64 - [ 65 - 'Error("Messages are still loading.")', 66 - /console\.warn\("Unsupported Locale",.\),/ 67 - ], 68 - ['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/], 69 - [ 70 - '"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "', 71 - /.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/ 76 + '("ComponentDispatchUtils")', 77 + /new \i\.Z\("ComponentDispatchUtils"\)\.warn\("ComponentDispatch\.resubscribe: Resubscribe without existing subscription",\i\),/ 72 78 ] 73 79 ]; 74 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 + 75 101 const simplePatches = [ 76 102 // Moment.js deprecation warnings 77 - ["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"], 78 - 79 - // Zustand related 80 - [ 81 - /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\)/g, 82 - "/*$&*/" 83 - ] 103 + ["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"] 84 104 ] as { [0]: string | RegExp; [1]: string }[]; 85 105 86 106 export const patches: Patch[] = [ 87 107 { 88 - find: ".Messages.XSSDefenses", 108 + find: ".Messages.SELF_XSS_HEADER", 89 109 replace: { 90 - match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/, 110 + match: /\(null!=\i&&"0\.0\.0"===\i\.remoteApp\.getVersion\(\)\)/, 91 111 replacement: "(true)" 92 112 } 93 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 + }, 94 141 ...loggerFixes, 95 142 ...stubPatches.map((patch) => ({ 96 143 find: patch[0], ··· 105 152 replace: { 106 153 match: patch[0], 107 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: "(()=>{})(" 108 163 }, 109 164 prerequisite: notXssDefensesOnly 110 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 + }
+6 -8
packages/core-extensions/src/settings/index.ts
··· 3 3 4 4 export const patches: Patch[] = [ 5 5 { 6 - find: ".UserSettingsSections.HOTSPOT_OPTIONS", 6 + find: '"useGenerateUserSettingsSections"', 7 7 replace: { 8 - match: /\.CUSTOM,element:(.+?)}\];return (.{1,2})/, 9 - replacement: (_, lastElement, sections) => 10 - `.CUSTOM,element:${lastElement}}];return 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 - /Object\.values\(.\.UserSettingsSections\)/, 20 - (orig) => 21 - `[...require("settings_settings").Settings.sectionNames,...${orig}]` 18 + /Object\.values\(.\..+?\)/, 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",
+18 -12
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: [], 7 + sectionMenuItems: {}, 9 8 10 - addSection: (section, label, element, color = null, pos, notice) => { 9 + addSection: (section, label, element, color = null, pos, notice, onClick) => { 11 10 const data: SettingsSection = { 12 11 section, 13 12 label, 14 13 color, 15 14 element, 16 15 pos: pos ?? -4, 17 - notice: notice 16 + notice: notice, 17 + onClick: onClick ?? (() => UserSettingsModalActionCreators.open(section)) 18 18 }; 19 19 20 20 Settings.ourSections.push(data); 21 - Settings.sectionNames.push(label); 21 + Settings.sectionNames.push(section); 22 22 return data; 23 23 }, 24 + addSectionMenuItems(section, ...newItems) { 25 + const data = Settings.ourSections.find((x) => x.section === section); 26 + if (!data || !("element" in data)) throw new Error(`Could not find section "${section}"`); 27 + (Settings.sectionMenuItems[section] ??= []).push(...newItems); 28 + data._moonlight_submenu ??= () => Settings.sectionMenuItems[section]; 29 + }, 24 30 25 31 addDivider: (pos = null) => { 26 32 Settings.ourSections.push({ ··· 39 45 40 46 _mutateSections: (sections) => { 41 47 for (const section of Settings.ourSections) { 42 - sections.splice( 43 - section.pos < 0 ? sections.length + section.pos : section.pos, 44 - 0, 45 - section 46 - ); 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); 47 53 } 48 54 49 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",
+100 -62
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: (find: string | RegExp | (string | RegExp)[], match: RegExp) => { 166 - const module = Array.isArray(find) 167 - ? spacepack.findByCode(...find) 168 - : spacepack.findByCode(find); 169 - if (module.length < 1) return Promise.reject("Find failed"); 186 + lazyLoad: (find: string | RegExp | (string | RegExp)[], chunk: RegExp, module: RegExp) => { 187 + chunk = processFind(chunk); 188 + module = processFind(module); 170 189 171 - const findId = module[0].id; 190 + const mod = Array.isArray(find) ? spacepack.findByCode(...find) : spacepack.findByCode(find); 191 + if (mod.length < 1) { 192 + logger.warn("lazyLoad: Module find failed", find, chunk, module, new Error().stack!.substring(5)); 193 + return Promise.reject("Module find failed"); 194 + } 195 + 196 + const findId = mod[0].id; 172 197 const findCode = webpackRequire.m[findId].toString().replace(/\n/g, ""); 173 198 174 - const matchResult = findCode.match(match); 175 - if (!matchResult) return Promise.reject("Match failed"); 199 + let chunkIds; 200 + if (chunk.flags.includes("g")) { 201 + chunkIds = [...findCode.matchAll(chunk)].map(([, id]) => id); 202 + } else { 203 + const match = findCode.match(chunk); 204 + if (match) chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id); 205 + } 176 206 177 - const matchId = matchResult[1]; 178 - return webpackRequire.el(matchId).then(() => webpackRequire(matchId)); 207 + if (!chunkIds || chunkIds.length === 0) { 208 + logger.warn("lazyLoad: Chunk ID match failed", find, chunk, module, new Error().stack!.substring(5)); 209 + return Promise.reject("Chunk ID match failed"); 210 + } 211 + 212 + const moduleId = findCode.match(module)?.[1]; 213 + if (!moduleId) { 214 + logger.warn("lazyLoad: Module ID match failed", find, chunk, module, new Error().stack!.substring(5)); 215 + return Promise.reject("Module ID match failed"); 216 + } 217 + 218 + return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() => webpackRequire(moduleId)); 179 219 }, 180 220 181 221 filterReal: (modules: WebpackModule[]) => { ··· 183 223 } 184 224 }; 185 225 186 - if ( 187 - moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true 188 - ) { 226 + if (moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true) { 189 227 window.spacepack = spacepack; 190 228 } 191 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 -7
packages/types/package.json
··· 1 1 { 2 2 "name": "@moonlight-mod/types", 3 - "version": "1.1.6", 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/node": "^20.6.2", 14 - "@types/react": "^18.2.22", 15 - "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", 16 24 "standalone-electron-types": "^1.0.0" 17 25 } 18 26 }
+56 -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; ··· 19 17 Boolean = "boolean", 20 18 Number = "number", 21 19 String = "string", 20 + MultilineString = "multilinestring", 22 21 Select = "select", 23 22 MultiSelect = "multiselect", 24 23 List = "list", ··· 34 33 }; 35 34 36 35 export type BooleanSettingType = { 36 + /** 37 + * Displays as a simple switch. 38 + */ 37 39 type: ExtensionSettingType.Boolean; 38 40 default?: boolean; 39 41 }; 40 42 41 43 export type NumberSettingType = { 44 + /** 45 + * Displays as a simple slider. 46 + */ 42 47 type: ExtensionSettingType.Number; 43 48 default?: number; 44 49 min?: number; ··· 46 51 }; 47 52 48 53 export type StringSettingType = { 54 + /** 55 + * Displays as a single line string input. 56 + */ 49 57 type: ExtensionSettingType.String; 50 58 default?: string; 51 59 }; 52 60 61 + export type MultilineTextInputSettingType = { 62 + /** 63 + * Displays as a multiple line string input. 64 + */ 65 + type: ExtensionSettingType.MultilineString; 66 + default?: string; 67 + }; 68 + 53 69 export type SelectSettingType = { 70 + /** 71 + * A dropdown to pick between one of many values. 72 + */ 54 73 type: ExtensionSettingType.Select; 55 74 options: SelectOption[]; 56 75 default?: string; 57 76 }; 58 77 59 78 export type MultiSelectSettingType = { 79 + /** 80 + * A dropdown to pick multiple values. 81 + */ 60 82 type: ExtensionSettingType.MultiSelect; 61 83 options: string[]; 62 84 default?: string[]; 63 85 }; 64 86 65 87 export type ListSettingType = { 88 + /** 89 + * A list of strings that the user can add or remove from. 90 + */ 66 91 type: ExtensionSettingType.List; 67 92 default?: string[]; 68 93 }; 69 94 70 95 export type DictionarySettingType = { 96 + /** 97 + * A dictionary (key-value pair) that the user can add or remove from. 98 + */ 71 99 type: ExtensionSettingType.Dictionary; 72 100 default?: Record<string, string>; 73 101 }; 74 102 75 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 + */ 76 108 type: ExtensionSettingType.Custom; 77 109 default?: any; 78 110 }; 79 111 112 + export enum ExtensionSettingsAdvice { 113 + None = "none", 114 + Reload = "reload", 115 + Restart = "restart" 116 + } 117 + 80 118 export type ExtensionSettingsManifest = { 119 + /** 120 + * A human friendly name for the setting. 121 + */ 81 122 displayName?: string; 123 + 124 + /** 125 + * A longer description for the setting. 126 + * Markdown is not supported. 127 + */ 82 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; 83 135 } & ( 84 136 | BooleanSettingType 85 137 | NumberSettingType 86 138 | StringSettingType 139 + | MultilineTextInputSettingType 87 140 | SelectSettingType 88 141 | MultiSelectSettingType 89 142 | ListSettingType
+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
-363
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 FormTextTypes { 47 - DEFAULT = "default", 48 - DESCRIPTION = "description", 49 - ERROR = "error", 50 - INPUT_PLACEHOLDER = "placeholder", 51 - LABEL_BOLD = "labelBold", 52 - LABEL_DESCRIPTOR = "labelDescriptor", 53 - LABEL_SELECTED = "labelSelected", 54 - SUCCESS = "success" 55 - } 56 - 57 - interface FormText 58 - extends ComponentClass< 59 - PropsWithChildren<{ 60 - type?: FormTextTypes; 61 - className?: string; 62 - disabled?: boolean; 63 - selectable?: boolean; 64 - style?: CSSProperties; 65 - }> 66 - > { 67 - Types: FormTextTypes; 68 - } 69 - 70 - declare enum SliderMarkerPosition { 71 - ABOVE, 72 - BELOW 73 - } 74 - 75 - declare enum ButtonLooks { 76 - FILLED = "lookFilled", 77 - INVERTED = "lookInverted", 78 - OUTLINED = "lookOutlined", 79 - LINK = "lookLink", 80 - BLANK = "lookBlank" 81 - } 82 - declare enum ButtonColors { 83 - BRAND = "colorBrand", 84 - RED = "colorRed", 85 - GREEN = "colorGreen", 86 - YELLOW = "colorYellow", 87 - PRIMARY = "colorPrimary", 88 - LINK = "colorLink", 89 - WHITE = "colorWhite", 90 - BLACK = "colorBlack", 91 - TRANSPARENT = "colorTransparent", 92 - BRAND_NEW = "colorBrandNew", 93 - CUSTOM = "" 94 - } 95 - declare enum ButtonBorderColors { 96 - BRAND = "borderBrand", 97 - RED = "borderRed", 98 - GREEN = "borderGreen", 99 - YELLOW = "borderYellow", 100 - PRIMARY = "borderPrimary", 101 - LINK = "borderLink", 102 - WHITE = "borderWhite", 103 - BLACK = "borderBlack", 104 - TRANSPARENT = "borderTransparent", 105 - BRAND_NEW = "borderBrandNew" 106 - } 107 - declare enum ButtonHovers { 108 - DEFAULT = "", 109 - BRAND = "hoverBrand", 110 - RED = "hoverRed", 111 - GREEN = "hoverGreen", 112 - YELLOW = "hoverYellow", 113 - PRIMARY = "hoverPrimary", 114 - LINK = "hoverLink", 115 - WHITE = "hoverWhite", 116 - BLACK = "hoverBlack", 117 - TRANSPARENT = "hoverTransparent" 118 - } 119 - declare enum ButtonSizes { 120 - NONE = "", 121 - TINY = "sizeTiny", 122 - SMALL = "sizeSmall", 123 - MEDIUM = "sizeMedium", 124 - LARGE = "sizeLarge", 125 - XLARGE = "sizeXlarge", 126 - MIN = "sizeMin", 127 - MAX = "sizeMax", 128 - ICON = "sizeIcon" 129 - } 130 - 131 - type Button = ComponentType< 132 - PropsWithChildren<{ 133 - look?: ButtonLooks; 134 - color?: ButtonColors; 135 - borderColor?: ButtonBorderColors; 136 - hover?: ButtonHovers; 137 - size?: ButtonSizes; 138 - fullWidth?: boolean; 139 - grow?: boolean; 140 - disabled?: boolean; 141 - submitting?: boolean; 142 - type?: string; 143 - style?: CSSProperties; 144 - wrapperClassName?: string; 145 - className?: string; 146 - innerClassName?: string; 147 - onClick?: MouseEventHandler; 148 - onDoubleClick?: MouseEventHandler; 149 - onMouseDown?: MouseEventHandler; 150 - onMouseUp?: MouseEventHandler; 151 - onMouseEnter?: MouseEventHandler; 152 - onMouseLeave?: MouseEventHandler; 153 - onKeyDown?: KeyboardEventHandler; 154 - rel?: any; 155 - buttonRef?: Ref<any>; 156 - focusProps?: PropsWithChildren<any>; 157 - "aria-label"?: string; 158 - submittingStartedLabel?: string; 159 - submittingFinishedLabel?: string; 160 - }> 161 - > & { 162 - Looks: typeof ButtonLooks; 163 - Colors: typeof ButtonColors; 164 - BorderColors: typeof ButtonBorderColors; 165 - Hovers: typeof ButtonHovers; 166 - Sizes: typeof ButtonSizes; 167 - }; 168 - 169 - export enum FlexDirection { 170 - VERTICAL = "vertical", 171 - HORIZONTAL = "horizontal", 172 - HORIZONTAL_REVERSE = "horizontalReverse" 173 - } 174 - 175 - declare enum FlexAlign { 176 - START = "alignStart", 177 - END = "alignEnd", 178 - CENTER = "alignCenter", 179 - STRETCH = "alignStretch", 180 - BASELINE = "alignBaseline" 181 - } 182 - declare enum FlexJustify { 183 - START = "justifyStart", 184 - END = "justifyEnd", 185 - CENTER = "justifyCenter", 186 - BETWEEN = "justifyBetween", 187 - AROUND = "justifyAround" 188 - } 189 - declare enum FlexWrap { 190 - NO_WRAP = "noWrap", 191 - WRAP = "wrap", 192 - WRAP_REVERSE = "wrapReverse" 193 - } 194 - interface Flex 195 - extends ComponentClass< 196 - PropsWithChildren<{ 197 - className?: string; 198 - direction?: FlexDirection; 199 - justify?: FlexJustify; 200 - align?: FlexAlign; 201 - wrap?: FlexWrap; 202 - shrink?: CSS.Property.FlexShrink; 203 - grow?: CSS.Property.FlexGrow; 204 - basis?: CSS.Property.FlexBasis; 205 - style?: CSSProperties; 206 - }> 207 - > { 208 - Direction: typeof FlexDirection; 209 - Align: typeof FlexAlign; 210 - Justify: typeof FlexJustify; 211 - Wrap: typeof FlexWrap; 212 - Child: Component< 213 - PropsWithChildren<{ 214 - className?: string; 215 - shrink?: CSS.Property.FlexShrink; 216 - grow?: CSS.Property.FlexGrow; 217 - basis?: CSS.Property.FlexBasis; 218 - style?: CSSProperties; 219 - wrap?: boolean; 220 - }> 221 - >; 222 - } 223 - 224 - // TODO: wtaf is up with react types not working in jsx 225 - export type CommonComponents = { 226 - Clickable: ComponentClass< 227 - PropsWithChildren<{ 228 - onClick?: () => void; 229 - href?: any; 230 - onKeyPress?: () => void; 231 - ignoreKeyPress?: boolean; 232 - innerRef?: Ref<any>; 233 - focusProps?: any; 234 - tag?: string | Component; 235 - role?: any; 236 - tabIndex?: any; 237 - className?: string; 238 - }> 239 - >; 240 - TextInput: TextInput; 241 - FormDivider: ComponentClass<any>; 242 - FormSection: ComponentClass< 243 - PropsWithChildren<{ 244 - className?: string; 245 - titleClassName?: string; 246 - title?: ReactNode; 247 - icon?: ReactNode; 248 - disabled?: boolean; 249 - htmlFor?: any; 250 - tag?: string; 251 - }> 252 - >; 253 - FormText: FormText; 254 - FormTitle: ComponentClass< 255 - PropsWithChildren<{ 256 - tag?: string; 257 - className?: string; 258 - faded?: boolean; 259 - disabled?: boolean; 260 - required?: boolean; 261 - error?: string; 262 - }> 263 - >; 264 - FormSwitch: ComponentClass<PropsWithChildren<any>>; 265 - FormItem: ComponentClass<PropsWithChildren<any>>; 266 - Slider: ComponentClass< 267 - PropsWithChildren<{ 268 - disabled?: boolean; 269 - stickToMarkers?: boolean; 270 - className?: string; 271 - barStyles?: CSSProperties; 272 - fillStyles?: CSSProperties; 273 - mini?: boolean; 274 - hideBubble?: boolean; 275 - initialValue?: number; 276 - orientation?: "horizontal" | "vertical"; 277 - onValueRender?: (value: number) => string; 278 - renderMarker?: (marker: number) => ReactNode; 279 - getAriaValueText?: (value: number) => string; 280 - barClassName?: string; 281 - grabberClassName?: string; 282 - grabberStyles?: CSSProperties; 283 - markerPosition?: SliderMarkerPosition; 284 - "aria-hidden"?: "true" | "false"; 285 - "aria-label"?: string; 286 - "aria-labelledby"?: string; 287 - "aria-describedby"?: string; 288 - minValue?: number; 289 - maxValue?: number; 290 - asValueChanges?: (value: number) => void; 291 - onValueChange?: (value: number) => void; 292 - keyboardStep?: number; 293 - }> 294 - >; 295 - Switch: ComponentClass<PropsWithChildren<any>>; 296 - Button: Button; 297 - Tooltip: ComponentClass<PropsWithChildren<any>>; 298 - SmallSlider: Component; 299 - Avatar: Component; 300 - Scroller: Component; 301 - Text: ComponentClass<PropsWithChildren<any>>; 302 - Heading: ComponentClass<PropsWithChildren<any>>; 303 - LegacyText: Component; 304 - Flex: Flex; 305 - Card: ComponentClass<PropsWithChildren<any>>; 306 - Popout: ComponentClass<PropsWithChildren<any>>; 307 - Dialog: ComponentClass<PropsWithChildren<any>>; 308 - Menu: ComponentClass<PropsWithChildren<any>>; 309 - MenuItem: ComponentClass<PropsWithChildren<any>>; 310 - MenuGroup: ComponentClass<PropsWithChildren<any>>; 311 - MenuCheckboxItem: ComponentClass<PropsWithChildren<any>>; 312 - CardClasses: { 313 - card: string; 314 - cardHeader: string; 315 - }; 316 - ControlClasses: { 317 - container: string; 318 - control: string; 319 - disabled: string; 320 - dividerDefault: string; 321 - labelRow: string; 322 - note: string; 323 - title: string; 324 - titleDefault: string; 325 - titleMini: string; 326 - }; 327 - MarkdownParser: { 328 - parse: (text: string) => ReactElement; 329 - }; 330 - SettingsNotice: React.ComponentType<{ 331 - submitting: boolean; 332 - onReset: () => void; 333 - onSave: () => void; 334 - }>; 335 - TabBar: React.ComponentType<any> & { 336 - Item: React.ComponentType<any>; 337 - }; 338 - SingleSelect: React.ComponentType<{ 339 - autofocus?: boolean; 340 - clearable?: boolean; 341 - value?: string; 342 - options?: { 343 - value: string; 344 - label: string; 345 - }[]; 346 - onChange?: (value: string) => void; 347 - }>; 348 - Select: React.ComponentType<{ 349 - autofocus?: boolean; 350 - clearable?: boolean; 351 - value?: string[]; 352 - options?: { 353 - value: string; 354 - label: string; 355 - }[]; 356 - onChange?: (value: string[]) => void; 357 - }>; 358 - 359 - // TODO 360 - useVariableSelect: any; 361 - multiSelect: any; 362 - tokens: any; 363 - };
+64
packages/types/src/coreExtensions/contextMenu.ts
··· 1 + import { 2 + Menu, 3 + MenuCheckboxItem, 4 + MenuControlItem, 5 + MenuGroup, 6 + MenuRadioItem, 7 + MenuSeparator, 8 + MenuItem, 9 + MenuElement 10 + } from "@moonlight-mod/mappings/discord/components/common/index"; 11 + 12 + export type ContextMenu = { 13 + /** 14 + * Registers a new context menu item for a given context menu type. 15 + * @param navId The navigation ID for the target context menu (e.g. "user-context", "message") 16 + * @param item A React component 17 + * @param anchor An existing item's ID to anchor the new item to 18 + * @param before Whether to insert the new item before the anchor item 19 + */ 20 + addItem: (navId: string, item: React.FC<any>, anchor: string | RegExp, before?: boolean) => void; 21 + 22 + MenuCheckboxItem: MenuCheckboxItem; 23 + MenuControlItem: MenuControlItem; 24 + MenuGroup: MenuGroup; 25 + MenuItem: MenuItem; 26 + MenuRadioItem: MenuRadioItem; 27 + MenuSeparator: MenuSeparator; 28 + }; 29 + 30 + export type InternalItem = { 31 + type: string; 32 + key?: string; 33 + }; 34 + 35 + export type InternalSeparator = { 36 + type: "separator"; 37 + navigable: false; 38 + }; 39 + export type InternalGroupStart = { 40 + type: "groupstart"; 41 + length: number; 42 + navigable: false; 43 + props: React.ComponentProps<MenuGroup>; 44 + }; 45 + export type InternalGroupEnd = { 46 + type: "groupend"; 47 + } & Omit<InternalGroupStart, "type">; 48 + export type InternalCustomItem = { 49 + type: "customitem"; 50 + key: any; 51 + navigable?: boolean; 52 + render: any; 53 + props: Extract<React.ComponentProps<MenuItem>, { render: any }>; 54 + }; 55 + export type InternalItem_ = { 56 + type: "item"; 57 + key: any; 58 + navigable: true; 59 + label: string; 60 + }; 61 + 62 + export type EvilItemParser = (el: MenuElement | MenuElement[]) => InternalItem[]; 63 + 64 + export type { Menu, MenuElement };
+20 -23
packages/types/src/coreExtensions/markdown.ts
··· 11 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 + };
+9 -77
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 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 - match: RegExp 33 - ) => Promise<any>; 34 - filterReal: (modules: WebpackModule[]) => WebpackModule[]; 35 - }; 36 - 37 - export type NoticeProps = { 38 - stores: Store<any>[]; 39 - element: React.FunctionComponent; 40 - }; 41 - 42 - export type SettingsSection = 43 - | { section: "DIVIDER"; pos: number } 44 - | { section: "HEADER"; label: string; pos: number } 45 - | { 46 - section: string; 47 - label: string; 48 - color: string | null; 49 - element: React.FunctionComponent; 50 - pos: number; 51 - notice?: NoticeProps; 52 - _moonlight_submenu?: () => any; 53 - }; 54 - 55 - export type Settings = { 56 - ourSections: SettingsSection[]; 57 - sectionNames: string[]; 58 - 59 - addSection: ( 60 - section: string, 61 - label: string, 62 - element: React.FunctionComponent, 63 - color?: string | null, 64 - pos?: number, 65 - notice?: NoticeProps 66 - ) => void; 67 - 68 - addDivider: (pos: number | null) => void; 69 - addHeader: (label: string, pos: number | null) => void; 70 - _mutateSections: (sections: SettingsSection[]) => SettingsSection[]; 71 - }; 72 - 73 - export type CommonReact = typeof import("react"); 74 - export type CommonFlux = FluxDefault; 75 - export type CommonComponents = CommonComponents_; // lol 76 - export type CommonFluxDispatcher = Dispatcher<any>; 77 - 1 + export * as Spacepack from "./coreExtensions/spacepack"; 2 + export * as Settings from "./coreExtensions/settings"; 78 3 export * as Markdown from "./coreExtensions/markdown"; 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 - }
+32 -19
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"; 5 + import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu"; 9 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"; 10 11 11 12 declare function WebpackRequire(id: string): any; 12 - declare function WebpackRequire(id: "spacepack_spacepack"): { 13 - default: Spacepack; 14 - spacepack: Spacepack; 15 - }; 13 + 14 + declare function WebpackRequire(id: "appPanels_appPanels"): AppPanels; 15 + 16 + declare function WebpackRequire(id: "commands_commands"): Commands; 17 + 18 + declare function WebpackRequire(id: "common_ErrorBoundary"): ErrorBoundary; 19 + declare function WebpackRequire(id: "common_icons"): Icons; 20 + 21 + declare function WebpackRequire(id: "componentEditor_dmList"): DMList; 22 + declare function WebpackRequire(id: "componentEditor_memberList"): MemberList; 23 + declare function WebpackRequire(id: "componentEditor_messages"): Messages; 24 + 25 + declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser; 26 + declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu; 27 + 28 + declare function WebpackRequire(id: "markdown_markdown"): Markdown; 29 + 30 + declare function WebpackRequire(id: "moonbase_moonbase"): Moonbase; 16 31 17 - declare function WebpackRequire(id: "common_components"): CommonComponents; 18 - declare function WebpackRequire(id: "common_flux"): CommonFlux; 19 - declare function WebpackRequire( 20 - id: "common_fluxDispatcher" 21 - ): CommonFluxDispatcher; 22 - declare function WebpackRequire(id: "common_react"): CommonReact; 32 + declare function WebpackRequire(id: "notices_notices"): Notices; 23 33 24 34 declare function WebpackRequire(id: "settings_settings"): { 25 35 Settings: Settings; 26 36 default: Settings; 27 37 }; 28 38 29 - declare function WebpackRequire(id: "markdown_markdown"): Markdown; 39 + declare function WebpackRequire(id: "spacepack_spacepack"): { 40 + default: Spacepack; 41 + spacepack: Spacepack; 42 + }; 30 43 31 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 - el: (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 + };
+68 -14
packages/types/src/globals.ts
··· 1 - import { Logger } from "./logger"; 2 - import { Config, ConfigExtension } from "./config"; 3 - import { 4 - DetectedExtension, 5 - IdentifiedPatch, 6 - ProcessedExtensions 7 - } from "./extension"; 8 - 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"; 9 15 10 16 export type MoonlightHost = { 11 - asarPath: string; 12 17 config: Config; 13 - events: EventEmitter; 14 18 extensions: DetectedExtension[]; 15 19 processedExtensions: ProcessedExtensions; 20 + asarPath: string; 21 + events: EventEmitter; 22 + 23 + version: string; 24 + branch: MoonlightBranch; 16 25 17 26 getConfig: (ext: string) => ConfigExtension["config"]; 27 + getConfigPath: () => Promise<string>; 18 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 + 19 32 getLogger: (id: string) => Logger; 33 + getMoonlightDir: () => string; 34 + getExtensionDir: (ext: string) => string; 20 35 }; 21 36 22 37 export type MoonlightNode = { ··· 24 39 extensions: DetectedExtension[]; 25 40 processedExtensions: ProcessedExtensions; 26 41 nativesCache: Record<string, any>; 42 + isBrowser: boolean; 43 + events: MoonlightEventEmitter<NodeEventType, NodeEventPayloads>; 44 + 45 + version: string; 46 + branch: MoonlightBranch; 27 47 28 48 getConfig: (ext: string) => ConfigExtension["config"]; 29 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 + 30 53 getNatives: (ext: string) => any | undefined; 31 54 getLogger: (id: string) => Logger; 55 + getMoonlightDir: () => string; 56 + getExtensionDir: (ext: string) => string; 57 + }; 32 58 33 - getExtensionDir: (ext: string) => string; 34 - writeConfig: (config: Config) => void; 59 + export type MoonlightNodeSandboxed = { 60 + fs: MoonlightFS; 61 + addCors: (url: string) => void; 62 + addBlocked: (url: string) => void; 35 63 }; 36 64 37 65 export type MoonlightWeb = { 66 + patched: Map<string, Set<string>>; 38 67 unpatched: Set<IdentifiedPatch>; 68 + pendingModules: Set<IdentifiedWebpackModule>; 39 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; 40 77 41 - getConfig: (ext: string) => ConfigExtension["config"]; 42 - 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 + 43 88 getNatives: (ext: string) => any | undefined; 44 89 getLogger: (id: string) => Logger; 90 + 91 + lunast: LunAST; 92 + moonmap: Moonmap; 45 93 }; 46 94 47 95 export enum MoonlightEnv { ··· 49 97 NodePreload = "node-preload", 50 98 WebPreload = "web-preload" 51 99 } 100 + 101 + export enum MoonlightBranch { 102 + STABLE = "stable", 103 + NIGHTLY = "nightly", 104 + DEV = "dev" 105 + }
+59 -21
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 components: CoreExtensions.CommonComponents; 10 - export default components; 11 - export = components; 9 + export const commands: CoreExtensions.Commands.Commands; 10 + export default commands; 12 11 } 13 12 14 - declare module "@moonlight-mod/wp/common_flux" { 13 + declare module "@moonlight-mod/wp/common_ErrorBoundary" { 15 14 import { CoreExtensions } from "@moonlight-mod/types"; 16 - const Flux: CoreExtensions.CommonFlux; 17 - export default 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 } 23 + declare module "@moonlight-mod/wp/common_stores"; 25 24 26 - declare module "@moonlight-mod/wp/common_react" { 27 - import React from "react"; 28 - 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; 29 39 } 30 40 31 - declare module "@moonlight-mod/wp/settings_settings" { 41 + declare module "@moonlight-mod/wp/contextMenu_evilMenu" { 32 42 import { CoreExtensions } from "@moonlight-mod/types"; 33 - export const Settings: CoreExtensions.Settings; 34 - export default Settings; 35 - export = 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; 36 50 } 37 51 38 52 declare module "@moonlight-mod/wp/markdown_markdown" { ··· 40 54 const Markdown: CoreExtensions.Markdown.Markdown; 41 55 export = Markdown; 42 56 } 57 + 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" { 65 + import { CoreExtensions } from "@moonlight-mod/types"; 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; 74 + } 75 + 76 + declare module "@moonlight-mod/wp/spacepack_spacepack" { 77 + import { CoreExtensions } from "@moonlight-mod/types"; 78 + export const spacepack: CoreExtensions.Spacepack.Spacepack; 79 + export default spacepack; 80 + }
+14 -8
packages/types/src/index.ts
··· 1 - /// <reference types="node" /> 2 1 /// <reference types="standalone-electron-types" /> 3 2 /// <reference types="react" /> 4 - /// <reference types="flux" /> 5 3 /// <reference types="./import" /> 4 + /// <reference types="./mappings" /> 6 5 /* eslint-disable no-var */ 7 6 8 - import { 9 - MoonlightEnv, 10 - MoonlightHost, 11 - MoonlightNode, 12 - MoonlightWeb 13 - } from "./globals"; 7 + import { MoonlightEnv, MoonlightHost, MoonlightNode, MoonlightNodeSandboxed, MoonlightWeb } from "./globals"; 14 8 15 9 export * from "./discord"; 16 10 export * from "./config"; ··· 19 13 export * from "./globals"; 20 14 export * from "./logger"; 21 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"; 22 20 23 21 declare global { 24 22 const MOONLIGHT_ENV: MoonlightEnv; ··· 26 24 const MOONLIGHT_INJECTOR: boolean; 27 25 const MOONLIGHT_NODE_PRELOAD: boolean; 28 26 const MOONLIGHT_WEB_PRELOAD: boolean; 27 + const MOONLIGHT_BROWSER: boolean; 28 + const MOONLIGHT_BRANCH: string; 29 + const MOONLIGHT_VERSION: string; 29 30 30 31 var moonlightHost: MoonlightHost; 31 32 var moonlightNode: MoonlightNode; 33 + var moonlightNodeSandboxed: MoonlightNodeSandboxed; 32 34 var moonlight: MoonlightWeb; 35 + var _moonlight_coreExtensionsStr: string; 36 + 37 + var _moonlightBrowserInit: undefined | (() => Promise<void>); 38 + var _moonlightWebLoad: undefined | (() => Promise<void>); 33 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 -8
packages/types/tsconfig.json
··· 1 1 { 2 2 "compilerOptions": { 3 - "target": "es2016", 4 - "module": "es6", 3 + "target": "ES2016", 4 + "jsx": "react", 5 + "module": "ES6", 6 + "moduleResolution": "bundler", 7 + "strict": true, 8 + "declaration": true, 5 9 "esModuleInterop": true, 6 - "forceConsistentCasingInFileNames": true, 7 - "strict": true, 8 - "skipLibCheck": true, 9 - "moduleResolution": "bundler", 10 - "jsx": "react", 11 - "declaration": true 10 + "forceConsistentCasingInFileNames": true 12 11 }, 13 12 "include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"] 14 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 }
+41 -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(), 20 + pendingModules: new Set(), 11 21 enabledExtensions: new Set(), 12 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 + 13 35 getConfig: moonlightNode.getConfig.bind(moonlightNode), 14 36 getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode), 37 + setConfigOption: moonlightNode.setConfigOption.bind(moonlightNode), 38 + writeConfig: moonlightNode.writeConfig.bind(moonlightNode), 39 + 15 40 getNatives: moonlightNode.getNatives.bind(moonlightNode), 16 - getLogger: (id: string) => { 41 + getLogger(id) { 17 42 return new Logger(id); 18 - } 43 + }, 44 + 45 + lunast: new LunAST(), 46 + moonmap: new Moonmap() 19 47 }; 20 48 21 49 try { 50 + loadMappings(window.moonlight.moonmap, window.moonlight.lunast); 22 51 await loadProcessedExtensions(moonlightNode.processedExtensions); 23 52 await installWebpackPatcher(); 24 53 } catch (e) { 25 54 logger.error("Error setting up web-preload", e); 26 55 } 27 56 28 - window.addEventListener("DOMContentLoaded", () => { 57 + if (document.readyState === "complete") { 29 58 installStyles(); 30 - }); 31 - })(); 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 }
+2446 -1622
pnpm-lock.yaml
··· 1 - lockfileVersion: '6.0' 1 + lockfileVersion: '9.0' 2 2 3 3 settings: 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 83 - '@types/node': 84 - specifier: ^20.6.2 85 - version: 20.6.2 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 86 166 '@types/react': 87 - specifier: ^18.2.22 88 - version: 18.2.22 167 + specifier: ^18.3.10 168 + version: 18.3.20 89 169 csstype: 90 - specifier: ^3.1.2 91 - version: 3.1.2 170 + specifier: ^3.1.3 171 + version: 3.1.3 92 172 standalone-electron-types: 93 173 specifier: ^1.0.0 94 174 version: 1.0.0 ··· 98 178 '@moonlight-mod/core': 99 179 specifier: workspace:* 100 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 101 193 102 194 packages: 103 195 104 - /@aashutoshrathi/word-wrap@1.2.6: 196 + '@aashutoshrathi/word-wrap@1.2.6': 105 197 resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 106 198 engines: {node: '>=0.10.0'} 107 - dev: true 108 199 109 - /@electron/asar@3.2.5: 110 - resolution: {integrity: sha512-Ypahc2ElTj9YOrFvUHuoXv5Z/V1nPA5enlhmQapc578m/HZBHKTbqhoL5JZQjje2+/6Ti5AHh7Gj1/haeJa63Q==} 111 - engines: {node: '>=10.12.0'} 200 + '@antfu/ni@24.3.0': 201 + resolution: {integrity: sha512-wBSav4mBxvHEW9RbdSo1SWLQ6MAlT0Dc423weC58yOWqW4OcMvtnNDdDrxOZeJ88fEIyPK93gDUWIelBxzSf8g==} 112 202 hasBin: true 113 - dependencies: 114 - commander: 5.1.0 115 - glob: 7.2.3 116 - minimatch: 3.1.2 117 - dev: false 118 203 119 - /@esbuild/android-arm64@0.19.3: 204 + '@esbuild/android-arm64@0.19.3': 120 205 resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==} 121 206 engines: {node: '>=12'} 122 207 cpu: [arm64] 123 208 os: [android] 124 - requiresBuild: true 125 - dev: true 126 - optional: true 127 209 128 - /@esbuild/android-arm@0.19.3: 210 + '@esbuild/android-arm@0.19.3': 129 211 resolution: {integrity: sha512-Lemgw4io4VZl9GHJmjiBGzQ7ONXRfRPHcUEerndjwiSkbxzrpq0Uggku5MxxrXdwJ+pTj1qyw4jwTu7hkPsgIA==} 130 212 engines: {node: '>=12'} 131 213 cpu: [arm] 132 214 os: [android] 133 - requiresBuild: true 134 - dev: true 135 - optional: true 136 215 137 - /@esbuild/android-x64@0.19.3: 216 + '@esbuild/android-x64@0.19.3': 138 217 resolution: {integrity: sha512-FKQJKkK5MXcBHoNZMDNUAg1+WcZlV/cuXrWCoGF/TvdRiYS4znA0m5Il5idUwfxrE20bG/vU1Cr5e1AD6IEIjQ==} 139 218 engines: {node: '>=12'} 140 219 cpu: [x64] 141 220 os: [android] 142 - requiresBuild: true 143 - dev: true 144 - optional: true 145 221 146 - /@esbuild/darwin-arm64@0.19.3: 222 + '@esbuild/darwin-arm64@0.19.3': 147 223 resolution: {integrity: sha512-kw7e3FXU+VsJSSSl2nMKvACYlwtvZB8RUIeVShIEY6PVnuZ3c9+L9lWB2nWeeKWNNYDdtL19foCQ0ZyUL7nqGw==} 148 224 engines: {node: '>=12'} 149 225 cpu: [arm64] 150 226 os: [darwin] 151 - requiresBuild: true 152 - dev: true 153 - optional: true 154 227 155 - /@esbuild/darwin-x64@0.19.3: 228 + '@esbuild/darwin-x64@0.19.3': 156 229 resolution: {integrity: sha512-tPfZiwF9rO0jW6Jh9ipi58N5ZLoSjdxXeSrAYypy4psA2Yl1dAMhM71KxVfmjZhJmxRjSnb29YlRXXhh3GqzYw==} 157 230 engines: {node: '>=12'} 158 231 cpu: [x64] 159 232 os: [darwin] 160 - requiresBuild: true 161 - dev: true 162 - optional: true 163 233 164 - /@esbuild/freebsd-arm64@0.19.3: 234 + '@esbuild/freebsd-arm64@0.19.3': 165 235 resolution: {integrity: sha512-ERDyjOgYeKe0Vrlr1iLrqTByB026YLPzTytDTz1DRCYM+JI92Dw2dbpRHYmdqn6VBnQ9Bor6J8ZlNwdZdxjlSg==} 166 236 engines: {node: '>=12'} 167 237 cpu: [arm64] 168 238 os: [freebsd] 169 - requiresBuild: true 170 - dev: true 171 - optional: true 172 239 173 - /@esbuild/freebsd-x64@0.19.3: 240 + '@esbuild/freebsd-x64@0.19.3': 174 241 resolution: {integrity: sha512-nXesBZ2Ad1qL+Rm3crN7NmEVJ5uvfLFPLJev3x1j3feCQXfAhoYrojC681RhpdOph8NsvKBBwpYZHR7W0ifTTA==} 175 242 engines: {node: '>=12'} 176 243 cpu: [x64] 177 244 os: [freebsd] 178 - requiresBuild: true 179 - dev: true 180 - optional: true 181 245 182 - /@esbuild/linux-arm64@0.19.3: 246 + '@esbuild/linux-arm64@0.19.3': 183 247 resolution: {integrity: sha512-qXvYKmXj8GcJgWq3aGvxL/JG1ZM3UR272SdPU4QSTzD0eymrM7leiZH77pvY3UetCy0k1xuXZ+VPvoJNdtrsWQ==} 184 248 engines: {node: '>=12'} 185 249 cpu: [arm64] 186 250 os: [linux] 187 - requiresBuild: true 188 - dev: true 189 - optional: true 190 251 191 - /@esbuild/linux-arm@0.19.3: 252 + '@esbuild/linux-arm@0.19.3': 192 253 resolution: {integrity: sha512-zr48Cg/8zkzZCzDHNxXO/89bf9e+r4HtzNUPoz4GmgAkF1gFAFmfgOdCbR8zMbzFDGb1FqBBhdXUpcTQRYS1cQ==} 193 254 engines: {node: '>=12'} 194 255 cpu: [arm] 195 256 os: [linux] 196 - requiresBuild: true 197 - dev: true 198 - optional: true 199 257 200 - /@esbuild/linux-ia32@0.19.3: 258 + '@esbuild/linux-ia32@0.19.3': 201 259 resolution: {integrity: sha512-7XlCKCA0nWcbvYpusARWkFjRQNWNGlt45S+Q18UeS///K6Aw8bB2FKYe9mhVWy/XLShvCweOLZPrnMswIaDXQA==} 202 260 engines: {node: '>=12'} 203 261 cpu: [ia32] 204 262 os: [linux] 205 - requiresBuild: true 206 - dev: true 207 - optional: true 208 263 209 - /@esbuild/linux-loong64@0.19.3: 264 + '@esbuild/linux-loong64@0.19.3': 210 265 resolution: {integrity: sha512-qGTgjweER5xqweiWtUIDl9OKz338EQqCwbS9c2Bh5jgEH19xQ1yhgGPNesugmDFq+UUSDtWgZ264st26b3de8A==} 211 266 engines: {node: '>=12'} 212 267 cpu: [loong64] 213 268 os: [linux] 214 - requiresBuild: true 215 - dev: true 216 - optional: true 217 269 218 - /@esbuild/linux-mips64el@0.19.3: 270 + '@esbuild/linux-mips64el@0.19.3': 219 271 resolution: {integrity: sha512-gy1bFskwEyxVMFRNYSvBauDIWNggD6pyxUksc0MV9UOBD138dKTzr8XnM2R4mBsHwVzeuIH8X5JhmNs2Pzrx+A==} 220 272 engines: {node: '>=12'} 221 273 cpu: [mips64el] 222 274 os: [linux] 223 - requiresBuild: true 224 - dev: true 225 - optional: true 226 275 227 - /@esbuild/linux-ppc64@0.19.3: 276 + '@esbuild/linux-ppc64@0.19.3': 228 277 resolution: {integrity: sha512-UrYLFu62x1MmmIe85rpR3qou92wB9lEXluwMB/STDzPF9k8mi/9UvNsG07Tt9AqwPQXluMQ6bZbTzYt01+Ue5g==} 229 278 engines: {node: '>=12'} 230 279 cpu: [ppc64] 231 280 os: [linux] 232 - requiresBuild: true 233 - dev: true 234 - optional: true 235 281 236 - /@esbuild/linux-riscv64@0.19.3: 282 + '@esbuild/linux-riscv64@0.19.3': 237 283 resolution: {integrity: sha512-9E73TfyMCbE+1AwFOg3glnzZ5fBAFK4aawssvuMgCRqCYzE0ylVxxzjEfut8xjmKkR320BEoMui4o/t9KA96gA==} 238 284 engines: {node: '>=12'} 239 285 cpu: [riscv64] 240 286 os: [linux] 241 - requiresBuild: true 242 - dev: true 243 - optional: true 244 287 245 - /@esbuild/linux-s390x@0.19.3: 288 + '@esbuild/linux-s390x@0.19.3': 246 289 resolution: {integrity: sha512-LlmsbuBdm1/D66TJ3HW6URY8wO6IlYHf+ChOUz8SUAjVTuaisfuwCOAgcxo3Zsu3BZGxmI7yt//yGOxV+lHcEA==} 247 290 engines: {node: '>=12'} 248 291 cpu: [s390x] 249 292 os: [linux] 250 - requiresBuild: true 251 - dev: true 252 - optional: true 253 293 254 - /@esbuild/linux-x64@0.19.3: 294 + '@esbuild/linux-x64@0.19.3': 255 295 resolution: {integrity: sha512-ogV0+GwEmvwg/8ZbsyfkYGaLACBQWDvO0Kkh8LKBGKj9Ru8VM39zssrnu9Sxn1wbapA2qNS6BiLdwJZGouyCwQ==} 256 296 engines: {node: '>=12'} 257 297 cpu: [x64] 258 298 os: [linux] 259 - requiresBuild: true 260 - dev: true 261 - optional: true 262 299 263 - /@esbuild/netbsd-x64@0.19.3: 300 + '@esbuild/netbsd-x64@0.19.3': 264 301 resolution: {integrity: sha512-o1jLNe4uzQv2DKXMlmEzf66Wd8MoIhLNO2nlQBHLtWyh2MitDG7sMpfCO3NTcoTMuqHjfufgUQDFRI5C+xsXQw==} 265 302 engines: {node: '>=12'} 266 303 cpu: [x64] 267 304 os: [netbsd] 268 - requiresBuild: true 269 - dev: true 270 - optional: true 271 305 272 - /@esbuild/openbsd-x64@0.19.3: 306 + '@esbuild/openbsd-x64@0.19.3': 273 307 resolution: {integrity: sha512-AZJCnr5CZgZOdhouLcfRdnk9Zv6HbaBxjcyhq0StNcvAdVZJSKIdOiPB9az2zc06ywl0ePYJz60CjdKsQacp5Q==} 274 308 engines: {node: '>=12'} 275 309 cpu: [x64] 276 310 os: [openbsd] 277 - requiresBuild: true 278 - dev: true 279 - optional: true 280 311 281 - /@esbuild/sunos-x64@0.19.3: 312 + '@esbuild/sunos-x64@0.19.3': 282 313 resolution: {integrity: sha512-Acsujgeqg9InR4glTRvLKGZ+1HMtDm94ehTIHKhJjFpgVzZG9/pIcWW/HA/DoMfEyXmANLDuDZ2sNrWcjq1lxw==} 283 314 engines: {node: '>=12'} 284 315 cpu: [x64] 285 316 os: [sunos] 286 - requiresBuild: true 287 - dev: true 288 - optional: true 289 317 290 - /@esbuild/win32-arm64@0.19.3: 318 + '@esbuild/win32-arm64@0.19.3': 291 319 resolution: {integrity: sha512-FSrAfjVVy7TifFgYgliiJOyYynhQmqgPj15pzLyJk8BUsnlWNwP/IAy6GAiB1LqtoivowRgidZsfpoYLZH586A==} 292 320 engines: {node: '>=12'} 293 321 cpu: [arm64] 294 322 os: [win32] 295 - requiresBuild: true 296 - dev: true 297 - optional: true 298 323 299 - /@esbuild/win32-ia32@0.19.3: 324 + '@esbuild/win32-ia32@0.19.3': 300 325 resolution: {integrity: sha512-xTScXYi12xLOWZ/sc5RBmMN99BcXp/eEf7scUC0oeiRoiT5Vvo9AycuqCp+xdpDyAU+LkrCqEpUS9fCSZF8J3Q==} 301 326 engines: {node: '>=12'} 302 327 cpu: [ia32] 303 328 os: [win32] 304 - requiresBuild: true 305 - dev: true 306 - optional: true 307 329 308 - /@esbuild/win32-x64@0.19.3: 330 + '@esbuild/win32-x64@0.19.3': 309 331 resolution: {integrity: sha512-FbUN+0ZRXsypPyWE2IwIkVjDkDnJoMJARWOcFZn4KPPli+QnKqF0z1anvfaYe3ev5HFCpRDLLBDHyOALLppWHw==} 310 332 engines: {node: '>=12'} 311 333 cpu: [x64] 312 334 os: [win32] 313 - requiresBuild: true 314 - dev: true 315 - optional: true 316 335 317 - /@eslint-community/eslint-utils@4.4.0(eslint@8.55.0): 318 - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 336 + '@eslint-community/eslint-utils@4.5.1': 337 + resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} 319 338 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 320 339 peerDependencies: 321 340 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 322 - dependencies: 323 - eslint: 8.55.0 324 - eslint-visitor-keys: 3.4.3 325 - dev: true 326 341 327 - /@eslint-community/regexpp@4.10.0: 328 - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} 342 + '@eslint-community/regexpp@4.12.1': 343 + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} 329 344 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 330 - dev: true 345 + 346 + '@eslint/config-array@0.19.2': 347 + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} 348 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 349 + 350 + '@eslint/config-helpers@0.2.1': 351 + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} 352 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 353 + 354 + '@eslint/core@0.12.0': 355 + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} 356 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 357 + 358 + '@eslint/core@0.13.0': 359 + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} 360 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 361 + 362 + '@eslint/eslintrc@3.3.1': 363 + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} 364 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 365 + 366 + '@eslint/js@9.23.0': 367 + resolution: {integrity: sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==} 368 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 369 + 370 + '@eslint/object-schema@2.1.6': 371 + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} 372 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 373 + 374 + '@eslint/plugin-kit@0.2.8': 375 + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} 376 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 377 + 378 + '@humanfs/core@0.19.1': 379 + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 380 + engines: {node: '>=18.18.0'} 381 + 382 + '@humanfs/node@0.16.6': 383 + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} 384 + engines: {node: '>=18.18.0'} 385 + 386 + '@humanwhocodes/module-importer@1.0.1': 387 + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 388 + engines: {node: '>=12.22'} 389 + 390 + '@humanwhocodes/retry@0.3.1': 391 + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} 392 + engines: {node: '>=18.18'} 393 + 394 + '@humanwhocodes/retry@0.4.2': 395 + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} 396 + engines: {node: '>=18.18'} 397 + 398 + '@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9': 399 + resolution: {tarball: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9} 400 + version: 1.0.1 401 + peerDependencies: 402 + eslint: '>= 9' 403 + typescript: '>= 5.3' 404 + 405 + '@moonlight-mod/lunast@1.0.1': 406 + resolution: {integrity: sha512-K3vxzDlfFuYKjciIW2FMlcZ1qrrkAGDGpSBlNqYGtJ0sMt9bRCd2lpSpg6AX/giSljDtmAUXa/5mOfUoDQxjBA==} 407 + 408 + '@moonlight-mod/mappings@1.1.25': 409 + resolution: {integrity: sha512-bgnSN9H/IBdMGxGev6RQKXuzhQxwo1090NhIDHnflguZnjiu2pg/usPfh76bqyhxRuX4SS7tiZSNTwBoSflCLg==} 410 + engines: {node: '>=22', npm: pnpm, pnpm: '>=10', yarn: pnpm} 411 + peerDependencies: 412 + '@moonlight-mod/lunast': ^1.0.1 413 + '@moonlight-mod/moonmap': ^1.0.5 414 + 415 + '@moonlight-mod/moonmap@1.0.5': 416 + resolution: {integrity: sha512-Fdpxj8ghdulKB6TlTnchlCPey2YUKgEf1chuO1ofOIcvlqnVPBcQwSf2S80naOUQpXCDo4dQ+LWSE2fmhdDiiw==} 417 + 418 + '@nodelib/fs.scandir@2.1.5': 419 + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 420 + engines: {node: '>= 8'} 421 + 422 + '@nodelib/fs.stat@2.0.5': 423 + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 424 + engines: {node: '>= 8'} 425 + 426 + '@nodelib/fs.walk@1.2.8': 427 + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 428 + engines: {node: '>= 8'} 429 + 430 + '@pkgr/core@0.2.0': 431 + resolution: {integrity: sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==} 432 + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 433 + 434 + '@quansync/fs@0.1.2': 435 + resolution: {integrity: sha512-ezIadUb1aFhwJLd++WVqVpi9rnlX8vnd4ju7saPhwLHJN1mJgOv0puePTGV+FbtSnWtwoHDT8lAm4kagDZmpCg==} 436 + engines: {node: '>=20.0.0'} 437 + 438 + '@types/chroma-js@3.1.0': 439 + resolution: {integrity: sha512-Uwl3SOtUkbQ6Ye6ZYu4q4xdLGBzmY839sEHYtOT7i691neeyd+7fXWT5VIkcUSfNwIFrIjQutNYQn9h4q5HFvg==} 440 + 441 + '@types/chrome@0.0.313': 442 + resolution: {integrity: sha512-9R5T7gTaYZhkxlu+Ho4wk9FL+y/werWQY2yjGWSqCuiTsqS7nL/BE5UMTP6rU7J+oIG2FRKqrEycHhJATeltVA==} 443 + 444 + '@types/eslint@9.6.1': 445 + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} 446 + 447 + '@types/estree-jsx@1.0.5': 448 + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} 449 + 450 + '@types/estree@1.0.6': 451 + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 452 + 453 + '@types/estree@1.0.7': 454 + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} 455 + 456 + '@types/fbemitter@2.0.35': 457 + resolution: {integrity: sha512-Xem6d7qUfmouCHntCrRYgDBwbf+WWRd6G+7WEFlEZFZ67LZXiYRvT2LV8wcZa6mIaAil95+ABQdKgB6hPIsnng==} 458 + 459 + '@types/filesystem@0.0.36': 460 + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} 461 + 462 + '@types/filewriter@0.0.33': 463 + resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} 464 + 465 + '@types/flux@3.1.14': 466 + resolution: {integrity: sha512-WRXN0kQPCnqxN0/PgNgc7WBF6c8rbSHsEep3/qBLpsQ824RONdOmTs0TV7XhIW2GDNRAHO2CqCgAFLR5PChosw==} 467 + 468 + '@types/har-format@1.2.16': 469 + resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} 470 + 471 + '@types/highlightjs@9.12.6': 472 + resolution: {integrity: sha512-Qfd1DUrwE851Hc3tExADJY4qY8yeZMt06Xw9AJm/UtpneepJS3MZY29c33BY0wP899veaaHD4gZzYiSuQm84Fg==} 473 + 474 + '@types/json-schema@7.0.15': 475 + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 476 + 477 + '@types/lodash@4.17.14': 478 + resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} 479 + 480 + '@types/node@18.17.17': 481 + resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} 482 + 483 + '@types/node@22.13.6': 484 + resolution: {integrity: sha512-GYmF65GI7417CpZXsEXMjT8goQQDnpRnJnDw6jIYa+le3V/lMazPZ4vZmK1B/9R17fh2VLr2zuy9d/h5xgrLAg==} 485 + 486 + '@types/node@22.14.0': 487 + resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} 488 + 489 + '@types/platform@1.3.6': 490 + resolution: {integrity: sha512-ZmSaqHuvzv+jC232cFoz2QqPUkaj6EvMmCrWcx3WRr7xTPVFCMUOTcOq8m2d+Zw1iKRc1kDiaA+jtNrV0hkVew==} 491 + 492 + '@types/prop-types@15.7.13': 493 + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} 494 + 495 + '@types/react@18.3.20': 496 + resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==} 497 + 498 + '@typescript-eslint/eslint-plugin@8.29.0': 499 + resolution: {integrity: sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==} 500 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 501 + peerDependencies: 502 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 503 + eslint: ^8.57.0 || ^9.0.0 504 + typescript: '>=4.8.4 <5.9.0' 505 + 506 + '@typescript-eslint/parser@8.29.0': 507 + resolution: {integrity: sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==} 508 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 509 + peerDependencies: 510 + eslint: ^8.57.0 || ^9.0.0 511 + typescript: '>=4.8.4 <5.9.0' 512 + 513 + '@typescript-eslint/scope-manager@8.29.0': 514 + resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==} 515 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 516 + 517 + '@typescript-eslint/type-utils@8.29.0': 518 + resolution: {integrity: sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==} 519 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 520 + peerDependencies: 521 + eslint: ^8.57.0 || ^9.0.0 522 + typescript: '>=4.8.4 <5.9.0' 523 + 524 + '@typescript-eslint/types@8.29.0': 525 + resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==} 526 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 527 + 528 + '@typescript-eslint/typescript-estree@8.29.0': 529 + resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==} 530 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 531 + peerDependencies: 532 + typescript: '>=4.8.4 <5.9.0' 533 + 534 + '@typescript-eslint/utils@8.29.0': 535 + resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==} 536 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 537 + peerDependencies: 538 + eslint: ^8.57.0 || ^9.0.0 539 + typescript: '>=4.8.4 <5.9.0' 540 + 541 + '@typescript-eslint/visitor-keys@8.29.0': 542 + resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==} 543 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 544 + 545 + '@xterm/xterm@5.5.0': 546 + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} 547 + 548 + '@zenfs/core@2.0.0': 549 + resolution: {integrity: sha512-wOKNFTY1DJ1vdLqKdU7M8cRh0nVYZcDVu7WHuk/3u49hrSwTZVm4PzGxJUjFd8O9Wi3U5nYTbZoN7RX5mS2ldA==} 550 + engines: {node: '>= 18'} 551 + hasBin: true 552 + 553 + '@zenfs/dom@1.1.6': 554 + resolution: {integrity: sha512-7SBTWgA0esuEv/TE+N/xk6W/XJf8uBF+LhlPNHQdXds0H7aOy/UYsWv/8glvARe+meDMMidoeWFLzUWoMXfjlA==} 555 + engines: {node: '>= 18'} 556 + peerDependencies: 557 + '@zenfs/core': ^2.0.0 558 + utilium: ^1.9.0 559 + 560 + abort-controller@3.0.0: 561 + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} 562 + engines: {node: '>=6.5'} 563 + 564 + acorn-jsx@5.3.2: 565 + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 566 + peerDependencies: 567 + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 568 + 569 + acorn@8.14.1: 570 + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 571 + engines: {node: '>=0.4.0'} 572 + hasBin: true 573 + 574 + ajv@6.12.6: 575 + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 576 + 577 + ansi-styles@4.3.0: 578 + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 579 + engines: {node: '>=8'} 580 + 581 + ansis@3.17.0: 582 + resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} 583 + engines: {node: '>=14'} 584 + 585 + argparse@2.0.1: 586 + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 587 + 588 + array-buffer-byte-length@1.0.2: 589 + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} 590 + engines: {node: '>= 0.4'} 591 + 592 + array-includes@3.1.8: 593 + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} 594 + engines: {node: '>= 0.4'} 595 + 596 + array.prototype.findlast@1.2.5: 597 + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} 598 + engines: {node: '>= 0.4'} 599 + 600 + array.prototype.flat@1.3.3: 601 + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} 602 + engines: {node: '>= 0.4'} 603 + 604 + array.prototype.flatmap@1.3.3: 605 + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} 606 + engines: {node: '>= 0.4'} 607 + 608 + array.prototype.tosorted@1.1.4: 609 + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} 610 + engines: {node: '>= 0.4'} 611 + 612 + arraybuffer.prototype.slice@1.0.4: 613 + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} 614 + engines: {node: '>= 0.4'} 615 + 616 + astring@1.9.0: 617 + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} 618 + hasBin: true 619 + 620 + async-function@1.0.0: 621 + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} 622 + engines: {node: '>= 0.4'} 623 + 624 + available-typed-arrays@1.0.7: 625 + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} 626 + engines: {node: '>= 0.4'} 627 + 628 + balanced-match@1.0.2: 629 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 630 + 631 + base64-js@1.5.1: 632 + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 633 + 634 + brace-expansion@1.1.11: 635 + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 636 + 637 + brace-expansion@2.0.1: 638 + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 639 + 640 + braces@3.0.3: 641 + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 642 + engines: {node: '>=8'} 643 + 644 + buffer@6.0.3: 645 + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 646 + 647 + cac@6.7.14: 648 + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 649 + engines: {node: '>=8'} 650 + 651 + call-bind-apply-helpers@1.0.2: 652 + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 653 + engines: {node: '>= 0.4'} 654 + 655 + call-bind@1.0.8: 656 + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} 657 + engines: {node: '>= 0.4'} 658 + 659 + call-bound@1.0.4: 660 + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 661 + engines: {node: '>= 0.4'} 662 + 663 + callsites@3.1.0: 664 + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 665 + engines: {node: '>=6'} 666 + 667 + chalk@4.1.2: 668 + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 669 + engines: {node: '>=10'} 670 + 671 + color-convert@2.0.1: 672 + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 673 + engines: {node: '>=7.0.0'} 674 + 675 + color-name@1.1.4: 676 + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 331 677 332 - /@eslint/eslintrc@2.1.4: 333 - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} 678 + concat-map@0.0.1: 679 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 680 + 681 + cross-spawn@7.0.6: 682 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 683 + engines: {node: '>= 8'} 684 + 685 + csstype@3.1.3: 686 + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 687 + 688 + data-view-buffer@1.0.2: 689 + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} 690 + engines: {node: '>= 0.4'} 691 + 692 + data-view-byte-length@1.0.2: 693 + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} 694 + engines: {node: '>= 0.4'} 695 + 696 + data-view-byte-offset@1.0.1: 697 + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} 698 + engines: {node: '>= 0.4'} 699 + 700 + debug@4.4.0: 701 + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 702 + engines: {node: '>=6.0'} 703 + peerDependencies: 704 + supports-color: '*' 705 + peerDependenciesMeta: 706 + supports-color: 707 + optional: true 708 + 709 + deep-is@0.1.4: 710 + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 711 + 712 + define-data-property@1.1.4: 713 + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 714 + engines: {node: '>= 0.4'} 715 + 716 + define-properties@1.2.1: 717 + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} 718 + engines: {node: '>= 0.4'} 719 + 720 + defu@6.1.4: 721 + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 722 + 723 + destr@2.0.4: 724 + resolution: {integrity: sha512-FCAorltMy7QwX0QU38jOkhrv20LBpsHA8ogzvMhhPHCCKVCaN6GxrB0GGaWEWBUYI4eEjjfJ95RdP6dk9IdMQA==} 725 + 726 + doctrine@2.1.0: 727 + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} 728 + engines: {node: '>=0.10.0'} 729 + 730 + dunder-proto@1.0.1: 731 + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 732 + engines: {node: '>= 0.4'} 733 + 734 + es-abstract@1.23.9: 735 + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} 736 + engines: {node: '>= 0.4'} 737 + 738 + es-define-property@1.0.1: 739 + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 740 + engines: {node: '>= 0.4'} 741 + 742 + es-errors@1.3.0: 743 + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 744 + engines: {node: '>= 0.4'} 745 + 746 + es-iterator-helpers@1.2.1: 747 + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} 748 + engines: {node: '>= 0.4'} 749 + 750 + es-object-atoms@1.1.1: 751 + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 752 + engines: {node: '>= 0.4'} 753 + 754 + es-set-tostringtag@2.1.0: 755 + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} 756 + engines: {node: '>= 0.4'} 757 + 758 + es-shim-unscopables@1.1.0: 759 + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} 760 + engines: {node: '>= 0.4'} 761 + 762 + es-to-primitive@1.3.0: 763 + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} 764 + engines: {node: '>= 0.4'} 765 + 766 + esbuild-copy-static-files@0.1.0: 767 + resolution: {integrity: sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w==} 768 + 769 + esbuild@0.19.3: 770 + resolution: {integrity: sha512-UlJ1qUUA2jL2nNib1JTSkifQTcYTroFqRjwCFW4QYEKEsixXD5Tik9xML7zh2gTxkYTBKGHNH9y7txMwVyPbjw==} 771 + engines: {node: '>=12'} 772 + hasBin: true 773 + 774 + escape-string-regexp@4.0.0: 775 + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 776 + engines: {node: '>=10'} 777 + 778 + eslint-config-prettier@9.1.0: 779 + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} 780 + hasBin: true 781 + peerDependencies: 782 + eslint: '>=7.0.0' 783 + 784 + eslint-plugin-prettier@5.2.6: 785 + resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==} 786 + engines: {node: ^14.18.0 || >=16.0.0} 787 + peerDependencies: 788 + '@types/eslint': '>=8.0.0' 789 + eslint: '>=8.0.0' 790 + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' 791 + prettier: '>=3.0.0' 792 + peerDependenciesMeta: 793 + '@types/eslint': 794 + optional: true 795 + eslint-config-prettier: 796 + optional: true 797 + 798 + eslint-plugin-react@7.37.5: 799 + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} 800 + engines: {node: '>=4'} 801 + peerDependencies: 802 + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 803 + 804 + eslint-scope@8.3.0: 805 + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} 806 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 807 + 808 + eslint-visitor-keys@3.4.3: 809 + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 334 810 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 811 + 812 + eslint-visitor-keys@4.2.0: 813 + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} 814 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 815 + 816 + eslint@9.23.0: 817 + resolution: {integrity: sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==} 818 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 819 + hasBin: true 820 + peerDependencies: 821 + jiti: '*' 822 + peerDependenciesMeta: 823 + jiti: 824 + optional: true 825 + 826 + espree@10.3.0: 827 + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} 828 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 829 + 830 + esquery@1.6.0: 831 + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 832 + engines: {node: '>=0.10'} 833 + 834 + esrecurse@4.3.0: 835 + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 836 + engines: {node: '>=4.0'} 837 + 838 + estraverse@5.3.0: 839 + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 840 + engines: {node: '>=4.0'} 841 + 842 + estree-toolkit@1.7.8: 843 + resolution: {integrity: sha512-v0Q0L+0agSDFe3x9Sj7aAzrI9afvsfr5r7AM2SNk/8bKYRQ3tUf4PQEUWe99LkWysmT1PsuSpW+W1w/xZmCKeg==} 844 + 845 + esutils@2.0.3: 846 + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 847 + engines: {node: '>=0.10.0'} 848 + 849 + event-target-shim@5.0.1: 850 + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} 851 + engines: {node: '>=6'} 852 + 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'} 859 + 860 + fast-deep-equal@3.1.3: 861 + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 862 + 863 + fast-diff@1.3.0: 864 + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} 865 + 866 + fast-glob@3.3.2: 867 + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} 868 + engines: {node: '>=8.6.0'} 869 + 870 + fast-json-stable-stringify@2.1.0: 871 + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 872 + 873 + fast-levenshtein@2.0.6: 874 + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 875 + 876 + fastq@1.17.1: 877 + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} 878 + 879 + fdir@6.4.3: 880 + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} 881 + peerDependencies: 882 + picomatch: ^3 || ^4 883 + peerDependenciesMeta: 884 + picomatch: 885 + optional: true 886 + 887 + file-entry-cache@8.0.0: 888 + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 889 + engines: {node: '>=16.0.0'} 890 + 891 + fill-range@7.1.1: 892 + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 893 + engines: {node: '>=8'} 894 + 895 + find-up-simple@1.0.1: 896 + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} 897 + engines: {node: '>=18'} 898 + 899 + find-up@5.0.0: 900 + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 901 + engines: {node: '>=10'} 902 + 903 + flat-cache@4.0.1: 904 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 905 + engines: {node: '>=16'} 906 + 907 + flatted@3.2.9: 908 + resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} 909 + 910 + for-each@0.3.5: 911 + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} 912 + engines: {node: '>= 0.4'} 913 + 914 + function-bind@1.1.2: 915 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 916 + 917 + function.prototype.name@1.1.8: 918 + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} 919 + engines: {node: '>= 0.4'} 920 + 921 + functions-have-names@1.2.3: 922 + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} 923 + 924 + fzf@0.5.2: 925 + resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==} 926 + 927 + get-intrinsic@1.3.0: 928 + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 929 + engines: {node: '>= 0.4'} 930 + 931 + get-proto@1.0.1: 932 + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 933 + engines: {node: '>= 0.4'} 934 + 935 + get-symbol-description@1.1.0: 936 + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} 937 + engines: {node: '>= 0.4'} 938 + 939 + glob-parent@5.1.2: 940 + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 941 + engines: {node: '>= 6'} 942 + 943 + glob-parent@6.0.2: 944 + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 945 + engines: {node: '>=10.13.0'} 946 + 947 + globals@14.0.0: 948 + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 949 + engines: {node: '>=18'} 950 + 951 + globalthis@1.0.4: 952 + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} 953 + engines: {node: '>= 0.4'} 954 + 955 + gopd@1.2.0: 956 + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 957 + engines: {node: '>= 0.4'} 958 + 959 + graphemer@1.4.0: 960 + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 961 + 962 + has-bigints@1.1.0: 963 + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} 964 + engines: {node: '>= 0.4'} 965 + 966 + has-flag@4.0.0: 967 + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 968 + engines: {node: '>=8'} 969 + 970 + has-property-descriptors@1.0.2: 971 + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} 972 + 973 + has-proto@1.2.0: 974 + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} 975 + engines: {node: '>= 0.4'} 976 + 977 + has-symbols@1.1.0: 978 + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 979 + engines: {node: '>= 0.4'} 980 + 981 + has-tostringtag@1.0.2: 982 + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 983 + engines: {node: '>= 0.4'} 984 + 985 + hasown@2.0.2: 986 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 987 + engines: {node: '>= 0.4'} 988 + 989 + husky@8.0.3: 990 + resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} 991 + engines: {node: '>=14'} 992 + hasBin: true 993 + 994 + ieee754@1.2.1: 995 + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 996 + 997 + ignore@5.3.2: 998 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 999 + engines: {node: '>= 4'} 1000 + 1001 + import-fresh@3.3.0: 1002 + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 1003 + engines: {node: '>=6'} 1004 + 1005 + imurmurhash@0.1.4: 1006 + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 1007 + engines: {node: '>=0.8.19'} 1008 + 1009 + internal-slot@1.1.0: 1010 + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} 1011 + engines: {node: '>= 0.4'} 1012 + 1013 + is-array-buffer@3.0.5: 1014 + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} 1015 + engines: {node: '>= 0.4'} 1016 + 1017 + is-async-function@2.1.1: 1018 + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} 1019 + engines: {node: '>= 0.4'} 1020 + 1021 + is-bigint@1.1.0: 1022 + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} 1023 + engines: {node: '>= 0.4'} 1024 + 1025 + is-boolean-object@1.2.2: 1026 + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} 1027 + engines: {node: '>= 0.4'} 1028 + 1029 + is-callable@1.2.7: 1030 + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 1031 + engines: {node: '>= 0.4'} 1032 + 1033 + is-core-module@2.16.1: 1034 + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} 1035 + engines: {node: '>= 0.4'} 1036 + 1037 + is-data-view@1.0.2: 1038 + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} 1039 + engines: {node: '>= 0.4'} 1040 + 1041 + is-date-object@1.1.0: 1042 + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} 1043 + engines: {node: '>= 0.4'} 1044 + 1045 + is-extglob@2.1.1: 1046 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1047 + engines: {node: '>=0.10.0'} 1048 + 1049 + is-finalizationregistry@1.1.1: 1050 + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} 1051 + engines: {node: '>= 0.4'} 1052 + 1053 + is-generator-function@1.1.0: 1054 + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} 1055 + engines: {node: '>= 0.4'} 1056 + 1057 + is-glob@4.0.3: 1058 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1059 + engines: {node: '>=0.10.0'} 1060 + 1061 + is-map@2.0.3: 1062 + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} 1063 + engines: {node: '>= 0.4'} 1064 + 1065 + is-number-object@1.1.1: 1066 + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} 1067 + engines: {node: '>= 0.4'} 1068 + 1069 + is-number@7.0.0: 1070 + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 1071 + engines: {node: '>=0.12.0'} 1072 + 1073 + is-regex@1.2.1: 1074 + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} 1075 + engines: {node: '>= 0.4'} 1076 + 1077 + is-set@2.0.3: 1078 + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} 1079 + engines: {node: '>= 0.4'} 1080 + 1081 + is-shared-array-buffer@1.0.4: 1082 + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} 1083 + engines: {node: '>= 0.4'} 1084 + 1085 + is-string@1.1.1: 1086 + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} 1087 + engines: {node: '>= 0.4'} 1088 + 1089 + is-symbol@1.1.1: 1090 + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} 1091 + engines: {node: '>= 0.4'} 1092 + 1093 + is-typed-array@1.1.15: 1094 + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} 1095 + engines: {node: '>= 0.4'} 1096 + 1097 + is-weakmap@2.0.2: 1098 + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} 1099 + engines: {node: '>= 0.4'} 1100 + 1101 + is-weakref@1.1.1: 1102 + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} 1103 + engines: {node: '>= 0.4'} 1104 + 1105 + is-weakset@2.0.4: 1106 + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} 1107 + engines: {node: '>= 0.4'} 1108 + 1109 + isarray@2.0.5: 1110 + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} 1111 + 1112 + isexe@2.0.0: 1113 + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1114 + 1115 + iterator.prototype@1.1.5: 1116 + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} 1117 + engines: {node: '>= 0.4'} 1118 + 1119 + jiti@2.4.2: 1120 + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} 1121 + hasBin: true 1122 + 1123 + js-tokens@4.0.0: 1124 + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 1125 + 1126 + js-yaml@4.1.0: 1127 + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 1128 + hasBin: true 1129 + 1130 + json-buffer@3.0.1: 1131 + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 1132 + 1133 + json-schema-traverse@0.4.1: 1134 + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1135 + 1136 + json-stable-stringify-without-jsonify@1.0.1: 1137 + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 1138 + 1139 + jsx-ast-utils@3.3.5: 1140 + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} 1141 + engines: {node: '>=4.0'} 1142 + 1143 + keyv@4.5.4: 1144 + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 1145 + 1146 + levn@0.4.1: 1147 + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 1148 + engines: {node: '>= 0.8.0'} 1149 + 1150 + locate-path@6.0.0: 1151 + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 1152 + engines: {node: '>=10'} 1153 + 1154 + lodash.merge@4.6.2: 1155 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1156 + 1157 + loose-envify@1.4.0: 1158 + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 1159 + hasBin: true 1160 + 1161 + math-intrinsics@1.1.0: 1162 + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 1163 + engines: {node: '>= 0.4'} 1164 + 1165 + merge2@1.4.1: 1166 + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 1167 + engines: {node: '>= 8'} 1168 + 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==} 1178 + engines: {node: '>=8.6'} 1179 + 1180 + mimic-function@5.0.1: 1181 + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} 1182 + engines: {node: '>=18'} 1183 + 1184 + minimatch@3.1.2: 1185 + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1186 + 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==} 1196 + 1197 + natural-compare@1.4.0: 1198 + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1199 + 1200 + node-fetch-native@1.6.6: 1201 + resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} 1202 + 1203 + object-assign@4.1.1: 1204 + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 1205 + engines: {node: '>=0.10.0'} 1206 + 1207 + object-inspect@1.13.4: 1208 + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 1209 + engines: {node: '>= 0.4'} 1210 + 1211 + object-keys@1.1.1: 1212 + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 1213 + engines: {node: '>= 0.4'} 1214 + 1215 + object.assign@4.1.7: 1216 + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} 1217 + engines: {node: '>= 0.4'} 1218 + 1219 + object.entries@1.1.9: 1220 + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} 1221 + engines: {node: '>= 0.4'} 1222 + 1223 + object.fromentries@2.0.8: 1224 + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} 1225 + engines: {node: '>= 0.4'} 1226 + 1227 + object.values@1.2.1: 1228 + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} 1229 + engines: {node: '>= 0.4'} 1230 + 1231 + ofetch@1.4.1: 1232 + resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} 1233 + 1234 + onetime@7.0.0: 1235 + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} 1236 + engines: {node: '>=18'} 1237 + 1238 + optionator@0.9.3: 1239 + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1240 + engines: {node: '>= 0.8.0'} 1241 + 1242 + own-keys@1.0.1: 1243 + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} 1244 + engines: {node: '>= 0.4'} 1245 + 1246 + p-limit@3.1.0: 1247 + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1248 + engines: {node: '>=10'} 1249 + 1250 + p-locate@5.0.0: 1251 + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1252 + engines: {node: '>=10'} 1253 + 1254 + package-manager-detector@1.1.0: 1255 + resolution: {integrity: sha512-Y8f9qUlBzW8qauJjd/eu6jlpJZsuPJm2ZAV0cDVd420o4EdpH5RPdoCv+60/TdJflGatr4sDfpAL6ArWZbM5tA==} 1256 + 1257 + parent-module@1.0.1: 1258 + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1259 + engines: {node: '>=6'} 1260 + 1261 + path-exists@4.0.0: 1262 + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1263 + engines: {node: '>=8'} 1264 + 1265 + path-key@3.1.1: 1266 + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1267 + engines: {node: '>=8'} 1268 + 1269 + path-parse@1.0.7: 1270 + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1271 + 1272 + pathe@2.0.3: 1273 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 1274 + 1275 + picomatch@2.3.1: 1276 + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1277 + engines: {node: '>=8.6'} 1278 + 1279 + picomatch@4.0.2: 1280 + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 1281 + engines: {node: '>=12'} 1282 + 1283 + pnpm-workspace-yaml@0.3.1: 1284 + resolution: {integrity: sha512-3nW5RLmREmZ8Pm8MbPsO2RM+99RRjYd25ynj3NV0cFsN7CcEl4sDFzgoFmSyduFwxFQ2Qbu3y2UdCh6HlyUOeA==} 1285 + 1286 + possible-typed-array-names@1.1.0: 1287 + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} 1288 + engines: {node: '>= 0.4'} 1289 + 1290 + prelude-ls@1.2.1: 1291 + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1292 + engines: {node: '>= 0.8.0'} 1293 + 1294 + prettier-linter-helpers@1.0.0: 1295 + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} 1296 + engines: {node: '>=6.0.0'} 1297 + 1298 + prettier@3.1.0: 1299 + resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==} 1300 + engines: {node: '>=14'} 1301 + hasBin: true 1302 + 1303 + process@0.11.10: 1304 + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} 1305 + engines: {node: '>= 0.6.0'} 1306 + 1307 + prop-types@15.8.1: 1308 + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} 1309 + 1310 + punycode@2.3.1: 1311 + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1312 + engines: {node: '>=6'} 1313 + 1314 + quansync@0.2.10: 1315 + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} 1316 + 1317 + queue-microtask@1.2.3: 1318 + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1319 + 1320 + react-is@16.13.1: 1321 + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} 1322 + 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==} 1329 + engines: {node: '>= 0.4'} 1330 + 1331 + regexp.prototype.flags@1.5.4: 1332 + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} 1333 + engines: {node: '>= 0.4'} 1334 + 1335 + resolve-from@4.0.0: 1336 + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1337 + engines: {node: '>=4'} 1338 + 1339 + resolve@2.0.0-next.5: 1340 + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} 1341 + hasBin: true 1342 + 1343 + restore-cursor@5.1.0: 1344 + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} 1345 + engines: {node: '>=18'} 1346 + 1347 + reusify@1.0.4: 1348 + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1349 + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1350 + 1351 + run-parallel@1.2.0: 1352 + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1353 + 1354 + safe-array-concat@1.1.3: 1355 + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} 1356 + engines: {node: '>=0.4'} 1357 + 1358 + safe-buffer@5.2.1: 1359 + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1360 + 1361 + safe-push-apply@1.0.0: 1362 + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} 1363 + engines: {node: '>= 0.4'} 1364 + 1365 + safe-regex-test@1.1.0: 1366 + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} 1367 + engines: {node: '>= 0.4'} 1368 + 1369 + semver@6.3.1: 1370 + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1371 + hasBin: true 1372 + 1373 + semver@7.7.1: 1374 + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} 1375 + engines: {node: '>=10'} 1376 + hasBin: true 1377 + 1378 + set-function-length@1.2.2: 1379 + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} 1380 + engines: {node: '>= 0.4'} 1381 + 1382 + set-function-name@2.0.2: 1383 + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} 1384 + engines: {node: '>= 0.4'} 1385 + 1386 + set-proto@1.0.0: 1387 + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} 1388 + engines: {node: '>= 0.4'} 1389 + 1390 + shebang-command@2.0.0: 1391 + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1392 + engines: {node: '>=8'} 1393 + 1394 + shebang-regex@3.0.0: 1395 + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1396 + engines: {node: '>=8'} 1397 + 1398 + side-channel-list@1.0.0: 1399 + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} 1400 + engines: {node: '>= 0.4'} 1401 + 1402 + side-channel-map@1.0.1: 1403 + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} 1404 + engines: {node: '>= 0.4'} 1405 + 1406 + side-channel-weakmap@1.0.2: 1407 + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} 1408 + engines: {node: '>= 0.4'} 1409 + 1410 + side-channel@1.1.0: 1411 + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} 1412 + engines: {node: '>= 0.4'} 1413 + 1414 + signal-exit@4.1.0: 1415 + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 1416 + engines: {node: '>=14'} 1417 + 1418 + standalone-electron-types@1.0.0: 1419 + resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} 1420 + 1421 + string.prototype.matchall@4.0.12: 1422 + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} 1423 + engines: {node: '>= 0.4'} 1424 + 1425 + string.prototype.repeat@1.0.0: 1426 + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} 1427 + 1428 + string.prototype.trim@1.2.10: 1429 + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} 1430 + engines: {node: '>= 0.4'} 1431 + 1432 + string.prototype.trimend@1.0.9: 1433 + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} 1434 + engines: {node: '>= 0.4'} 1435 + 1436 + string.prototype.trimstart@1.0.8: 1437 + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} 1438 + engines: {node: '>= 0.4'} 1439 + 1440 + string_decoder@1.3.0: 1441 + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1442 + 1443 + strip-json-comments@3.1.1: 1444 + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1445 + engines: {node: '>=8'} 1446 + 1447 + supports-color@7.2.0: 1448 + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1449 + engines: {node: '>=8'} 1450 + 1451 + supports-preserve-symlinks-flag@1.0.0: 1452 + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1453 + engines: {node: '>= 0.4'} 1454 + 1455 + synckit@0.11.1: 1456 + resolution: {integrity: sha512-fWZqNBZNNFp/7mTUy1fSsydhKsAKJ+u90Nk7kOK5Gcq9vObaqLBLjWFDBkyVU9Vvc6Y71VbOevMuGhqv02bT+Q==} 1457 + engines: {node: ^14.18.0 || >=16.0.0} 1458 + 1459 + taze@19.0.4: 1460 + resolution: {integrity: sha512-bviyNotzqcIWpVBCC4QYVb2yupzKyUDGQi2m/8GERdiPaudVMtgAqaE98+x0cDDaByYRMJCyhQWM04ikUL6+kQ==} 1461 + hasBin: true 1462 + 1463 + tinyexec@1.0.1: 1464 + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} 1465 + 1466 + tinyglobby@0.2.12: 1467 + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} 1468 + engines: {node: '>=12.0.0'} 1469 + 1470 + to-regex-range@5.0.1: 1471 + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1472 + engines: {node: '>=8.0'} 1473 + 1474 + ts-api-utils@2.1.0: 1475 + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} 1476 + engines: {node: '>=18.12'} 1477 + peerDependencies: 1478 + typescript: '>=4.8.4' 1479 + 1480 + tslib@2.8.1: 1481 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1482 + 1483 + type-check@0.4.0: 1484 + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1485 + engines: {node: '>= 0.8.0'} 1486 + 1487 + typed-array-buffer@1.0.3: 1488 + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} 1489 + engines: {node: '>= 0.4'} 1490 + 1491 + typed-array-byte-length@1.0.3: 1492 + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} 1493 + engines: {node: '>= 0.4'} 1494 + 1495 + typed-array-byte-offset@1.0.4: 1496 + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} 1497 + engines: {node: '>= 0.4'} 1498 + 1499 + typed-array-length@1.0.7: 1500 + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} 1501 + engines: {node: '>= 0.4'} 1502 + 1503 + typescript-eslint@8.29.0: 1504 + resolution: {integrity: sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==} 1505 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1506 + peerDependencies: 1507 + eslint: ^8.57.0 || ^9.0.0 1508 + typescript: '>=4.8.4 <5.9.0' 1509 + 1510 + typescript@5.8.2: 1511 + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} 1512 + engines: {node: '>=14.17'} 1513 + hasBin: true 1514 + 1515 + ufo@1.5.4: 1516 + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} 1517 + 1518 + unbox-primitive@1.1.0: 1519 + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} 1520 + engines: {node: '>= 0.4'} 1521 + 1522 + unconfig@7.3.1: 1523 + resolution: {integrity: sha512-LH5WL+un92tGAzWS87k7LkAfwpMdm7V0IXG2FxEjZz/QxiIW5J5LkcrKQThj0aRz6+h/lFmKI9EUXmK/T0bcrw==} 1524 + 1525 + undici-types@6.20.0: 1526 + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 1527 + 1528 + undici-types@6.21.0: 1529 + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 1530 + 1531 + uri-js@4.4.1: 1532 + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1533 + 1534 + utilium@1.10.1: 1535 + resolution: {integrity: sha512-GQINDTb/ocyz4acQj3GXAe0wipYxws6L+9ouqaq10KlInTk9DGvW9TJd0pYa/Xu3cppNnZuB4T/sBuSXpcN2ng==} 1536 + 1537 + which-boxed-primitive@1.1.1: 1538 + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} 1539 + engines: {node: '>= 0.4'} 1540 + 1541 + which-builtin-type@1.2.1: 1542 + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} 1543 + engines: {node: '>= 0.4'} 1544 + 1545 + which-collection@1.0.2: 1546 + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} 1547 + engines: {node: '>= 0.4'} 1548 + 1549 + which-typed-array@1.1.19: 1550 + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} 1551 + engines: {node: '>= 0.4'} 1552 + 1553 + which@2.0.2: 1554 + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1555 + engines: {node: '>= 8'} 1556 + hasBin: true 1557 + 1558 + yaml@2.7.1: 1559 + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} 1560 + engines: {node: '>= 14'} 1561 + hasBin: true 1562 + 1563 + yocto-queue@0.1.0: 1564 + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1565 + engines: {node: '>=10'} 1566 + 1567 + zustand@5.0.3: 1568 + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} 1569 + engines: {node: '>=12.20.0'} 1570 + peerDependencies: 1571 + '@types/react': '>=18.0.0' 1572 + immer: '>=9.0.6' 1573 + react: '>=18.0.0' 1574 + use-sync-external-store: '>=1.2.0' 1575 + peerDependenciesMeta: 1576 + '@types/react': 1577 + optional: true 1578 + immer: 1579 + optional: true 1580 + react: 1581 + optional: true 1582 + use-sync-external-store: 1583 + optional: true 1584 + 1585 + snapshots: 1586 + 1587 + '@aashutoshrathi/word-wrap@1.2.6': {} 1588 + 1589 + '@antfu/ni@24.3.0': 1590 + dependencies: 1591 + ansis: 3.17.0 1592 + fzf: 0.5.2 1593 + package-manager-detector: 1.1.0 1594 + tinyexec: 1.0.1 1595 + 1596 + '@esbuild/android-arm64@0.19.3': 1597 + optional: true 1598 + 1599 + '@esbuild/android-arm@0.19.3': 1600 + optional: true 1601 + 1602 + '@esbuild/android-x64@0.19.3': 1603 + optional: true 1604 + 1605 + '@esbuild/darwin-arm64@0.19.3': 1606 + optional: true 1607 + 1608 + '@esbuild/darwin-x64@0.19.3': 1609 + optional: true 1610 + 1611 + '@esbuild/freebsd-arm64@0.19.3': 1612 + optional: true 1613 + 1614 + '@esbuild/freebsd-x64@0.19.3': 1615 + optional: true 1616 + 1617 + '@esbuild/linux-arm64@0.19.3': 1618 + optional: true 1619 + 1620 + '@esbuild/linux-arm@0.19.3': 1621 + optional: true 1622 + 1623 + '@esbuild/linux-ia32@0.19.3': 1624 + optional: true 1625 + 1626 + '@esbuild/linux-loong64@0.19.3': 1627 + optional: true 1628 + 1629 + '@esbuild/linux-mips64el@0.19.3': 1630 + optional: true 1631 + 1632 + '@esbuild/linux-ppc64@0.19.3': 1633 + optional: true 1634 + 1635 + '@esbuild/linux-riscv64@0.19.3': 1636 + optional: true 1637 + 1638 + '@esbuild/linux-s390x@0.19.3': 1639 + optional: true 1640 + 1641 + '@esbuild/linux-x64@0.19.3': 1642 + optional: true 1643 + 1644 + '@esbuild/netbsd-x64@0.19.3': 1645 + optional: true 1646 + 1647 + '@esbuild/openbsd-x64@0.19.3': 1648 + optional: true 1649 + 1650 + '@esbuild/sunos-x64@0.19.3': 1651 + optional: true 1652 + 1653 + '@esbuild/win32-arm64@0.19.3': 1654 + optional: true 1655 + 1656 + '@esbuild/win32-ia32@0.19.3': 1657 + optional: true 1658 + 1659 + '@esbuild/win32-x64@0.19.3': 1660 + optional: true 1661 + 1662 + '@eslint-community/eslint-utils@4.5.1(eslint@9.23.0(jiti@2.4.2))': 1663 + dependencies: 1664 + eslint: 9.23.0(jiti@2.4.2) 1665 + eslint-visitor-keys: 3.4.3 1666 + 1667 + '@eslint-community/regexpp@4.12.1': {} 1668 + 1669 + '@eslint/config-array@0.19.2': 1670 + dependencies: 1671 + '@eslint/object-schema': 2.1.6 1672 + debug: 4.4.0 1673 + minimatch: 3.1.2 1674 + transitivePeerDependencies: 1675 + - supports-color 1676 + 1677 + '@eslint/config-helpers@0.2.1': {} 1678 + 1679 + '@eslint/core@0.12.0': 1680 + dependencies: 1681 + '@types/json-schema': 7.0.15 1682 + 1683 + '@eslint/core@0.13.0': 1684 + dependencies: 1685 + '@types/json-schema': 7.0.15 1686 + 1687 + '@eslint/eslintrc@3.3.1': 335 1688 dependencies: 336 1689 ajv: 6.12.6 337 - debug: 4.3.4 338 - espree: 9.6.1 339 - globals: 13.23.0 340 - ignore: 5.3.0 1690 + debug: 4.4.0 1691 + espree: 10.3.0 1692 + globals: 14.0.0 1693 + ignore: 5.3.2 341 1694 import-fresh: 3.3.0 342 1695 js-yaml: 4.1.0 343 1696 minimatch: 3.1.2 344 1697 strip-json-comments: 3.1.1 345 1698 transitivePeerDependencies: 346 1699 - supports-color 347 - dev: true 348 1700 349 - /@eslint/js@8.55.0: 350 - resolution: {integrity: sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==} 351 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 352 - dev: true 1701 + '@eslint/js@9.23.0': {} 353 1702 354 - /@humanwhocodes/config-array@0.11.13: 355 - resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} 356 - engines: {node: '>=10.10.0'} 1703 + '@eslint/object-schema@2.1.6': {} 1704 + 1705 + '@eslint/plugin-kit@0.2.8': 357 1706 dependencies: 358 - '@humanwhocodes/object-schema': 2.0.1 359 - debug: 4.3.4 360 - 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) 361 1732 transitivePeerDependencies: 1733 + - '@types/eslint' 1734 + - prettier 362 1735 - supports-color 363 - dev: true 364 1736 365 - /@humanwhocodes/module-importer@1.0.1: 366 - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 367 - engines: {node: '>=12.22'} 368 - dev: true 1737 + '@moonlight-mod/lunast@1.0.1': 1738 + dependencies: 1739 + astring: 1.9.0 1740 + estree-toolkit: 1.7.8 1741 + meriyah: 6.0.1 1742 + 1743 + '@moonlight-mod/mappings@1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)': 1744 + dependencies: 1745 + '@moonlight-mod/lunast': 1.0.1 1746 + '@moonlight-mod/moonmap': 1.0.5 1747 + '@types/chroma-js': 3.1.0 1748 + '@types/flux': 3.1.14 1749 + '@types/highlightjs': 9.12.6 1750 + '@types/lodash': 4.17.14 1751 + '@types/platform': 1.3.6 1752 + '@types/react': 18.3.20 1753 + csstype: 3.1.3 1754 + zustand: 5.0.3(@types/react@18.3.20) 1755 + transitivePeerDependencies: 1756 + - immer 1757 + - react 1758 + - use-sync-external-store 369 1759 370 - /@humanwhocodes/object-schema@2.0.1: 371 - resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} 372 - dev: true 1760 + '@moonlight-mod/moonmap@1.0.5': {} 373 1761 374 - /@nodelib/fs.scandir@2.1.5: 375 - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 376 - engines: {node: '>= 8'} 1762 + '@nodelib/fs.scandir@2.1.5': 377 1763 dependencies: 378 1764 '@nodelib/fs.stat': 2.0.5 379 1765 run-parallel: 1.2.0 380 - dev: true 381 1766 382 - /@nodelib/fs.stat@2.0.5: 383 - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 384 - engines: {node: '>= 8'} 385 - dev: true 1767 + '@nodelib/fs.stat@2.0.5': {} 386 1768 387 - /@nodelib/fs.walk@1.2.8: 388 - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 389 - engines: {node: '>= 8'} 1769 + '@nodelib/fs.walk@1.2.8': 390 1770 dependencies: 391 1771 '@nodelib/fs.scandir': 2.1.5 392 - fastq: 1.15.0 393 - dev: true 1772 + fastq: 1.17.1 394 1773 395 - /@pkgr/utils@2.4.2: 396 - resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} 397 - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 1774 + '@pkgr/core@0.2.0': {} 1775 + 1776 + '@quansync/fs@0.1.2': 398 1777 dependencies: 399 - cross-spawn: 7.0.3 400 - fast-glob: 3.3.2 401 - is-glob: 4.0.3 402 - open: 9.1.0 403 - picocolors: 1.0.0 404 - tslib: 2.6.2 405 - dev: true 1778 + quansync: 0.2.10 406 1779 407 - /@types/fbemitter@2.0.33: 408 - resolution: {integrity: sha512-KcSilwdl0D8YgXGL6l9d+rTBm2W7pDyTZrDEw0+IzqQ724676KJtMeO+xHodJewKFWZT+GFWaJubA5mpMxSkcg==} 409 - dev: false 1780 + '@types/chroma-js@3.1.0': {} 410 1781 411 - /@types/flux@3.1.12: 412 - resolution: {integrity: sha512-HZ8o/DTVNgcgnXoDyn0ZnjqEZMT4Chr4w5ktMQSbQAnqVDklasmRqNGd2agZDsk5i0jYHQLgQQuM782bWG7fUA==} 1782 + '@types/chrome@0.0.313': 413 1783 dependencies: 414 - '@types/fbemitter': 2.0.33 415 - '@types/react': 18.2.22 416 - dev: false 1784 + '@types/filesystem': 0.0.36 1785 + '@types/har-format': 1.2.16 417 1786 418 - /@types/json-schema@7.0.15: 419 - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 420 - dev: true 1787 + '@types/eslint@9.6.1': 1788 + dependencies: 1789 + '@types/estree': 1.0.7 1790 + '@types/json-schema': 7.0.15 1791 + optional: true 421 1792 422 - /@types/node@18.17.17: 423 - resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} 424 - dev: false 1793 + '@types/estree-jsx@1.0.5': 1794 + dependencies: 1795 + '@types/estree': 1.0.6 425 1796 426 - /@types/node@20.6.2: 427 - resolution: {integrity: sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==} 428 - dev: false 1797 + '@types/estree@1.0.6': {} 429 1798 430 - /@types/prop-types@15.7.6: 431 - resolution: {integrity: sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==} 432 - dev: false 1799 + '@types/estree@1.0.7': 1800 + optional: true 433 1801 434 - /@types/react@18.2.22: 435 - resolution: {integrity: sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==} 1802 + '@types/fbemitter@2.0.35': {} 1803 + 1804 + '@types/filesystem@0.0.36': 436 1805 dependencies: 437 - '@types/prop-types': 15.7.6 438 - '@types/scheduler': 0.16.3 439 - csstype: 3.1.2 440 - dev: false 1806 + '@types/filewriter': 0.0.33 441 1807 442 - /@types/scheduler@0.16.3: 443 - resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} 444 - dev: false 1808 + '@types/filewriter@0.0.33': {} 445 1809 446 - /@types/semver@7.5.6: 447 - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} 448 - dev: true 1810 + '@types/flux@3.1.14': 1811 + dependencies: 1812 + '@types/fbemitter': 2.0.35 1813 + '@types/react': 18.3.20 449 1814 450 - /@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.2): 451 - resolution: {integrity: sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==} 452 - engines: {node: ^16.0.0 || >=18.0.0} 453 - peerDependencies: 454 - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha 455 - eslint: ^7.0.0 || ^8.0.0 456 - typescript: '*' 457 - peerDependenciesMeta: 458 - typescript: 459 - optional: true 1815 + '@types/har-format@1.2.16': {} 1816 + 1817 + '@types/highlightjs@9.12.6': {} 1818 + 1819 + '@types/json-schema@7.0.15': {} 1820 + 1821 + '@types/lodash@4.17.14': {} 1822 + 1823 + '@types/node@18.17.17': {} 1824 + 1825 + '@types/node@22.13.6': 1826 + dependencies: 1827 + undici-types: 6.20.0 1828 + 1829 + '@types/node@22.14.0': 1830 + dependencies: 1831 + undici-types: 6.21.0 1832 + 1833 + '@types/platform@1.3.6': {} 1834 + 1835 + '@types/prop-types@15.7.13': {} 1836 + 1837 + '@types/react@18.3.20': 1838 + dependencies: 1839 + '@types/prop-types': 15.7.13 1840 + csstype: 3.1.3 1841 + 1842 + '@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 460 1843 dependencies: 461 - '@eslint-community/regexpp': 4.10.0 462 - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 463 - '@typescript-eslint/scope-manager': 6.13.2 464 - '@typescript-eslint/type-utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 465 - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 466 - '@typescript-eslint/visitor-keys': 6.13.2 467 - debug: 4.3.4 468 - eslint: 8.55.0 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) 469 1851 graphemer: 1.4.0 470 - ignore: 5.3.0 1852 + ignore: 5.3.2 471 1853 natural-compare: 1.4.0 472 - semver: 7.5.4 473 - ts-api-utils: 1.0.3(typescript@5.3.2) 474 - typescript: 5.3.2 1854 + ts-api-utils: 2.1.0(typescript@5.8.2) 1855 + typescript: 5.8.2 475 1856 transitivePeerDependencies: 476 1857 - supports-color 477 - dev: true 478 1858 479 - /@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2): 480 - resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==} 481 - engines: {node: ^16.0.0 || >=18.0.0} 482 - peerDependencies: 483 - eslint: ^7.0.0 || ^8.0.0 484 - typescript: '*' 485 - peerDependenciesMeta: 486 - typescript: 487 - optional: true 1859 + '@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 488 1860 dependencies: 489 - '@typescript-eslint/scope-manager': 6.13.2 490 - '@typescript-eslint/types': 6.13.2 491 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 492 - '@typescript-eslint/visitor-keys': 6.13.2 493 - debug: 4.3.4 494 - eslint: 8.55.0 495 - 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 496 1868 transitivePeerDependencies: 497 1869 - supports-color 498 - dev: true 499 1870 500 - /@typescript-eslint/scope-manager@6.13.2: 501 - resolution: {integrity: sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==} 502 - engines: {node: ^16.0.0 || >=18.0.0} 1871 + '@typescript-eslint/scope-manager@8.29.0': 503 1872 dependencies: 504 - '@typescript-eslint/types': 6.13.2 505 - '@typescript-eslint/visitor-keys': 6.13.2 506 - dev: true 1873 + '@typescript-eslint/types': 8.29.0 1874 + '@typescript-eslint/visitor-keys': 8.29.0 507 1875 508 - /@typescript-eslint/type-utils@6.13.2(eslint@8.55.0)(typescript@5.3.2): 509 - resolution: {integrity: sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==} 510 - engines: {node: ^16.0.0 || >=18.0.0} 511 - peerDependencies: 512 - eslint: ^7.0.0 || ^8.0.0 513 - typescript: '*' 514 - peerDependenciesMeta: 515 - typescript: 516 - optional: true 1876 + '@typescript-eslint/type-utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 517 1877 dependencies: 518 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 519 - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 520 - debug: 4.3.4 521 - eslint: 8.55.0 522 - ts-api-utils: 1.0.3(typescript@5.3.2) 523 - 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 524 1884 transitivePeerDependencies: 525 1885 - supports-color 526 - dev: true 527 1886 528 - /@typescript-eslint/types@6.13.2: 529 - resolution: {integrity: sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==} 530 - engines: {node: ^16.0.0 || >=18.0.0} 531 - dev: true 1887 + '@typescript-eslint/types@8.29.0': {} 532 1888 533 - /@typescript-eslint/typescript-estree@6.13.2(typescript@5.3.2): 534 - resolution: {integrity: sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==} 535 - engines: {node: ^16.0.0 || >=18.0.0} 536 - peerDependencies: 537 - typescript: '*' 538 - peerDependenciesMeta: 539 - typescript: 540 - optional: true 1889 + '@typescript-eslint/typescript-estree@8.29.0(typescript@5.8.2)': 541 1890 dependencies: 542 - '@typescript-eslint/types': 6.13.2 543 - '@typescript-eslint/visitor-keys': 6.13.2 544 - debug: 4.3.4 545 - 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 546 1895 is-glob: 4.0.3 547 - semver: 7.5.4 548 - ts-api-utils: 1.0.3(typescript@5.3.2) 549 - 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 550 1900 transitivePeerDependencies: 551 1901 - supports-color 552 - dev: true 553 1902 554 - /@typescript-eslint/utils@6.13.2(eslint@8.55.0)(typescript@5.3.2): 555 - resolution: {integrity: sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==} 556 - engines: {node: ^16.0.0 || >=18.0.0} 557 - peerDependencies: 558 - eslint: ^7.0.0 || ^8.0.0 1903 + '@typescript-eslint/utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)': 559 1904 dependencies: 560 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) 561 - '@types/json-schema': 7.0.15 562 - '@types/semver': 7.5.6 563 - '@typescript-eslint/scope-manager': 6.13.2 564 - '@typescript-eslint/types': 6.13.2 565 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 566 - eslint: 8.55.0 567 - 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 568 1911 transitivePeerDependencies: 569 1912 - supports-color 570 - - typescript 571 - dev: true 572 1913 573 - /@typescript-eslint/visitor-keys@6.13.2: 574 - resolution: {integrity: sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==} 575 - engines: {node: ^16.0.0 || >=18.0.0} 1914 + '@typescript-eslint/visitor-keys@8.29.0': 1915 + dependencies: 1916 + '@typescript-eslint/types': 8.29.0 1917 + eslint-visitor-keys: 4.2.0 1918 + 1919 + '@xterm/xterm@5.5.0': 1920 + optional: true 1921 + 1922 + '@zenfs/core@2.0.0': 1923 + dependencies: 1924 + '@types/node': 22.13.6 1925 + buffer: 6.0.3 1926 + eventemitter3: 5.0.1 1927 + readable-stream: 4.5.2 1928 + utilium: 1.10.1 1929 + 1930 + '@zenfs/dom@1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1)': 576 1931 dependencies: 577 - '@typescript-eslint/types': 6.13.2 578 - eslint-visitor-keys: 3.4.3 579 - dev: true 1932 + '@zenfs/core': 2.0.0 1933 + utilium: 1.10.1 580 1934 581 - /@ungap/structured-clone@1.2.0: 582 - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} 583 - dev: true 1935 + abort-controller@3.0.0: 1936 + dependencies: 1937 + event-target-shim: 5.0.1 584 1938 585 - /acorn-jsx@5.3.2(acorn@8.11.2): 586 - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 587 - peerDependencies: 588 - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 1939 + acorn-jsx@5.3.2(acorn@8.14.1): 589 1940 dependencies: 590 - acorn: 8.11.2 591 - dev: true 1941 + acorn: 8.14.1 592 1942 593 - /acorn@8.11.2: 594 - resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} 595 - engines: {node: '>=0.4.0'} 596 - hasBin: true 597 - dev: true 1943 + acorn@8.14.1: {} 598 1944 599 - /ajv@6.12.6: 600 - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 1945 + ajv@6.12.6: 601 1946 dependencies: 602 1947 fast-deep-equal: 3.1.3 603 1948 fast-json-stable-stringify: 2.1.0 604 1949 json-schema-traverse: 0.4.1 605 1950 uri-js: 4.4.1 606 - dev: true 607 1951 608 - /ansi-regex@5.0.1: 609 - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 610 - engines: {node: '>=8'} 611 - dev: true 612 - 613 - /ansi-styles@4.3.0: 614 - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 615 - engines: {node: '>=8'} 1952 + ansi-styles@4.3.0: 616 1953 dependencies: 617 1954 color-convert: 2.0.1 618 - dev: true 619 1955 620 - /argparse@2.0.1: 621 - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 622 - dev: true 1956 + ansis@3.17.0: {} 623 1957 624 - /array-buffer-byte-length@1.0.0: 625 - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} 1958 + argparse@2.0.1: {} 1959 + 1960 + array-buffer-byte-length@1.0.2: 626 1961 dependencies: 627 - call-bind: 1.0.5 628 - is-array-buffer: 3.0.2 629 - dev: true 1962 + call-bound: 1.0.4 1963 + is-array-buffer: 3.0.5 630 1964 631 - /array-includes@3.1.7: 632 - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} 633 - engines: {node: '>= 0.4'} 1965 + array-includes@3.1.8: 634 1966 dependencies: 635 - call-bind: 1.0.5 1967 + call-bind: 1.0.8 636 1968 define-properties: 1.2.1 637 - es-abstract: 1.22.3 638 - get-intrinsic: 1.2.2 639 - is-string: 1.0.7 640 - dev: true 641 - 642 - /array-union@2.1.0: 643 - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 644 - engines: {node: '>=8'} 645 - dev: true 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 646 1973 647 - /array.prototype.flat@1.3.2: 648 - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} 649 - engines: {node: '>= 0.4'} 1974 + array.prototype.findlast@1.2.5: 650 1975 dependencies: 651 - call-bind: 1.0.5 1976 + call-bind: 1.0.8 652 1977 define-properties: 1.2.1 653 - es-abstract: 1.22.3 654 - es-shim-unscopables: 1.0.2 655 - dev: true 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 656 1982 657 - /array.prototype.flatmap@1.3.2: 658 - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} 659 - engines: {node: '>= 0.4'} 1983 + array.prototype.flat@1.3.3: 660 1984 dependencies: 661 - call-bind: 1.0.5 1985 + call-bind: 1.0.8 662 1986 define-properties: 1.2.1 663 - es-abstract: 1.22.3 664 - es-shim-unscopables: 1.0.2 665 - dev: true 1987 + es-abstract: 1.23.9 1988 + es-shim-unscopables: 1.1.0 666 1989 667 - /array.prototype.tosorted@1.1.2: 668 - resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} 1990 + array.prototype.flatmap@1.3.3: 669 1991 dependencies: 670 - call-bind: 1.0.5 1992 + call-bind: 1.0.8 671 1993 define-properties: 1.2.1 672 - es-abstract: 1.22.3 673 - es-shim-unscopables: 1.0.2 674 - get-intrinsic: 1.2.2 675 - dev: true 1994 + es-abstract: 1.23.9 1995 + es-shim-unscopables: 1.1.0 676 1996 677 - /arraybuffer.prototype.slice@1.0.2: 678 - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} 679 - engines: {node: '>= 0.4'} 1997 + array.prototype.tosorted@1.1.4: 680 1998 dependencies: 681 - array-buffer-byte-length: 1.0.0 682 - call-bind: 1.0.5 1999 + call-bind: 1.0.8 683 2000 define-properties: 1.2.1 684 - es-abstract: 1.22.3 685 - get-intrinsic: 1.2.2 686 - is-array-buffer: 3.0.2 687 - is-shared-array-buffer: 1.0.2 688 - dev: true 2001 + es-abstract: 1.23.9 2002 + es-errors: 1.3.0 2003 + es-shim-unscopables: 1.1.0 689 2004 690 - /asynciterator.prototype@1.0.0: 691 - resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} 2005 + arraybuffer.prototype.slice@1.0.4: 692 2006 dependencies: 693 - has-symbols: 1.0.3 694 - dev: true 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 695 2014 696 - /available-typed-arrays@1.0.5: 697 - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} 698 - engines: {node: '>= 0.4'} 699 - dev: true 2015 + astring@1.9.0: {} 700 2016 701 - /balanced-match@1.0.2: 702 - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 2017 + async-function@1.0.0: {} 703 2018 704 - /big-integer@1.6.52: 705 - resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} 706 - engines: {node: '>=0.6'} 707 - dev: true 2019 + available-typed-arrays@1.0.7: 2020 + dependencies: 2021 + possible-typed-array-names: 1.1.0 708 2022 709 - /bplist-parser@0.2.0: 710 - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} 711 - engines: {node: '>= 5.10.0'} 2023 + balanced-match@1.0.2: {} 2024 + 2025 + base64-js@1.5.1: {} 2026 + 2027 + brace-expansion@1.1.11: 712 2028 dependencies: 713 - big-integer: 1.6.52 714 - dev: true 2029 + balanced-match: 1.0.2 2030 + concat-map: 0.0.1 715 2031 716 - /brace-expansion@1.1.11: 717 - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 2032 + brace-expansion@2.0.1: 718 2033 dependencies: 719 2034 balanced-match: 1.0.2 720 - concat-map: 0.0.1 721 2035 722 - /braces@3.0.2: 723 - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 724 - engines: {node: '>=8'} 2036 + braces@3.0.3: 725 2037 dependencies: 726 - fill-range: 7.0.1 727 - dev: true 2038 + fill-range: 7.1.1 728 2039 729 - /bundle-name@3.0.0: 730 - resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} 731 - engines: {node: '>=12'} 2040 + buffer@6.0.3: 732 2041 dependencies: 733 - run-applescript: 5.0.0 734 - dev: true 2042 + base64-js: 1.5.1 2043 + ieee754: 1.2.1 735 2044 736 - /call-bind@1.0.5: 737 - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} 2045 + cac@6.7.14: {} 2046 + 2047 + call-bind-apply-helpers@1.0.2: 738 2048 dependencies: 2049 + es-errors: 1.3.0 739 2050 function-bind: 1.1.2 740 - get-intrinsic: 1.2.2 741 - set-function-length: 1.1.1 742 - dev: true 743 2051 744 - /callsites@3.1.0: 745 - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 746 - engines: {node: '>=6'} 747 - dev: true 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 748 2058 749 - /chalk@4.1.2: 750 - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 751 - engines: {node: '>=10'} 2059 + call-bound@1.0.4: 2060 + dependencies: 2061 + call-bind-apply-helpers: 1.0.2 2062 + get-intrinsic: 1.3.0 2063 + 2064 + callsites@3.1.0: {} 2065 + 2066 + chalk@4.1.2: 752 2067 dependencies: 753 2068 ansi-styles: 4.3.0 754 2069 supports-color: 7.2.0 755 - dev: true 756 2070 757 - /color-convert@2.0.1: 758 - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 759 - engines: {node: '>=7.0.0'} 2071 + color-convert@2.0.1: 760 2072 dependencies: 761 2073 color-name: 1.1.4 762 - dev: true 763 2074 764 - /color-name@1.1.4: 765 - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 766 - dev: true 2075 + color-name@1.1.4: {} 767 2076 768 - /commander@5.1.0: 769 - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} 770 - engines: {node: '>= 6'} 771 - dev: false 2077 + concat-map@0.0.1: {} 772 2078 773 - /concat-map@0.0.1: 774 - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 775 - 776 - /cross-spawn@7.0.3: 777 - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 778 - engines: {node: '>= 8'} 2079 + cross-spawn@7.0.6: 779 2080 dependencies: 780 2081 path-key: 3.1.1 781 2082 shebang-command: 2.0.0 782 2083 which: 2.0.2 783 - dev: true 784 2084 785 - /csstype@3.1.2: 786 - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} 787 - dev: false 2085 + csstype@3.1.3: {} 788 2086 789 - /debug@4.3.4: 790 - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 791 - engines: {node: '>=6.0'} 792 - peerDependencies: 793 - supports-color: '*' 794 - peerDependenciesMeta: 795 - supports-color: 796 - optional: true 2087 + data-view-buffer@1.0.2: 797 2088 dependencies: 798 - ms: 2.1.2 799 - dev: true 2089 + call-bound: 1.0.4 2090 + es-errors: 1.3.0 2091 + is-data-view: 1.0.2 800 2092 801 - /deep-is@0.1.4: 802 - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 803 - dev: true 2093 + data-view-byte-length@1.0.2: 2094 + dependencies: 2095 + call-bound: 1.0.4 2096 + es-errors: 1.3.0 2097 + is-data-view: 1.0.2 804 2098 805 - /default-browser-id@3.0.0: 806 - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} 807 - engines: {node: '>=12'} 2099 + data-view-byte-offset@1.0.1: 808 2100 dependencies: 809 - bplist-parser: 0.2.0 810 - untildify: 4.0.0 811 - dev: true 2101 + call-bound: 1.0.4 2102 + es-errors: 1.3.0 2103 + is-data-view: 1.0.2 812 2104 813 - /default-browser@4.0.0: 814 - resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} 815 - engines: {node: '>=14.16'} 2105 + debug@4.4.0: 816 2106 dependencies: 817 - bundle-name: 3.0.0 818 - default-browser-id: 3.0.0 819 - execa: 7.2.0 820 - titleize: 3.0.0 821 - dev: true 2107 + ms: 2.1.3 2108 + 2109 + deep-is@0.1.4: {} 822 2110 823 - /define-data-property@1.1.1: 824 - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} 825 - engines: {node: '>= 0.4'} 2111 + define-data-property@1.1.4: 826 2112 dependencies: 827 - get-intrinsic: 1.2.2 828 - gopd: 1.0.1 829 - has-property-descriptors: 1.0.1 830 - dev: true 831 - 832 - /define-lazy-prop@3.0.0: 833 - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} 834 - engines: {node: '>=12'} 835 - dev: true 2113 + es-define-property: 1.0.1 2114 + es-errors: 1.3.0 2115 + gopd: 1.2.0 836 2116 837 - /define-properties@1.2.1: 838 - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} 839 - engines: {node: '>= 0.4'} 2117 + define-properties@1.2.1: 840 2118 dependencies: 841 - define-data-property: 1.1.1 842 - has-property-descriptors: 1.0.1 2119 + define-data-property: 1.1.4 2120 + has-property-descriptors: 1.0.2 843 2121 object-keys: 1.1.1 844 - dev: true 845 2122 846 - /dir-glob@3.0.1: 847 - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 848 - engines: {node: '>=8'} 849 - dependencies: 850 - path-type: 4.0.0 851 - dev: true 2123 + defu@6.1.4: {} 852 2124 853 - /doctrine@2.1.0: 854 - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} 855 - engines: {node: '>=0.10.0'} 2125 + destr@2.0.4: {} 2126 + 2127 + doctrine@2.1.0: 856 2128 dependencies: 857 2129 esutils: 2.0.3 858 - dev: true 859 2130 860 - /doctrine@3.0.0: 861 - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 862 - engines: {node: '>=6.0.0'} 2131 + dunder-proto@1.0.1: 863 2132 dependencies: 864 - esutils: 2.0.3 865 - dev: true 2133 + call-bind-apply-helpers: 1.0.2 2134 + es-errors: 1.3.0 2135 + gopd: 1.2.0 866 2136 867 - /es-abstract@1.22.3: 868 - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} 869 - engines: {node: '>= 0.4'} 2137 + es-abstract@1.23.9: 870 2138 dependencies: 871 - array-buffer-byte-length: 1.0.0 872 - arraybuffer.prototype.slice: 1.0.2 873 - available-typed-arrays: 1.0.5 874 - call-bind: 1.0.5 875 - es-set-tostringtag: 2.0.2 876 - es-to-primitive: 1.2.1 877 - function.prototype.name: 1.1.6 878 - get-intrinsic: 1.2.2 879 - get-symbol-description: 1.0.0 880 - globalthis: 1.0.3 881 - gopd: 1.0.1 882 - has-property-descriptors: 1.0.1 883 - has-proto: 1.0.1 884 - has-symbols: 1.0.3 885 - hasown: 2.0.0 886 - internal-slot: 1.0.6 887 - 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 888 2164 is-callable: 1.2.7 889 - is-negative-zero: 2.0.2 890 - is-regex: 1.1.4 891 - is-shared-array-buffer: 1.0.2 892 - is-string: 1.0.7 893 - is-typed-array: 1.1.12 894 - is-weakref: 1.0.2 895 - 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 896 2173 object-keys: 1.1.1 897 - object.assign: 4.1.5 898 - regexp.prototype.flags: 1.5.1 899 - safe-array-concat: 1.0.1 900 - safe-regex-test: 1.0.0 901 - string.prototype.trim: 1.2.8 902 - string.prototype.trimend: 1.0.7 903 - string.prototype.trimstart: 1.0.7 904 - typed-array-buffer: 1.0.0 905 - typed-array-byte-length: 1.0.0 906 - typed-array-byte-offset: 1.0.0 907 - typed-array-length: 1.0.4 908 - unbox-primitive: 1.0.2 909 - which-typed-array: 1.1.13 910 - dev: true 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 911 2190 912 - /es-iterator-helpers@1.0.15: 913 - resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} 2191 + es-define-property@1.0.1: {} 2192 + 2193 + es-errors@1.3.0: {} 2194 + 2195 + es-iterator-helpers@1.2.1: 914 2196 dependencies: 915 - asynciterator.prototype: 1.0.0 916 - call-bind: 1.0.5 2197 + call-bind: 1.0.8 2198 + call-bound: 1.0.4 917 2199 define-properties: 1.2.1 918 - es-abstract: 1.22.3 919 - 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 920 2203 function-bind: 1.1.2 921 - get-intrinsic: 1.2.2 922 - globalthis: 1.0.3 923 - has-property-descriptors: 1.0.1 924 - has-proto: 1.0.1 925 - has-symbols: 1.0.3 926 - internal-slot: 1.0.6 927 - iterator.prototype: 1.1.2 928 - safe-array-concat: 1.0.1 929 - dev: true 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 930 2213 931 - /es-set-tostringtag@2.0.2: 932 - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} 933 - engines: {node: '>= 0.4'} 2214 + es-object-atoms@1.1.1: 934 2215 dependencies: 935 - get-intrinsic: 1.2.2 936 - has-tostringtag: 1.0.0 937 - hasown: 2.0.0 938 - dev: true 2216 + es-errors: 1.3.0 939 2217 940 - /es-shim-unscopables@1.0.2: 941 - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} 2218 + es-set-tostringtag@2.1.0: 942 2219 dependencies: 943 - hasown: 2.0.0 944 - dev: true 2220 + es-errors: 1.3.0 2221 + get-intrinsic: 1.3.0 2222 + has-tostringtag: 1.0.2 2223 + hasown: 2.0.2 945 2224 946 - /es-to-primitive@1.2.1: 947 - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} 948 - engines: {node: '>= 0.4'} 2225 + es-shim-unscopables@1.1.0: 2226 + dependencies: 2227 + hasown: 2.0.2 2228 + 2229 + es-to-primitive@1.3.0: 949 2230 dependencies: 950 2231 is-callable: 1.2.7 951 - is-date-object: 1.0.5 952 - is-symbol: 1.0.4 953 - dev: true 2232 + is-date-object: 1.1.0 2233 + is-symbol: 1.1.1 954 2234 955 - /esbuild-copy-static-files@0.1.0: 956 - resolution: {integrity: sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w==} 957 - dev: true 2235 + esbuild-copy-static-files@0.1.0: {} 958 2236 959 - /esbuild@0.19.3: 960 - resolution: {integrity: sha512-UlJ1qUUA2jL2nNib1JTSkifQTcYTroFqRjwCFW4QYEKEsixXD5Tik9xML7zh2gTxkYTBKGHNH9y7txMwVyPbjw==} 961 - engines: {node: '>=12'} 962 - hasBin: true 963 - requiresBuild: true 2237 + esbuild@0.19.3: 964 2238 optionalDependencies: 965 2239 '@esbuild/android-arm': 0.19.3 966 2240 '@esbuild/android-arm64': 0.19.3 ··· 984 2258 '@esbuild/win32-arm64': 0.19.3 985 2259 '@esbuild/win32-ia32': 0.19.3 986 2260 '@esbuild/win32-x64': 0.19.3 987 - dev: true 988 2261 989 - /escape-string-regexp@4.0.0: 990 - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 991 - engines: {node: '>=10'} 992 - dev: true 2262 + escape-string-regexp@4.0.0: {} 993 2263 994 - /eslint-config-prettier@9.1.0(eslint@8.55.0): 995 - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} 996 - hasBin: true 997 - peerDependencies: 998 - eslint: '>=7.0.0' 2264 + eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)): 999 2265 dependencies: 1000 - eslint: 8.55.0 1001 - dev: true 2266 + eslint: 9.23.0(jiti@2.4.2) 1002 2267 1003 - /eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.1.0)(eslint@8.55.0)(prettier@3.1.0): 1004 - resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} 1005 - engines: {node: ^14.18.0 || >=16.0.0} 1006 - peerDependencies: 1007 - '@types/eslint': '>=8.0.0' 1008 - eslint: '>=8.0.0' 1009 - eslint-config-prettier: '*' 1010 - prettier: '>=3.0.0' 1011 - peerDependenciesMeta: 1012 - '@types/eslint': 1013 - optional: true 1014 - eslint-config-prettier: 1015 - optional: true 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): 1016 2269 dependencies: 1017 - eslint: 8.55.0 1018 - eslint-config-prettier: 9.1.0(eslint@8.55.0) 2270 + eslint: 9.23.0(jiti@2.4.2) 1019 2271 prettier: 3.1.0 1020 2272 prettier-linter-helpers: 1.0.0 1021 - synckit: 0.8.6 1022 - dev: true 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)) 1023 2277 1024 - /eslint-plugin-react@7.33.2(eslint@8.55.0): 1025 - resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} 1026 - engines: {node: '>=4'} 1027 - peerDependencies: 1028 - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 2278 + eslint-plugin-react@7.37.5(eslint@9.23.0(jiti@2.4.2)): 1029 2279 dependencies: 1030 - array-includes: 3.1.7 1031 - array.prototype.flatmap: 1.3.2 1032 - 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 1033 2284 doctrine: 2.1.0 1034 - es-iterator-helpers: 1.0.15 1035 - eslint: 8.55.0 2285 + es-iterator-helpers: 1.2.1 2286 + eslint: 9.23.0(jiti@2.4.2) 1036 2287 estraverse: 5.3.0 2288 + hasown: 2.0.2 1037 2289 jsx-ast-utils: 3.3.5 1038 2290 minimatch: 3.1.2 1039 - object.entries: 1.1.7 1040 - object.fromentries: 2.0.7 1041 - object.hasown: 1.1.3 1042 - object.values: 1.1.7 2291 + object.entries: 1.1.9 2292 + object.fromentries: 2.0.8 2293 + object.values: 1.2.1 1043 2294 prop-types: 15.8.1 1044 2295 resolve: 2.0.0-next.5 1045 2296 semver: 6.3.1 1046 - string.prototype.matchall: 4.0.10 1047 - dev: true 2297 + string.prototype.matchall: 4.0.12 2298 + string.prototype.repeat: 1.0.0 1048 2299 1049 - /eslint-scope@7.2.2: 1050 - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 1051 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 2300 + eslint-scope@8.3.0: 1052 2301 dependencies: 1053 2302 esrecurse: 4.3.0 1054 2303 estraverse: 5.3.0 1055 - dev: true 1056 2304 1057 - /eslint-visitor-keys@3.4.3: 1058 - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 1059 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1060 - dev: true 2305 + eslint-visitor-keys@3.4.3: {} 1061 2306 1062 - /eslint@8.55.0: 1063 - resolution: {integrity: sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==} 1064 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1065 - hasBin: true 2307 + eslint-visitor-keys@4.2.0: {} 2308 + 2309 + eslint@9.23.0(jiti@2.4.2): 1066 2310 dependencies: 1067 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) 1068 - '@eslint-community/regexpp': 4.10.0 1069 - '@eslint/eslintrc': 2.1.4 1070 - '@eslint/js': 8.55.0 1071 - '@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 1072 2320 '@humanwhocodes/module-importer': 1.0.1 1073 - '@nodelib/fs.walk': 1.2.8 1074 - '@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 1075 2324 ajv: 6.12.6 1076 2325 chalk: 4.1.2 1077 - cross-spawn: 7.0.3 1078 - debug: 4.3.4 1079 - doctrine: 3.0.0 2326 + cross-spawn: 7.0.6 2327 + debug: 4.4.0 1080 2328 escape-string-regexp: 4.0.0 1081 - eslint-scope: 7.2.2 1082 - eslint-visitor-keys: 3.4.3 1083 - espree: 9.6.1 1084 - 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 1085 2333 esutils: 2.0.3 1086 2334 fast-deep-equal: 3.1.3 1087 - file-entry-cache: 6.0.1 2335 + file-entry-cache: 8.0.0 1088 2336 find-up: 5.0.0 1089 2337 glob-parent: 6.0.2 1090 - globals: 13.23.0 1091 - graphemer: 1.4.0 1092 - ignore: 5.3.0 2338 + ignore: 5.3.2 1093 2339 imurmurhash: 0.1.4 1094 2340 is-glob: 4.0.3 1095 - is-path-inside: 3.0.3 1096 - js-yaml: 4.1.0 1097 2341 json-stable-stringify-without-jsonify: 1.0.1 1098 - levn: 0.4.1 1099 2342 lodash.merge: 4.6.2 1100 2343 minimatch: 3.1.2 1101 2344 natural-compare: 1.4.0 1102 2345 optionator: 0.9.3 1103 - strip-ansi: 6.0.1 1104 - text-table: 0.2.0 2346 + optionalDependencies: 2347 + jiti: 2.4.2 1105 2348 transitivePeerDependencies: 1106 2349 - supports-color 1107 - dev: true 1108 2350 1109 - /espree@9.6.1: 1110 - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 1111 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 2351 + espree@10.3.0: 1112 2352 dependencies: 1113 - acorn: 8.11.2 1114 - acorn-jsx: 5.3.2(acorn@8.11.2) 1115 - eslint-visitor-keys: 3.4.3 1116 - dev: true 2353 + acorn: 8.14.1 2354 + acorn-jsx: 5.3.2(acorn@8.14.1) 2355 + eslint-visitor-keys: 4.2.0 1117 2356 1118 - /esquery@1.5.0: 1119 - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 1120 - engines: {node: '>=0.10'} 2357 + esquery@1.6.0: 1121 2358 dependencies: 1122 2359 estraverse: 5.3.0 1123 - dev: true 1124 2360 1125 - /esrecurse@4.3.0: 1126 - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 1127 - engines: {node: '>=4.0'} 2361 + esrecurse@4.3.0: 1128 2362 dependencies: 1129 2363 estraverse: 5.3.0 1130 - dev: true 1131 2364 1132 - /estraverse@5.3.0: 1133 - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 1134 - engines: {node: '>=4.0'} 1135 - dev: true 2365 + estraverse@5.3.0: {} 1136 2366 1137 - /esutils@2.0.3: 1138 - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 1139 - engines: {node: '>=0.10.0'} 1140 - dev: true 1141 - 1142 - /execa@5.1.1: 1143 - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} 1144 - engines: {node: '>=10'} 2367 + estree-toolkit@1.7.8: 1145 2368 dependencies: 1146 - cross-spawn: 7.0.3 1147 - get-stream: 6.0.1 1148 - human-signals: 2.1.0 1149 - is-stream: 2.0.1 1150 - merge-stream: 2.0.0 1151 - npm-run-path: 4.0.1 1152 - onetime: 5.1.2 1153 - signal-exit: 3.0.7 1154 - strip-final-newline: 2.0.0 1155 - dev: true 2369 + '@types/estree': 1.0.6 2370 + '@types/estree-jsx': 1.0.5 1156 2371 1157 - /execa@7.2.0: 1158 - resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} 1159 - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} 1160 - dependencies: 1161 - cross-spawn: 7.0.3 1162 - get-stream: 6.0.1 1163 - human-signals: 4.3.1 1164 - is-stream: 3.0.0 1165 - merge-stream: 2.0.0 1166 - npm-run-path: 5.1.0 1167 - onetime: 6.0.0 1168 - signal-exit: 3.0.7 1169 - strip-final-newline: 3.0.0 1170 - dev: true 2372 + esutils@2.0.3: {} 1171 2373 1172 - /fast-deep-equal@3.1.3: 1173 - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 1174 - dev: true 2374 + event-target-shim@5.0.1: {} 1175 2375 1176 - /fast-diff@1.3.0: 1177 - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} 1178 - dev: true 2376 + eventemitter3@5.0.1: {} 1179 2377 1180 - /fast-glob@3.3.2: 1181 - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} 1182 - engines: {node: '>=8.6.0'} 2378 + events@3.3.0: {} 2379 + 2380 + fast-deep-equal@3.1.3: {} 2381 + 2382 + fast-diff@1.3.0: {} 2383 + 2384 + fast-glob@3.3.2: 1183 2385 dependencies: 1184 2386 '@nodelib/fs.stat': 2.0.5 1185 2387 '@nodelib/fs.walk': 1.2.8 1186 2388 glob-parent: 5.1.2 1187 2389 merge2: 1.4.1 1188 - micromatch: 4.0.5 1189 - dev: true 2390 + micromatch: 4.0.8 1190 2391 1191 - /fast-json-stable-stringify@2.1.0: 1192 - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 1193 - dev: true 2392 + fast-json-stable-stringify@2.1.0: {} 1194 2393 1195 - /fast-levenshtein@2.0.6: 1196 - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 1197 - dev: true 2394 + fast-levenshtein@2.0.6: {} 1198 2395 1199 - /fastq@1.15.0: 1200 - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 2396 + fastq@1.17.1: 1201 2397 dependencies: 1202 2398 reusify: 1.0.4 1203 - dev: true 1204 2399 1205 - /file-entry-cache@6.0.1: 1206 - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 1207 - engines: {node: ^10.12.0 || >=12.0.0} 2400 + fdir@6.4.3(picomatch@4.0.2): 2401 + optionalDependencies: 2402 + picomatch: 4.0.2 2403 + 2404 + file-entry-cache@8.0.0: 1208 2405 dependencies: 1209 - flat-cache: 3.2.0 1210 - dev: true 2406 + flat-cache: 4.0.1 1211 2407 1212 - /fill-range@7.0.1: 1213 - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 1214 - engines: {node: '>=8'} 2408 + fill-range@7.1.1: 1215 2409 dependencies: 1216 2410 to-regex-range: 5.0.1 1217 - dev: true 1218 2411 1219 - /find-up@5.0.0: 1220 - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 1221 - engines: {node: '>=10'} 2412 + find-up-simple@1.0.1: {} 2413 + 2414 + find-up@5.0.0: 1222 2415 dependencies: 1223 2416 locate-path: 6.0.0 1224 2417 path-exists: 4.0.0 1225 - dev: true 1226 2418 1227 - /flat-cache@3.2.0: 1228 - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} 1229 - engines: {node: ^10.12.0 || >=12.0.0} 2419 + flat-cache@4.0.1: 1230 2420 dependencies: 1231 2421 flatted: 3.2.9 1232 2422 keyv: 4.5.4 1233 - rimraf: 3.0.2 1234 - dev: true 1235 2423 1236 - /flatted@3.2.9: 1237 - resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} 1238 - dev: true 2424 + flatted@3.2.9: {} 1239 2425 1240 - /for-each@0.3.3: 1241 - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} 2426 + for-each@0.3.5: 1242 2427 dependencies: 1243 2428 is-callable: 1.2.7 1244 - dev: true 1245 2429 1246 - /fs.realpath@1.0.0: 1247 - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 2430 + function-bind@1.1.2: {} 1248 2431 1249 - /function-bind@1.1.2: 1250 - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 1251 - dev: true 1252 - 1253 - /function.prototype.name@1.1.6: 1254 - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} 1255 - engines: {node: '>= 0.4'} 2432 + function.prototype.name@1.1.8: 1256 2433 dependencies: 1257 - call-bind: 1.0.5 2434 + call-bind: 1.0.8 2435 + call-bound: 1.0.4 1258 2436 define-properties: 1.2.1 1259 - es-abstract: 1.22.3 1260 2437 functions-have-names: 1.2.3 1261 - dev: true 2438 + hasown: 2.0.2 2439 + is-callable: 1.2.7 1262 2440 1263 - /functions-have-names@1.2.3: 1264 - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} 1265 - dev: true 2441 + functions-have-names@1.2.3: {} 2442 + 2443 + fzf@0.5.2: {} 1266 2444 1267 - /get-intrinsic@1.2.2: 1268 - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} 2445 + get-intrinsic@1.3.0: 1269 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 1270 2451 function-bind: 1.1.2 1271 - has-proto: 1.0.1 1272 - has-symbols: 1.0.3 1273 - hasown: 2.0.0 1274 - dev: true 1275 - 1276 - /get-stream@6.0.1: 1277 - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} 1278 - engines: {node: '>=10'} 1279 - dev: true 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 1280 2457 1281 - /get-symbol-description@1.0.0: 1282 - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} 1283 - engines: {node: '>= 0.4'} 2458 + get-proto@1.0.1: 1284 2459 dependencies: 1285 - call-bind: 1.0.5 1286 - get-intrinsic: 1.2.2 1287 - dev: true 2460 + dunder-proto: 1.0.1 2461 + es-object-atoms: 1.1.1 1288 2462 1289 - /glob-parent@5.1.2: 1290 - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 1291 - engines: {node: '>= 6'} 2463 + get-symbol-description@1.1.0: 1292 2464 dependencies: 1293 - is-glob: 4.0.3 1294 - dev: true 2465 + call-bound: 1.0.4 2466 + es-errors: 1.3.0 2467 + get-intrinsic: 1.3.0 1295 2468 1296 - /glob-parent@6.0.2: 1297 - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 1298 - engines: {node: '>=10.13.0'} 2469 + glob-parent@5.1.2: 1299 2470 dependencies: 1300 2471 is-glob: 4.0.3 1301 - dev: true 1302 2472 1303 - /glob@7.2.3: 1304 - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 2473 + glob-parent@6.0.2: 1305 2474 dependencies: 1306 - fs.realpath: 1.0.0 1307 - inflight: 1.0.6 1308 - inherits: 2.0.4 1309 - minimatch: 3.1.2 1310 - once: 1.4.0 1311 - path-is-absolute: 1.0.1 2475 + is-glob: 4.0.3 1312 2476 1313 - /globals@13.23.0: 1314 - resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} 1315 - engines: {node: '>=8'} 1316 - dependencies: 1317 - type-fest: 0.20.2 1318 - dev: true 2477 + globals@14.0.0: {} 1319 2478 1320 - /globalthis@1.0.3: 1321 - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} 1322 - engines: {node: '>= 0.4'} 2479 + globalthis@1.0.4: 1323 2480 dependencies: 1324 2481 define-properties: 1.2.1 1325 - dev: true 2482 + gopd: 1.2.0 1326 2483 1327 - /globby@11.1.0: 1328 - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 1329 - engines: {node: '>=10'} 1330 - dependencies: 1331 - array-union: 2.1.0 1332 - dir-glob: 3.0.1 1333 - fast-glob: 3.3.2 1334 - ignore: 5.3.0 1335 - merge2: 1.4.1 1336 - slash: 3.0.0 1337 - dev: true 2484 + gopd@1.2.0: {} 1338 2485 1339 - /gopd@1.0.1: 1340 - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} 1341 - dependencies: 1342 - get-intrinsic: 1.2.2 1343 - dev: true 2486 + graphemer@1.4.0: {} 1344 2487 1345 - /graphemer@1.4.0: 1346 - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 1347 - dev: true 2488 + has-bigints@1.1.0: {} 1348 2489 1349 - /has-bigints@1.0.2: 1350 - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} 1351 - dev: true 2490 + has-flag@4.0.0: {} 1352 2491 1353 - /has-flag@4.0.0: 1354 - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 1355 - engines: {node: '>=8'} 1356 - dev: true 2492 + has-property-descriptors@1.0.2: 2493 + dependencies: 2494 + es-define-property: 1.0.1 1357 2495 1358 - /has-property-descriptors@1.0.1: 1359 - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} 2496 + has-proto@1.2.0: 1360 2497 dependencies: 1361 - get-intrinsic: 1.2.2 1362 - dev: true 2498 + dunder-proto: 1.0.1 1363 2499 1364 - /has-proto@1.0.1: 1365 - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} 1366 - engines: {node: '>= 0.4'} 1367 - dev: true 2500 + has-symbols@1.1.0: {} 1368 2501 1369 - /has-symbols@1.0.3: 1370 - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 1371 - engines: {node: '>= 0.4'} 1372 - dev: true 1373 - 1374 - /has-tostringtag@1.0.0: 1375 - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} 1376 - engines: {node: '>= 0.4'} 2502 + has-tostringtag@1.0.2: 1377 2503 dependencies: 1378 - has-symbols: 1.0.3 1379 - dev: true 2504 + has-symbols: 1.1.0 1380 2505 1381 - /hasown@2.0.0: 1382 - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} 1383 - engines: {node: '>= 0.4'} 2506 + hasown@2.0.2: 1384 2507 dependencies: 1385 2508 function-bind: 1.1.2 1386 - dev: true 1387 2509 1388 - /human-signals@2.1.0: 1389 - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} 1390 - engines: {node: '>=10.17.0'} 1391 - dev: true 2510 + husky@8.0.3: {} 1392 2511 1393 - /human-signals@4.3.1: 1394 - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} 1395 - engines: {node: '>=14.18.0'} 1396 - dev: true 1397 - 1398 - /husky@8.0.3: 1399 - resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} 1400 - engines: {node: '>=14'} 1401 - hasBin: true 1402 - dev: true 2512 + ieee754@1.2.1: {} 1403 2513 1404 - /ignore@5.3.0: 1405 - resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} 1406 - engines: {node: '>= 4'} 1407 - dev: true 2514 + ignore@5.3.2: {} 1408 2515 1409 - /import-fresh@3.3.0: 1410 - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 1411 - engines: {node: '>=6'} 2516 + import-fresh@3.3.0: 1412 2517 dependencies: 1413 2518 parent-module: 1.0.1 1414 2519 resolve-from: 4.0.0 1415 - dev: true 1416 2520 1417 - /imurmurhash@0.1.4: 1418 - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 1419 - engines: {node: '>=0.8.19'} 1420 - dev: true 2521 + imurmurhash@0.1.4: {} 1421 2522 1422 - /inflight@1.0.6: 1423 - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 2523 + internal-slot@1.1.0: 1424 2524 dependencies: 1425 - once: 1.4.0 1426 - wrappy: 1.0.2 2525 + es-errors: 1.3.0 2526 + hasown: 2.0.2 2527 + side-channel: 1.1.0 1427 2528 1428 - /inherits@2.0.4: 1429 - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 2529 + is-array-buffer@3.0.5: 2530 + dependencies: 2531 + call-bind: 1.0.8 2532 + call-bound: 1.0.4 2533 + get-intrinsic: 1.3.0 1430 2534 1431 - /internal-slot@1.0.6: 1432 - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} 1433 - engines: {node: '>= 0.4'} 2535 + is-async-function@2.1.1: 1434 2536 dependencies: 1435 - get-intrinsic: 1.2.2 1436 - hasown: 2.0.0 1437 - side-channel: 1.0.4 1438 - dev: true 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 1439 2542 1440 - /is-array-buffer@3.0.2: 1441 - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} 2543 + is-bigint@1.1.0: 1442 2544 dependencies: 1443 - call-bind: 1.0.5 1444 - get-intrinsic: 1.2.2 1445 - is-typed-array: 1.1.12 1446 - dev: true 2545 + has-bigints: 1.1.0 1447 2546 1448 - /is-async-function@2.0.0: 1449 - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} 1450 - engines: {node: '>= 0.4'} 2547 + is-boolean-object@1.2.2: 1451 2548 dependencies: 1452 - has-tostringtag: 1.0.0 1453 - dev: true 2549 + call-bound: 1.0.4 2550 + has-tostringtag: 1.0.2 1454 2551 1455 - /is-bigint@1.0.4: 1456 - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} 1457 - dependencies: 1458 - has-bigints: 1.0.2 1459 - dev: true 2552 + is-callable@1.2.7: {} 1460 2553 1461 - /is-boolean-object@1.1.2: 1462 - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} 1463 - engines: {node: '>= 0.4'} 2554 + is-core-module@2.16.1: 1464 2555 dependencies: 1465 - call-bind: 1.0.5 1466 - has-tostringtag: 1.0.0 1467 - dev: true 1468 - 1469 - /is-callable@1.2.7: 1470 - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 1471 - engines: {node: '>= 0.4'} 1472 - dev: true 2556 + hasown: 2.0.2 1473 2557 1474 - /is-core-module@2.13.1: 1475 - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} 2558 + is-data-view@1.0.2: 1476 2559 dependencies: 1477 - hasown: 2.0.0 1478 - dev: true 2560 + call-bound: 1.0.4 2561 + get-intrinsic: 1.3.0 2562 + is-typed-array: 1.1.15 1479 2563 1480 - /is-date-object@1.0.5: 1481 - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} 1482 - engines: {node: '>= 0.4'} 2564 + is-date-object@1.1.0: 1483 2565 dependencies: 1484 - has-tostringtag: 1.0.0 1485 - dev: true 1486 - 1487 - /is-docker@2.2.1: 1488 - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} 1489 - engines: {node: '>=8'} 1490 - hasBin: true 1491 - dev: true 2566 + call-bound: 1.0.4 2567 + has-tostringtag: 1.0.2 1492 2568 1493 - /is-docker@3.0.0: 1494 - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} 1495 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1496 - hasBin: true 1497 - dev: true 2569 + is-extglob@2.1.1: {} 1498 2570 1499 - /is-extglob@2.1.1: 1500 - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1501 - engines: {node: '>=0.10.0'} 1502 - dev: true 1503 - 1504 - /is-finalizationregistry@1.0.2: 1505 - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} 2571 + is-finalizationregistry@1.1.1: 1506 2572 dependencies: 1507 - call-bind: 1.0.5 1508 - dev: true 2573 + call-bound: 1.0.4 1509 2574 1510 - /is-generator-function@1.0.10: 1511 - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} 1512 - engines: {node: '>= 0.4'} 2575 + is-generator-function@1.1.0: 1513 2576 dependencies: 1514 - has-tostringtag: 1.0.0 1515 - dev: true 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 1516 2581 1517 - /is-glob@4.0.3: 1518 - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1519 - engines: {node: '>=0.10.0'} 2582 + is-glob@4.0.3: 1520 2583 dependencies: 1521 2584 is-extglob: 2.1.1 1522 - dev: true 1523 2585 1524 - /is-inside-container@1.0.0: 1525 - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} 1526 - engines: {node: '>=14.16'} 1527 - hasBin: true 1528 - dependencies: 1529 - is-docker: 3.0.0 1530 - dev: true 1531 - 1532 - /is-map@2.0.2: 1533 - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} 1534 - dev: true 1535 - 1536 - /is-negative-zero@2.0.2: 1537 - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} 1538 - engines: {node: '>= 0.4'} 1539 - dev: true 2586 + is-map@2.0.3: {} 1540 2587 1541 - /is-number-object@1.0.7: 1542 - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} 1543 - engines: {node: '>= 0.4'} 2588 + is-number-object@1.1.1: 1544 2589 dependencies: 1545 - has-tostringtag: 1.0.0 1546 - dev: true 1547 - 1548 - /is-number@7.0.0: 1549 - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 1550 - engines: {node: '>=0.12.0'} 1551 - dev: true 2590 + call-bound: 1.0.4 2591 + has-tostringtag: 1.0.2 1552 2592 1553 - /is-path-inside@3.0.3: 1554 - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 1555 - engines: {node: '>=8'} 1556 - dev: true 2593 + is-number@7.0.0: {} 1557 2594 1558 - /is-regex@1.1.4: 1559 - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} 1560 - engines: {node: '>= 0.4'} 2595 + is-regex@1.2.1: 1561 2596 dependencies: 1562 - call-bind: 1.0.5 1563 - has-tostringtag: 1.0.0 1564 - dev: true 2597 + call-bound: 1.0.4 2598 + gopd: 1.2.0 2599 + has-tostringtag: 1.0.2 2600 + hasown: 2.0.2 1565 2601 1566 - /is-set@2.0.2: 1567 - resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} 1568 - dev: true 2602 + is-set@2.0.3: {} 1569 2603 1570 - /is-shared-array-buffer@1.0.2: 1571 - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} 2604 + is-shared-array-buffer@1.0.4: 1572 2605 dependencies: 1573 - call-bind: 1.0.5 1574 - dev: true 1575 - 1576 - /is-stream@2.0.1: 1577 - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 1578 - engines: {node: '>=8'} 1579 - dev: true 1580 - 1581 - /is-stream@3.0.0: 1582 - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} 1583 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1584 - dev: true 2606 + call-bound: 1.0.4 1585 2607 1586 - /is-string@1.0.7: 1587 - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} 1588 - engines: {node: '>= 0.4'} 2608 + is-string@1.1.1: 1589 2609 dependencies: 1590 - has-tostringtag: 1.0.0 1591 - dev: true 2610 + call-bound: 1.0.4 2611 + has-tostringtag: 1.0.2 1592 2612 1593 - /is-symbol@1.0.4: 1594 - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} 1595 - engines: {node: '>= 0.4'} 2613 + is-symbol@1.1.1: 1596 2614 dependencies: 1597 - has-symbols: 1.0.3 1598 - dev: true 2615 + call-bound: 1.0.4 2616 + has-symbols: 1.1.0 2617 + safe-regex-test: 1.1.0 1599 2618 1600 - /is-typed-array@1.1.12: 1601 - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} 1602 - engines: {node: '>= 0.4'} 2619 + is-typed-array@1.1.15: 1603 2620 dependencies: 1604 - which-typed-array: 1.1.13 1605 - dev: true 2621 + which-typed-array: 1.1.19 1606 2622 1607 - /is-weakmap@2.0.1: 1608 - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} 1609 - dev: true 2623 + is-weakmap@2.0.2: {} 1610 2624 1611 - /is-weakref@1.0.2: 1612 - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} 2625 + is-weakref@1.1.1: 1613 2626 dependencies: 1614 - call-bind: 1.0.5 1615 - dev: true 2627 + call-bound: 1.0.4 1616 2628 1617 - /is-weakset@2.0.2: 1618 - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} 2629 + is-weakset@2.0.4: 1619 2630 dependencies: 1620 - call-bind: 1.0.5 1621 - get-intrinsic: 1.2.2 1622 - dev: true 2631 + call-bound: 1.0.4 2632 + get-intrinsic: 1.3.0 1623 2633 1624 - /is-wsl@2.2.0: 1625 - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} 1626 - engines: {node: '>=8'} 1627 - dependencies: 1628 - is-docker: 2.2.1 1629 - dev: true 2634 + isarray@2.0.5: {} 1630 2635 1631 - /isarray@2.0.5: 1632 - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} 1633 - dev: true 2636 + isexe@2.0.0: {} 1634 2637 1635 - /isexe@2.0.0: 1636 - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1637 - dev: true 1638 - 1639 - /iterator.prototype@1.1.2: 1640 - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} 2638 + iterator.prototype@1.1.5: 1641 2639 dependencies: 1642 - define-properties: 1.2.1 1643 - get-intrinsic: 1.2.2 1644 - has-symbols: 1.0.3 1645 - reflect.getprototypeof: 1.0.4 1646 - set-function-name: 2.0.1 1647 - dev: true 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 1648 2646 1649 - /js-tokens@4.0.0: 1650 - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 1651 - dev: true 2647 + jiti@2.4.2: {} 1652 2648 1653 - /js-yaml@4.1.0: 1654 - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 1655 - hasBin: true 2649 + js-tokens@4.0.0: {} 2650 + 2651 + js-yaml@4.1.0: 1656 2652 dependencies: 1657 2653 argparse: 2.0.1 1658 - dev: true 1659 2654 1660 - /json-buffer@3.0.1: 1661 - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 1662 - dev: true 2655 + json-buffer@3.0.1: {} 1663 2656 1664 - /json-schema-traverse@0.4.1: 1665 - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1666 - dev: true 2657 + json-schema-traverse@0.4.1: {} 1667 2658 1668 - /json-stable-stringify-without-jsonify@1.0.1: 1669 - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 1670 - dev: true 2659 + json-stable-stringify-without-jsonify@1.0.1: {} 1671 2660 1672 - /jsx-ast-utils@3.3.5: 1673 - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} 1674 - engines: {node: '>=4.0'} 2661 + jsx-ast-utils@3.3.5: 1675 2662 dependencies: 1676 - array-includes: 3.1.7 1677 - array.prototype.flat: 1.3.2 1678 - object.assign: 4.1.5 1679 - object.values: 1.1.7 1680 - dev: true 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 1681 2667 1682 - /keyv@4.5.4: 1683 - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 2668 + keyv@4.5.4: 1684 2669 dependencies: 1685 2670 json-buffer: 3.0.1 1686 - dev: true 1687 2671 1688 - /levn@0.4.1: 1689 - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 1690 - engines: {node: '>= 0.8.0'} 2672 + levn@0.4.1: 1691 2673 dependencies: 1692 2674 prelude-ls: 1.2.1 1693 2675 type-check: 0.4.0 1694 - dev: true 1695 2676 1696 - /locate-path@6.0.0: 1697 - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 1698 - engines: {node: '>=10'} 2677 + locate-path@6.0.0: 1699 2678 dependencies: 1700 2679 p-locate: 5.0.0 1701 - dev: true 1702 2680 1703 - /lodash.merge@4.6.2: 1704 - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1705 - dev: true 2681 + lodash.merge@4.6.2: {} 1706 2682 1707 - /loose-envify@1.4.0: 1708 - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 1709 - hasBin: true 2683 + loose-envify@1.4.0: 1710 2684 dependencies: 1711 2685 js-tokens: 4.0.0 1712 - dev: true 1713 2686 1714 - /lru-cache@6.0.0: 1715 - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 1716 - engines: {node: '>=10'} 1717 - dependencies: 1718 - yallist: 4.0.0 1719 - dev: true 2687 + math-intrinsics@1.1.0: {} 1720 2688 1721 - /merge-stream@2.0.0: 1722 - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 1723 - dev: true 2689 + merge2@1.4.1: {} 1724 2690 1725 - /merge2@1.4.1: 1726 - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 1727 - engines: {node: '>= 8'} 1728 - dev: true 2691 + meriyah@6.0.1: {} 2692 + 2693 + microdiff@1.5.0: {} 1729 2694 1730 - /micromatch@4.0.5: 1731 - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 1732 - engines: {node: '>=8.6'} 2695 + micromatch@4.0.8: 1733 2696 dependencies: 1734 - braces: 3.0.2 2697 + braces: 3.0.3 1735 2698 picomatch: 2.3.1 1736 - dev: true 1737 2699 1738 - /mimic-fn@2.1.0: 1739 - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 1740 - engines: {node: '>=6'} 1741 - dev: true 2700 + mimic-function@5.0.1: {} 1742 2701 1743 - /mimic-fn@4.0.0: 1744 - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} 1745 - engines: {node: '>=12'} 1746 - dev: true 2702 + minimatch@3.1.2: 2703 + dependencies: 2704 + brace-expansion: 1.1.11 1747 2705 1748 - /minimatch@3.1.2: 1749 - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 2706 + minimatch@9.0.5: 1750 2707 dependencies: 1751 - brace-expansion: 1.1.11 2708 + brace-expansion: 2.0.1 1752 2709 1753 - /ms@2.1.2: 1754 - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1755 - dev: true 2710 + ms@2.1.3: {} 1756 2711 1757 - /natural-compare@1.4.0: 1758 - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1759 - dev: true 2712 + nanotar@0.1.1: {} 1760 2713 1761 - /npm-run-path@4.0.1: 1762 - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 1763 - engines: {node: '>=8'} 1764 - dependencies: 1765 - path-key: 3.1.1 1766 - dev: true 2714 + natural-compare@1.4.0: {} 1767 2715 1768 - /npm-run-path@5.1.0: 1769 - resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} 1770 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1771 - dependencies: 1772 - path-key: 4.0.0 1773 - dev: true 2716 + node-fetch-native@1.6.6: {} 1774 2717 1775 - /object-assign@4.1.1: 1776 - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 1777 - engines: {node: '>=0.10.0'} 1778 - dev: true 2718 + object-assign@4.1.1: {} 1779 2719 1780 - /object-inspect@1.13.1: 1781 - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} 1782 - dev: true 2720 + object-inspect@1.13.4: {} 1783 2721 1784 - /object-keys@1.1.1: 1785 - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 1786 - engines: {node: '>= 0.4'} 1787 - dev: true 2722 + object-keys@1.1.1: {} 1788 2723 1789 - /object.assign@4.1.5: 1790 - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} 1791 - engines: {node: '>= 0.4'} 2724 + object.assign@4.1.7: 1792 2725 dependencies: 1793 - call-bind: 1.0.5 2726 + call-bind: 1.0.8 2727 + call-bound: 1.0.4 1794 2728 define-properties: 1.2.1 1795 - has-symbols: 1.0.3 2729 + es-object-atoms: 1.1.1 2730 + has-symbols: 1.1.0 1796 2731 object-keys: 1.1.1 1797 - dev: true 1798 2732 1799 - /object.entries@1.1.7: 1800 - resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} 1801 - engines: {node: '>= 0.4'} 2733 + object.entries@1.1.9: 1802 2734 dependencies: 1803 - call-bind: 1.0.5 2735 + call-bind: 1.0.8 2736 + call-bound: 1.0.4 1804 2737 define-properties: 1.2.1 1805 - es-abstract: 1.22.3 1806 - dev: true 2738 + es-object-atoms: 1.1.1 1807 2739 1808 - /object.fromentries@2.0.7: 1809 - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} 1810 - engines: {node: '>= 0.4'} 2740 + object.fromentries@2.0.8: 1811 2741 dependencies: 1812 - call-bind: 1.0.5 2742 + call-bind: 1.0.8 1813 2743 define-properties: 1.2.1 1814 - es-abstract: 1.22.3 1815 - dev: true 2744 + es-abstract: 1.23.9 2745 + es-object-atoms: 1.1.1 1816 2746 1817 - /object.hasown@1.1.3: 1818 - resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} 2747 + object.values@1.2.1: 1819 2748 dependencies: 1820 - define-properties: 1.2.1 1821 - es-abstract: 1.22.3 1822 - dev: true 1823 - 1824 - /object.values@1.1.7: 1825 - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} 1826 - engines: {node: '>= 0.4'} 1827 - dependencies: 1828 - call-bind: 1.0.5 2749 + call-bind: 1.0.8 2750 + call-bound: 1.0.4 1829 2751 define-properties: 1.2.1 1830 - es-abstract: 1.22.3 1831 - dev: true 2752 + es-object-atoms: 1.1.1 1832 2753 1833 - /once@1.4.0: 1834 - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 2754 + ofetch@1.4.1: 1835 2755 dependencies: 1836 - wrappy: 1.0.2 2756 + destr: 2.0.4 2757 + node-fetch-native: 1.6.6 2758 + ufo: 1.5.4 1837 2759 1838 - /onetime@5.1.2: 1839 - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 1840 - engines: {node: '>=6'} 2760 + onetime@7.0.0: 1841 2761 dependencies: 1842 - mimic-fn: 2.1.0 1843 - dev: true 2762 + mimic-function: 5.0.1 1844 2763 1845 - /onetime@6.0.0: 1846 - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} 1847 - engines: {node: '>=12'} 1848 - dependencies: 1849 - mimic-fn: 4.0.0 1850 - dev: true 1851 - 1852 - /open@9.1.0: 1853 - resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} 1854 - engines: {node: '>=14.16'} 1855 - dependencies: 1856 - default-browser: 4.0.0 1857 - define-lazy-prop: 3.0.0 1858 - is-inside-container: 1.0.0 1859 - is-wsl: 2.2.0 1860 - dev: true 1861 - 1862 - /optionator@0.9.3: 1863 - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1864 - engines: {node: '>= 0.8.0'} 2764 + optionator@0.9.3: 1865 2765 dependencies: 1866 2766 '@aashutoshrathi/word-wrap': 1.2.6 1867 2767 deep-is: 0.1.4 ··· 1869 2769 levn: 0.4.1 1870 2770 prelude-ls: 1.2.1 1871 2771 type-check: 0.4.0 1872 - dev: true 1873 2772 1874 - /p-limit@3.1.0: 1875 - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1876 - engines: {node: '>=10'} 2773 + own-keys@1.0.1: 2774 + dependencies: 2775 + get-intrinsic: 1.3.0 2776 + object-keys: 1.1.1 2777 + safe-push-apply: 1.0.0 2778 + 2779 + p-limit@3.1.0: 1877 2780 dependencies: 1878 2781 yocto-queue: 0.1.0 1879 - dev: true 1880 2782 1881 - /p-locate@5.0.0: 1882 - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1883 - engines: {node: '>=10'} 2783 + p-locate@5.0.0: 1884 2784 dependencies: 1885 2785 p-limit: 3.1.0 1886 - dev: true 1887 2786 1888 - /parent-module@1.0.1: 1889 - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1890 - engines: {node: '>=6'} 2787 + package-manager-detector@1.1.0: {} 2788 + 2789 + parent-module@1.0.1: 1891 2790 dependencies: 1892 2791 callsites: 3.1.0 1893 - dev: true 1894 2792 1895 - /path-exists@4.0.0: 1896 - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1897 - engines: {node: '>=8'} 1898 - dev: true 2793 + path-exists@4.0.0: {} 1899 2794 1900 - /path-is-absolute@1.0.1: 1901 - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1902 - engines: {node: '>=0.10.0'} 2795 + path-key@3.1.1: {} 1903 2796 1904 - /path-key@3.1.1: 1905 - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1906 - engines: {node: '>=8'} 1907 - dev: true 2797 + path-parse@1.0.7: {} 1908 2798 1909 - /path-key@4.0.0: 1910 - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} 1911 - engines: {node: '>=12'} 1912 - dev: true 2799 + pathe@2.0.3: {} 1913 2800 1914 - /path-parse@1.0.7: 1915 - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1916 - dev: true 2801 + picomatch@2.3.1: {} 1917 2802 1918 - /path-type@4.0.0: 1919 - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1920 - engines: {node: '>=8'} 1921 - dev: true 2803 + picomatch@4.0.2: {} 1922 2804 1923 - /picocolors@1.0.0: 1924 - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 1925 - dev: true 2805 + pnpm-workspace-yaml@0.3.1: 2806 + dependencies: 2807 + yaml: 2.7.1 1926 2808 1927 - /picomatch@2.3.1: 1928 - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1929 - engines: {node: '>=8.6'} 1930 - dev: true 2809 + possible-typed-array-names@1.1.0: {} 1931 2810 1932 - /prelude-ls@1.2.1: 1933 - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1934 - engines: {node: '>= 0.8.0'} 1935 - dev: true 2811 + prelude-ls@1.2.1: {} 1936 2812 1937 - /prettier-linter-helpers@1.0.0: 1938 - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} 1939 - engines: {node: '>=6.0.0'} 2813 + prettier-linter-helpers@1.0.0: 1940 2814 dependencies: 1941 2815 fast-diff: 1.3.0 1942 - dev: true 1943 2816 1944 - /prettier@3.1.0: 1945 - resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==} 1946 - engines: {node: '>=14'} 1947 - hasBin: true 1948 - dev: true 2817 + prettier@3.1.0: {} 2818 + 2819 + process@0.11.10: {} 1949 2820 1950 - /prop-types@15.8.1: 1951 - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} 2821 + prop-types@15.8.1: 1952 2822 dependencies: 1953 2823 loose-envify: 1.4.0 1954 2824 object-assign: 4.1.1 1955 2825 react-is: 16.13.1 1956 - dev: true 1957 2826 1958 - /punycode@2.3.1: 1959 - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1960 - engines: {node: '>=6'} 1961 - dev: true 2827 + punycode@2.3.1: {} 1962 2828 1963 - /queue-microtask@1.2.3: 1964 - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1965 - dev: true 2829 + quansync@0.2.10: {} 1966 2830 1967 - /react-is@16.13.1: 1968 - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} 1969 - dev: true 2831 + queue-microtask@1.2.3: {} 1970 2832 1971 - /reflect.getprototypeof@1.0.4: 1972 - resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} 1973 - engines: {node: '>= 0.4'} 2833 + react-is@16.13.1: {} 2834 + 2835 + readable-stream@4.5.2: 2836 + dependencies: 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: 1974 2844 dependencies: 1975 - call-bind: 1.0.5 2845 + call-bind: 1.0.8 1976 2846 define-properties: 1.2.1 1977 - es-abstract: 1.22.3 1978 - get-intrinsic: 1.2.2 1979 - globalthis: 1.0.3 1980 - which-builtin-type: 1.1.3 1981 - dev: true 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 1982 2853 1983 - /regexp.prototype.flags@1.5.1: 1984 - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} 1985 - engines: {node: '>= 0.4'} 2854 + regexp.prototype.flags@1.5.4: 1986 2855 dependencies: 1987 - call-bind: 1.0.5 2856 + call-bind: 1.0.8 1988 2857 define-properties: 1.2.1 1989 - set-function-name: 2.0.1 1990 - dev: true 2858 + es-errors: 1.3.0 2859 + get-proto: 1.0.1 2860 + gopd: 1.2.0 2861 + set-function-name: 2.0.2 1991 2862 1992 - /resolve-from@4.0.0: 1993 - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1994 - engines: {node: '>=4'} 1995 - dev: true 2863 + resolve-from@4.0.0: {} 1996 2864 1997 - /resolve@2.0.0-next.5: 1998 - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} 1999 - hasBin: true 2865 + resolve@2.0.0-next.5: 2000 2866 dependencies: 2001 - is-core-module: 2.13.1 2867 + is-core-module: 2.16.1 2002 2868 path-parse: 1.0.7 2003 2869 supports-preserve-symlinks-flag: 1.0.0 2004 - dev: true 2005 2870 2006 - /reusify@1.0.4: 2007 - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 2008 - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 2009 - dev: true 2010 - 2011 - /rimraf@3.0.2: 2012 - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 2013 - hasBin: true 2871 + restore-cursor@5.1.0: 2014 2872 dependencies: 2015 - glob: 7.2.3 2016 - dev: true 2873 + onetime: 7.0.0 2874 + signal-exit: 4.1.0 2017 2875 2018 - /run-applescript@5.0.0: 2019 - resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} 2020 - engines: {node: '>=12'} 2021 - dependencies: 2022 - execa: 5.1.1 2023 - dev: true 2876 + reusify@1.0.4: {} 2024 2877 2025 - /run-parallel@1.2.0: 2026 - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 2878 + run-parallel@1.2.0: 2027 2879 dependencies: 2028 2880 queue-microtask: 1.2.3 2029 - dev: true 2030 2881 2031 - /safe-array-concat@1.0.1: 2032 - resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} 2033 - engines: {node: '>=0.4'} 2882 + safe-array-concat@1.1.3: 2034 2883 dependencies: 2035 - call-bind: 1.0.5 2036 - get-intrinsic: 1.2.2 2037 - 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 2038 2888 isarray: 2.0.5 2039 - dev: true 2040 2889 2041 - /safe-regex-test@1.0.0: 2042 - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} 2890 + safe-buffer@5.2.1: {} 2891 + 2892 + safe-push-apply@1.0.0: 2043 2893 dependencies: 2044 - call-bind: 1.0.5 2045 - get-intrinsic: 1.2.2 2046 - is-regex: 1.1.4 2047 - dev: true 2894 + es-errors: 1.3.0 2895 + isarray: 2.0.5 2048 2896 2049 - /semver@6.3.1: 2050 - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 2051 - hasBin: true 2052 - dev: true 2897 + safe-regex-test@1.1.0: 2898 + dependencies: 2899 + call-bound: 1.0.4 2900 + es-errors: 1.3.0 2901 + is-regex: 1.2.1 2902 + 2903 + semver@6.3.1: {} 2904 + 2905 + semver@7.7.1: {} 2053 2906 2054 - /semver@7.5.4: 2055 - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 2056 - engines: {node: '>=10'} 2057 - hasBin: true 2907 + set-function-length@1.2.2: 2058 2908 dependencies: 2059 - lru-cache: 6.0.0 2060 - dev: true 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 2061 2915 2062 - /set-function-length@1.1.1: 2063 - resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} 2064 - engines: {node: '>= 0.4'} 2916 + set-function-name@2.0.2: 2065 2917 dependencies: 2066 - define-data-property: 1.1.1 2067 - get-intrinsic: 1.2.2 2068 - gopd: 1.0.1 2069 - has-property-descriptors: 1.0.1 2070 - dev: true 2918 + define-data-property: 1.1.4 2919 + es-errors: 1.3.0 2920 + functions-have-names: 1.2.3 2921 + has-property-descriptors: 1.0.2 2071 2922 2072 - /set-function-name@2.0.1: 2073 - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} 2074 - engines: {node: '>= 0.4'} 2923 + set-proto@1.0.0: 2075 2924 dependencies: 2076 - define-data-property: 1.1.1 2077 - functions-have-names: 1.2.3 2078 - has-property-descriptors: 1.0.1 2079 - dev: true 2925 + dunder-proto: 1.0.1 2926 + es-errors: 1.3.0 2927 + es-object-atoms: 1.1.1 2080 2928 2081 - /shebang-command@2.0.0: 2082 - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 2083 - engines: {node: '>=8'} 2929 + shebang-command@2.0.0: 2084 2930 dependencies: 2085 2931 shebang-regex: 3.0.0 2086 - dev: true 2087 2932 2088 - /shebang-regex@3.0.0: 2089 - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 2090 - engines: {node: '>=8'} 2091 - dev: true 2933 + shebang-regex@3.0.0: {} 2092 2934 2093 - /side-channel@1.0.4: 2094 - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} 2935 + side-channel-list@1.0.0: 2095 2936 dependencies: 2096 - call-bind: 1.0.5 2097 - get-intrinsic: 1.2.2 2098 - object-inspect: 1.13.1 2099 - dev: true 2937 + es-errors: 1.3.0 2938 + object-inspect: 1.13.4 2100 2939 2101 - /signal-exit@3.0.7: 2102 - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 2103 - dev: true 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 2104 2946 2105 - /slash@3.0.0: 2106 - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 2107 - engines: {node: '>=8'} 2108 - dev: true 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 2109 2954 2110 - /standalone-electron-types@1.0.0: 2111 - resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} 2955 + side-channel@1.1.0: 2956 + dependencies: 2957 + es-errors: 1.3.0 2958 + object-inspect: 1.13.4 2959 + side-channel-list: 1.0.0 2960 + side-channel-map: 1.0.1 2961 + side-channel-weakmap: 1.0.2 2962 + 2963 + signal-exit@4.1.0: {} 2964 + 2965 + standalone-electron-types@1.0.0: 2112 2966 dependencies: 2113 2967 '@types/node': 18.17.17 2114 - dev: false 2115 2968 2116 - /string.prototype.matchall@4.0.10: 2117 - resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} 2969 + string.prototype.matchall@4.0.12: 2118 2970 dependencies: 2119 - call-bind: 1.0.5 2971 + call-bind: 1.0.8 2972 + call-bound: 1.0.4 2120 2973 define-properties: 1.2.1 2121 - es-abstract: 1.22.3 2122 - get-intrinsic: 1.2.2 2123 - has-symbols: 1.0.3 2124 - internal-slot: 1.0.6 2125 - regexp.prototype.flags: 1.5.1 2126 - set-function-name: 2.0.1 2127 - side-channel: 1.0.4 2128 - dev: true 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 2129 2984 2130 - /string.prototype.trim@1.2.8: 2131 - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} 2132 - engines: {node: '>= 0.4'} 2985 + string.prototype.repeat@1.0.0: 2133 2986 dependencies: 2134 - call-bind: 1.0.5 2135 2987 define-properties: 1.2.1 2136 - es-abstract: 1.22.3 2137 - dev: true 2988 + es-abstract: 1.23.9 2138 2989 2139 - /string.prototype.trimend@1.0.7: 2140 - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} 2990 + string.prototype.trim@1.2.10: 2141 2991 dependencies: 2142 - call-bind: 1.0.5 2992 + call-bind: 1.0.8 2993 + call-bound: 1.0.4 2994 + define-data-property: 1.1.4 2143 2995 define-properties: 1.2.1 2144 - es-abstract: 1.22.3 2145 - dev: true 2996 + es-abstract: 1.23.9 2997 + es-object-atoms: 1.1.1 2998 + has-property-descriptors: 1.0.2 2146 2999 2147 - /string.prototype.trimstart@1.0.7: 2148 - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} 3000 + string.prototype.trimend@1.0.9: 2149 3001 dependencies: 2150 - call-bind: 1.0.5 3002 + call-bind: 1.0.8 3003 + call-bound: 1.0.4 2151 3004 define-properties: 1.2.1 2152 - es-abstract: 1.22.3 2153 - dev: true 3005 + es-object-atoms: 1.1.1 2154 3006 2155 - /strip-ansi@6.0.1: 2156 - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 2157 - engines: {node: '>=8'} 3007 + string.prototype.trimstart@1.0.8: 2158 3008 dependencies: 2159 - ansi-regex: 5.0.1 2160 - dev: true 3009 + call-bind: 1.0.8 3010 + define-properties: 1.2.1 3011 + es-object-atoms: 1.1.1 2161 3012 2162 - /strip-final-newline@2.0.0: 2163 - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 2164 - engines: {node: '>=6'} 2165 - dev: true 2166 - 2167 - /strip-final-newline@3.0.0: 2168 - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} 2169 - engines: {node: '>=12'} 2170 - dev: true 3013 + string_decoder@1.3.0: 3014 + dependencies: 3015 + safe-buffer: 5.2.1 2171 3016 2172 - /strip-json-comments@3.1.1: 2173 - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 2174 - engines: {node: '>=8'} 2175 - dev: true 3017 + strip-json-comments@3.1.1: {} 2176 3018 2177 - /supports-color@7.2.0: 2178 - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 2179 - engines: {node: '>=8'} 3019 + supports-color@7.2.0: 2180 3020 dependencies: 2181 3021 has-flag: 4.0.0 2182 - dev: true 2183 3022 2184 - /supports-preserve-symlinks-flag@1.0.0: 2185 - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 2186 - engines: {node: '>= 0.4'} 2187 - dev: true 3023 + supports-preserve-symlinks-flag@1.0.0: {} 2188 3024 2189 - /synckit@0.8.6: 2190 - resolution: {integrity: sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==} 2191 - engines: {node: ^14.18.0 || >=16.0.0} 3025 + synckit@0.11.1: 2192 3026 dependencies: 2193 - '@pkgr/utils': 2.4.2 2194 - tslib: 2.6.2 2195 - dev: true 3027 + '@pkgr/core': 0.2.0 3028 + tslib: 2.8.1 2196 3029 2197 - /text-table@0.2.0: 2198 - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 2199 - dev: true 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 2200 3044 2201 - /titleize@3.0.0: 2202 - resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} 2203 - engines: {node: '>=12'} 2204 - dev: true 3045 + tinyexec@1.0.1: {} 2205 3046 2206 - /to-regex-range@5.0.1: 2207 - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 2208 - engines: {node: '>=8.0'} 3047 + tinyglobby@0.2.12: 3048 + dependencies: 3049 + fdir: 6.4.3(picomatch@4.0.2) 3050 + picomatch: 4.0.2 3051 + 3052 + to-regex-range@5.0.1: 2209 3053 dependencies: 2210 3054 is-number: 7.0.0 2211 - dev: true 2212 3055 2213 - /ts-api-utils@1.0.3(typescript@5.3.2): 2214 - resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} 2215 - engines: {node: '>=16.13.0'} 2216 - peerDependencies: 2217 - typescript: '>=4.2.0' 3056 + ts-api-utils@2.1.0(typescript@5.8.2): 2218 3057 dependencies: 2219 - typescript: 5.3.2 2220 - dev: true 3058 + typescript: 5.8.2 2221 3059 2222 - /tslib@2.6.2: 2223 - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} 2224 - dev: true 3060 + tslib@2.8.1: {} 2225 3061 2226 - /type-check@0.4.0: 2227 - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 2228 - engines: {node: '>= 0.8.0'} 3062 + type-check@0.4.0: 2229 3063 dependencies: 2230 3064 prelude-ls: 1.2.1 2231 - dev: true 2232 3065 2233 - /type-fest@0.20.2: 2234 - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 2235 - engines: {node: '>=10'} 2236 - dev: true 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 2237 3071 2238 - /typed-array-buffer@1.0.0: 2239 - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} 2240 - engines: {node: '>= 0.4'} 3072 + typed-array-byte-length@1.0.3: 2241 3073 dependencies: 2242 - call-bind: 1.0.5 2243 - get-intrinsic: 1.2.2 2244 - is-typed-array: 1.1.12 2245 - dev: true 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 2246 3079 2247 - /typed-array-byte-length@1.0.0: 2248 - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} 2249 - engines: {node: '>= 0.4'} 3080 + typed-array-byte-offset@1.0.4: 2250 3081 dependencies: 2251 - call-bind: 1.0.5 2252 - for-each: 0.3.3 2253 - has-proto: 1.0.1 2254 - is-typed-array: 1.1.12 2255 - dev: true 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 2256 3089 2257 - /typed-array-byte-offset@1.0.0: 2258 - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} 2259 - engines: {node: '>= 0.4'} 3090 + typed-array-length@1.0.7: 2260 3091 dependencies: 2261 - available-typed-arrays: 1.0.5 2262 - call-bind: 1.0.5 2263 - for-each: 0.3.3 2264 - has-proto: 1.0.1 2265 - is-typed-array: 1.1.12 2266 - dev: true 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 2267 3098 2268 - /typed-array-length@1.0.4: 2269 - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} 3099 + typescript-eslint@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2): 2270 3100 dependencies: 2271 - call-bind: 1.0.5 2272 - for-each: 0.3.3 2273 - is-typed-array: 1.1.12 2274 - dev: true 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 2275 3108 2276 - /typescript@5.3.2: 2277 - resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==} 2278 - engines: {node: '>=14.17'} 2279 - hasBin: true 2280 - dev: true 3109 + typescript@5.8.2: {} 2281 3110 2282 - /unbox-primitive@1.0.2: 2283 - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} 3111 + ufo@1.5.4: {} 3112 + 3113 + unbox-primitive@1.1.0: 2284 3114 dependencies: 2285 - call-bind: 1.0.5 2286 - has-bigints: 1.0.2 2287 - has-symbols: 1.0.3 2288 - which-boxed-primitive: 1.0.2 2289 - dev: true 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 2290 3119 2291 - /untildify@4.0.0: 2292 - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} 2293 - engines: {node: '>=8'} 2294 - dev: true 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 2295 3126 2296 - /uri-js@4.4.1: 2297 - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 3127 + undici-types@6.20.0: {} 3128 + 3129 + undici-types@6.21.0: {} 3130 + 3131 + uri-js@4.4.1: 2298 3132 dependencies: 2299 3133 punycode: 2.3.1 2300 - dev: true 2301 3134 2302 - /which-boxed-primitive@1.0.2: 2303 - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} 3135 + utilium@1.10.1: 2304 3136 dependencies: 2305 - is-bigint: 1.0.4 2306 - is-boolean-object: 1.1.2 2307 - is-number-object: 1.0.7 2308 - is-string: 1.0.7 2309 - is-symbol: 1.0.4 2310 - dev: true 3137 + eventemitter3: 5.0.1 3138 + optionalDependencies: 3139 + '@xterm/xterm': 5.5.0 2311 3140 2312 - /which-builtin-type@1.1.3: 2313 - resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} 2314 - engines: {node: '>= 0.4'} 3141 + which-boxed-primitive@1.1.1: 2315 3142 dependencies: 2316 - function.prototype.name: 1.1.6 2317 - has-tostringtag: 1.0.0 2318 - is-async-function: 2.0.0 2319 - is-date-object: 1.0.5 2320 - is-finalizationregistry: 1.0.2 2321 - is-generator-function: 1.0.10 2322 - is-regex: 1.1.4 2323 - 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 2324 3160 isarray: 2.0.5 2325 - which-boxed-primitive: 1.0.2 2326 - which-collection: 1.0.1 2327 - which-typed-array: 1.1.13 2328 - dev: true 3161 + which-boxed-primitive: 1.1.1 3162 + which-collection: 1.0.2 3163 + which-typed-array: 1.1.19 2329 3164 2330 - /which-collection@1.0.1: 2331 - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} 3165 + which-collection@1.0.2: 2332 3166 dependencies: 2333 - is-map: 2.0.2 2334 - is-set: 2.0.2 2335 - is-weakmap: 2.0.1 2336 - is-weakset: 2.0.2 2337 - dev: true 3167 + is-map: 2.0.3 3168 + is-set: 2.0.3 3169 + is-weakmap: 2.0.2 3170 + is-weakset: 2.0.4 2338 3171 2339 - /which-typed-array@1.1.13: 2340 - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} 2341 - engines: {node: '>= 0.4'} 3172 + which-typed-array@1.1.19: 2342 3173 dependencies: 2343 - available-typed-arrays: 1.0.5 2344 - call-bind: 1.0.5 2345 - for-each: 0.3.3 2346 - gopd: 1.0.1 2347 - has-tostringtag: 1.0.0 2348 - dev: true 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 2349 3181 2350 - /which@2.0.2: 2351 - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 2352 - engines: {node: '>= 8'} 2353 - hasBin: true 3182 + which@2.0.2: 2354 3183 dependencies: 2355 3184 isexe: 2.0.0 2356 - dev: true 2357 3185 2358 - /wrappy@1.0.2: 2359 - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 3186 + yaml@2.7.1: {} 2360 3187 2361 - /yallist@4.0.0: 2362 - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 2363 - dev: true 3188 + yocto-queue@0.1.0: {} 2364 3189 2365 - /yocto-queue@0.1.0: 2366 - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 2367 - engines: {node: '>=10'} 2368 - dev: true 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 -14
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 - "skipLibCheck": true, 9 - "moduleResolution": "bundler", 10 4 "baseUrl": "./packages/", 11 - "jsx": "react", 12 - "noEmit": true, 13 - 14 - // disable unreachable code detection because it breaks with esbuild labels 15 - "allowUnreachableCode": true 5 + "noEmit": true 16 6 }, 17 - "include": ["./packages/**/*", "./env.d.ts"], 18 - "exclude": ["node_modules"] 7 + "exclude": [ 8 + "**/node_modules/**", 9 + "**/dist/**", 10 + "**/build/**" 11 + ] 19 12 }