this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+13435 -5420
.github
.husky
img
nix
packages
browser
core
core-extensions
injector
node-preload
types
web-preload
scripts
-53
.eslintrc.json
···
-
{
-
"extends": [
-
"eslint:recommended",
-
"plugin:@typescript-eslint/recommended",
-
"plugin:prettier/recommended",
-
"plugin:react/recommended"
-
],
-
"plugins": ["@typescript-eslint", "prettier", "react"],
-
"parser": "@typescript-eslint/parser",
-
"env": {
-
"browser": true,
-
"node": true
-
},
-
"parserOptions": {
-
"ecmaFeatures": {
-
"jsx": true
-
},
-
"ecmaVersion": "latest",
-
"sourceType": "module"
-
},
-
"rules": {
-
"indent": "off",
-
"eqeqeq": ["error", "always", {
-
"null": "ignore"
-
}],
-
"quotes": [
-
"error",
-
"double",
-
{ "avoidEscape": true, "allowTemplateLiterals": true }
-
],
-
"@typescript-eslint/no-unused-vars": [
-
"error",
-
{ "args": "none", "varsIgnorePattern": "^_" }
-
],
-
// Mostly so we don't forget to leave these in when committing
-
"no-console": "error",
-
"no-debugger": "error",
-
-
// Quite honestly we're interacting with so much unknown within Discord that
-
// this being enabled is a hinderance
-
"@typescript-eslint/no-explicit-any": "off",
-
-
"@typescript-eslint/no-var-requires": "off",
-
-
// https://canary.discord.com/channels/1154257010532032512/1154275441788583996/1181760413231230976
-
"no-unused-labels": "off"
-
},
-
"settings": {
-
"react": {
-
"version": "18.2"
-
}
-
}
-
}
+41
.github/workflows/browser.yml
···
+
name: Browser extension builds
+
+
on:
+
push:
+
branches:
+
- develop
+
+
jobs:
+
browser:
+
name: Browser extension builds
+
runs-on: ubuntu-latest
+
steps:
+
- uses: actions/checkout@v4
+
- uses: pnpm/action-setup@v4
+
- uses: actions/setup-node@v4
+
with:
+
node-version: 22
+
cache: pnpm
+
+
- name: Install dependencies
+
run: pnpm install --frozen-lockfile
+
- name: Build moonlight
+
env:
+
NODE_ENV: production
+
run: pnpm run build
+
+
- name: Build MV3
+
run: pnpm run browser
+
- name: Build MV2
+
run: pnpm run browser-mv2
+
+
- name: Upload MV3
+
uses: actions/upload-artifact@v4
+
with:
+
name: browser
+
path: ./dist/browser
+
- name: Upload MV2
+
uses: actions/upload-artifact@v4
+
with:
+
name: browser-mv2
+
path: ./dist/browser-mv2
+4 -10
.github/workflows/lint.yml
···
name: Lint commits
runs-on: ubuntu-latest
steps:
-
- uses: actions/checkout@v3
-
-
- uses: pnpm/action-setup@v2
-
with:
-
version: 8
-
run_install: false
-
- uses: actions/setup-node@v3
+
- uses: actions/checkout@v4
+
- uses: pnpm/action-setup@v4
+
- uses: actions/setup-node@v4
with:
-
node-version: 18
+
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run tsc
run: pnpm run typecheck
-
continue-on-error: true
- name: Run ESLint
run: pnpm run lint
-
continue-on-error: true
+9 -11
.github/workflows/nightly.yml
···
name: Nightly builds on GitHub Pages
runs-on: ubuntu-latest
steps:
-
- uses: actions/checkout@v3
-
-
- uses: pnpm/action-setup@v2
-
with:
-
version: 8
-
run_install: false
-
- uses: actions/setup-node@v3
+
- uses: actions/checkout@v4
+
- uses: pnpm/action-setup@v4
+
- uses: actions/setup-node@v4
with:
-
node-version: 18
+
node-version: 22
cache: pnpm
- name: Install dependencies
···
- name: Build moonlight
env:
NODE_ENV: production
+
MOONLIGHT_BRANCH: nightly
+
MOONLIGHT_VERSION: ${{ github.sha }}
run: pnpm run build
- name: Write ref/commit to file
···
echo "$(date +%s)" >> ./dist/ref
- name: Setup GitHub Pages
-
uses: actions/configure-pages@v3
+
uses: actions/configure-pages@v5
- name: Upload artifact
-
uses: actions/upload-pages-artifact@v1
+
uses: actions/upload-pages-artifact@v3
with:
path: ./dist
- name: Deploy to GitHub Pages
-
uses: actions/deploy-pages@v2
+
uses: actions/deploy-pages@v4
+16
.github/workflows/nix.yml
···
+
name: Check Nix flake
+
on: [push, pull_request]
+
+
permissions:
+
checks: write
+
+
jobs:
+
nix:
+
name: Check Nix flake
+
runs-on: ubuntu-latest
+
steps:
+
- uses: actions/checkout@v4
+
- uses: DeterminateSystems/nix-installer-action@main
+
+
- name: Build default flake output
+
run: nix build
+6 -8
.github/workflows/release.yml
···
name: Release builds to GitHub Releases
runs-on: ubuntu-latest
steps:
-
- uses: actions/checkout@v3
-
-
- uses: pnpm/action-setup@v2
-
with:
-
version: 8
-
run_install: false
-
- uses: actions/setup-node@v3
+
- uses: actions/checkout@v4
+
- uses: pnpm/action-setup@v4
+
- uses: actions/setup-node@v4
with:
-
node-version: 18
+
node-version: 22
cache: pnpm
- name: Install dependencies
···
- name: Build moonlight
env:
NODE_ENV: production
+
MOONLIGHT_BRANCH: stable
+
MOONLIGHT_VERSION: ${{ github.ref_name }}
run: pnpm run build
- name: Create archive
run: |
+32
.github/workflows/types.yml
···
+
name: Publish types on npm
+
on: workflow_dispatch
+
+
permissions:
+
contents: read
+
pages: write
+
id-token: write
+
+
jobs:
+
types:
+
name: Publish types on npm
+
runs-on: ubuntu-latest
+
steps:
+
- uses: actions/checkout@v4
+
- uses: pnpm/action-setup@v4
+
- uses: actions/setup-node@v4
+
with:
+
node-version: 22
+
cache: pnpm
+
registry-url: https://registry.npmjs.org
+
+
- name: Install dependencies
+
run: pnpm install --frozen-lockfile
+
- name: Build moonlight
+
env:
+
NODE_ENV: production
+
run: pnpm run build
+
+
- name: Publish types
+
run: pnpm publish --filter=./packages/types --access public --no-git-checks
+
env:
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+8
.gitignore
···
dist.tar.gz
.DS_Store
eslint_report.json
+
.eslintcache
+
# Nix
+
/result
+
*.drv
+
+
# IDEs
+
.vscode/
+
.idea/
+4
.husky/pre-commit
···
+
#!/usr/bin/env sh
+
. "$(dirname -- "$0")/_/husky.sh"
+
+
pnpm run check
+1
.prettierignore
···
+
pnpm-lock.yaml
+4 -4
.prettierrc
···
{
-
"printWidth": 80,
-
"trailingComma": "none",
-
"tabWidth": 2,
-
"singleQuote": false
+
"printWidth": 120,
+
"trailingComma": "none",
+
"tabWidth": 2,
+
"singleQuote": false
}
+4 -2
CHANGELOG.md
···
-
- Improved the Moonbase UI (@redstonekasi, @xyzeva)
-
- Renamed the List setting type to MultiSelect, added a new List type in its place
+
## Core
+
+
- Updated mappings
+
- Fixed using remapped paths as patch finds not working
+125 -621
LICENSE
···
-
GNU AFFERO GENERAL PUBLIC LICENSE
-
Version 3, 19 November 2007
+
GNU LESSER GENERAL PUBLIC LICENSE
+
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
-
Preamble
-
The GNU Affero General Public License is a free, copyleft license for
-
software and other kinds of works, specifically designed to ensure
-
cooperation with the community in the case of network server software.
-
-
The licenses for most software and other practical works are designed
-
to take away your freedom to share and change the works. By contrast,
-
our General Public Licenses are intended to guarantee your freedom to
-
share and change all versions of a program--to make sure it remains free
-
software for all its users.
-
-
When we speak of free software, we are referring to freedom, not
-
price. Our General Public Licenses are designed to make sure that you
-
have the freedom to distribute copies of free software (and charge for
-
them if you wish), that you receive source code or can get it if you
-
want it, that you can change the software or use pieces of it in new
-
free programs, and that you know you can do these things.
-
-
Developers that use our General Public Licenses protect your rights
-
with two steps: (1) assert copyright on the software, and (2) offer
-
you this License which gives you legal permission to copy, distribute
-
and/or modify the software.
-
-
A secondary benefit of defending all users' freedom is that
-
improvements made in alternate versions of the program, if they
-
receive widespread use, become available for other developers to
-
incorporate. Many developers of free software are heartened and
-
encouraged by the resulting cooperation. However, in the case of
-
software used on network servers, this result may fail to come about.
-
The GNU General Public License permits making a modified version and
-
letting the public access it on a server without ever releasing its
-
source code to the public.
-
-
The GNU Affero General Public License is designed specifically to
-
ensure that, in such cases, the modified source code becomes available
-
to the community. It requires the operator of a network server to
-
provide the source code of the modified version running there to the
-
users of that server. Therefore, public use of a modified version, on
-
a publicly accessible server, gives the public access to the source
-
code of the modified version.
-
-
An older license, called the Affero General Public License and
-
published by Affero, was designed to accomplish similar goals. This is
-
a different license, not a version of the Affero GPL, but Affero has
-
released a new version of the Affero GPL which permits relicensing under
-
this license.
-
-
The precise terms and conditions for copying, distribution and
-
modification follow.
-
-
TERMS AND CONDITIONS
-
-
0. Definitions.
-
-
"This License" refers to version 3 of the GNU Affero General Public License.
-
-
"Copyright" also means copyright-like laws that apply to other kinds of
-
works, such as semiconductor masks.
-
-
"The Program" refers to any copyrightable work licensed under this
-
License. Each licensee is addressed as "you". "Licensees" and
-
"recipients" may be individuals or organizations.
-
-
To "modify" a work means to copy from or adapt all or part of the work
-
in a fashion requiring copyright permission, other than the making of an
-
exact copy. The resulting work is called a "modified version" of the
-
earlier work or a work "based on" the earlier work.
-
-
A "covered work" means either the unmodified Program or a work based
-
on the Program.
-
-
To "propagate" a work means to do anything with it that, without
-
permission, would make you directly or secondarily liable for
-
infringement under applicable copyright law, except executing it on a
-
computer or modifying a private copy. Propagation includes copying,
-
distribution (with or without modification), making available to the
-
public, and in some countries other activities as well.
-
-
To "convey" a work means any kind of propagation that enables other
-
parties to make or receive copies. Mere interaction with a user through
-
a computer network, with no transfer of a copy, is not conveying.
-
-
An interactive user interface displays "Appropriate Legal Notices"
-
to the extent that it includes a convenient and prominently visible
-
feature that (1) displays an appropriate copyright notice, and (2)
-
tells the user that there is no warranty for the work (except to the
-
extent that warranties are provided), that licensees may convey the
-
work under this License, and how to view a copy of this License. If
-
the interface presents a list of user commands or options, such as a
-
menu, a prominent item in the list meets this criterion.
-
-
1. Source Code.
-
-
The "source code" for a work means the preferred form of the work
-
for making modifications to it. "Object code" means any non-source
-
form of a work.
-
-
A "Standard Interface" means an interface that either is an official
-
standard defined by a recognized standards body, or, in the case of
-
interfaces specified for a particular programming language, one that
-
is widely used among developers working in that language.
-
-
The "System Libraries" of an executable work include anything, other
-
than the work as a whole, that (a) is included in the normal form of
-
packaging a Major Component, but which is not part of that Major
-
Component, and (b) serves only to enable use of the work with that
-
Major Component, or to implement a Standard Interface for which an
-
implementation is available to the public in source code form. A
-
"Major Component", in this context, means a major essential component
-
(kernel, window system, and so on) of the specific operating system
-
(if any) on which the executable work runs, or a compiler used to
-
produce the work, or an object code interpreter used to run it.
-
-
The "Corresponding Source" for a work in object code form means all
-
the source code needed to generate, install, and (for an executable
-
work) run the object code and to modify the work, including scripts to
-
control those activities. However, it does not include the work's
-
System Libraries, or general-purpose tools or generally available free
-
programs which are used unmodified in performing those activities but
-
which are not part of the work. For example, Corresponding Source
-
includes interface definition files associated with source files for
-
the work, and the source code for shared libraries and dynamically
-
linked subprograms that the work is specifically designed to require,
-
such as by intimate data communication or control flow between those
-
subprograms and other parts of the work.
-
-
The Corresponding Source need not include anything that users
-
can regenerate automatically from other parts of the Corresponding
-
Source.
-
-
The Corresponding Source for a work in source code form is that
-
same work.
-
-
2. Basic Permissions.
-
-
All rights granted under this License are granted for the term of
-
copyright on the Program, and are irrevocable provided the stated
-
conditions are met. This License explicitly affirms your unlimited
-
permission to run the unmodified Program. The output from running a
-
covered work is covered by this License only if the output, given its
-
content, constitutes a covered work. This License acknowledges your
-
rights of fair use or other equivalent, as provided by copyright law.
-
-
You may make, run and propagate covered works that you do not
-
convey, without conditions so long as your license otherwise remains
-
in force. You may convey covered works to others for the sole purpose
-
of having them make modifications exclusively for you, or provide you
-
with facilities for running those works, provided that you comply with
-
the terms of this License in conveying all material for which you do
-
not control copyright. Those thus making or running the covered works
-
for you must do so exclusively on your behalf, under your direction
-
and control, on terms that prohibit them from making any copies of
-
your copyrighted material outside their relationship with you.
-
-
Conveying under any other circumstances is permitted solely under
-
the conditions stated below. Sublicensing is not allowed; section 10
-
makes it unnecessary.
-
-
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-
No covered work shall be deemed part of an effective technological
-
measure under any applicable law fulfilling obligations under article
-
11 of the WIPO copyright treaty adopted on 20 December 1996, or
-
similar laws prohibiting or restricting circumvention of such
-
measures.
-
-
When you convey a covered work, you waive any legal power to forbid
-
circumvention of technological measures to the extent such circumvention
-
is effected by exercising rights under this License with respect to
-
the covered work, and you disclaim any intention to limit operation or
-
modification of the work as a means of enforcing, against the work's
-
users, your or third parties' legal rights to forbid circumvention of
-
technological measures.
-
-
4. Conveying Verbatim Copies.
-
-
You may convey verbatim copies of the Program's source code as you
-
receive it, in any medium, provided that you conspicuously and
-
appropriately publish on each copy an appropriate copyright notice;
-
keep intact all notices stating that this License and any
-
non-permissive terms added in accord with section 7 apply to the code;
-
keep intact all notices of the absence of any warranty; and give all
-
recipients a copy of this License along with the Program.
-
-
You may charge any price or no price for each copy that you convey,
-
and you may offer support or warranty protection for a fee.
-
-
5. Conveying Modified Source Versions.
-
-
You may convey a work based on the Program, or the modifications to
-
produce it from the Program, in the form of source code under the
-
terms of section 4, provided that you also meet all of these conditions:
-
-
a) The work must carry prominent notices stating that you modified
-
it, and giving a relevant date.
-
-
b) The work must carry prominent notices stating that it is
-
released under this License and any conditions added under section
-
7. This requirement modifies the requirement in section 4 to
-
"keep intact all notices".
-
-
c) You must license the entire work, as a whole, under this
-
License to anyone who comes into possession of a copy. This
-
License will therefore apply, along with any applicable section 7
-
additional terms, to the whole of the work, and all its parts,
-
regardless of how they are packaged. This License gives no
-
permission to license the work in any other way, but it does not
-
invalidate such permission if you have separately received it.
-
-
d) If the work has interactive user interfaces, each must display
-
Appropriate Legal Notices; however, if the Program has interactive
-
interfaces that do not display Appropriate Legal Notices, your
-
work need not make them do so.
-
-
A compilation of a covered work with other separate and independent
-
works, which are not by their nature extensions of the covered work,
-
and which are not combined with it such as to form a larger program,
-
in or on a volume of a storage or distribution medium, is called an
-
"aggregate" if the compilation and its resulting copyright are not
-
used to limit the access or legal rights of the compilation's users
-
beyond what the individual works permit. Inclusion of a covered work
-
in an aggregate does not cause this License to apply to the other
-
parts of the aggregate.
-
-
6. Conveying Non-Source Forms.
-
-
You may convey a covered work in object code form under the terms
-
of sections 4 and 5, provided that you also convey the
-
machine-readable Corresponding Source under the terms of this License,
-
in one of these ways:
-
-
a) Convey the object code in, or embodied in, a physical product
-
(including a physical distribution medium), accompanied by the
-
Corresponding Source fixed on a durable physical medium
-
customarily used for software interchange.
-
-
b) Convey the object code in, or embodied in, a physical product
-
(including a physical distribution medium), accompanied by a
-
written offer, valid for at least three years and valid for as
-
long as you offer spare parts or customer support for that product
-
model, to give anyone who possesses the object code either (1) a
-
copy of the Corresponding Source for all the software in the
-
product that is covered by this License, on a durable physical
-
medium customarily used for software interchange, for a price no
-
more than your reasonable cost of physically performing this
-
conveying of source, or (2) access to copy the
-
Corresponding Source from a network server at no charge.
-
-
c) Convey individual copies of the object code with a copy of the
-
written offer to provide the Corresponding Source. This
-
alternative is allowed only occasionally and noncommercially, and
-
only if you received the object code with such an offer, in accord
-
with subsection 6b.
-
-
d) Convey the object code by offering access from a designated
-
place (gratis or for a charge), and offer equivalent access to the
-
Corresponding Source in the same way through the same place at no
-
further charge. You need not require recipients to copy the
-
Corresponding Source along with the object code. If the place to
-
copy the object code is a network server, the Corresponding Source
-
may be on a different server (operated by you or a third party)
-
that supports equivalent copying facilities, provided you maintain
-
clear directions next to the object code saying where to find the
-
Corresponding Source. Regardless of what server hosts the
-
Corresponding Source, you remain obligated to ensure that it is
-
available for as long as needed to satisfy these requirements.
-
-
e) Convey the object code using peer-to-peer transmission, provided
-
you inform other peers where the object code and Corresponding
-
Source of the work are being offered to the general public at no
-
charge under subsection 6d.
-
-
A separable portion of the object code, whose source code is excluded
-
from the Corresponding Source as a System Library, need not be
-
included in conveying the object code work.
-
-
A "User Product" is either (1) a "consumer product", which means any
-
tangible personal property which is normally used for personal, family,
-
or household purposes, or (2) anything designed or sold for incorporation
-
into a dwelling. In determining whether a product is a consumer product,
-
doubtful cases shall be resolved in favor of coverage. For a particular
-
product received by a particular user, "normally used" refers to a
-
typical or common use of that class of product, regardless of the status
-
of the particular user or of the way in which the particular user
-
actually uses, or expects or is expected to use, the product. A product
-
is a consumer product regardless of whether the product has substantial
-
commercial, industrial or non-consumer uses, unless such uses represent
-
the only significant mode of use of the product.
-
-
"Installation Information" for a User Product means any methods,
-
procedures, authorization keys, or other information required to install
-
and execute modified versions of a covered work in that User Product from
-
a modified version of its Corresponding Source. The information must
-
suffice to ensure that the continued functioning of the modified object
-
code is in no case prevented or interfered with solely because
-
modification has been made.
-
-
If you convey an object code work under this section in, or with, or
-
specifically for use in, a User Product, and the conveying occurs as
-
part of a transaction in which the right of possession and use of the
-
User Product is transferred to the recipient in perpetuity or for a
-
fixed term (regardless of how the transaction is characterized), the
-
Corresponding Source conveyed under this section must be accompanied
-
by the Installation Information. But this requirement does not apply
-
if neither you nor any third party retains the ability to install
-
modified object code on the User Product (for example, the work has
-
been installed in ROM).
-
-
The requirement to provide Installation Information does not include a
-
requirement to continue to provide support service, warranty, or updates
-
for a work that has been modified or installed by the recipient, or for
-
the User Product in which it has been modified or installed. Access to a
-
network may be denied when the modification itself materially and
-
adversely affects the operation of the network or violates the rules and
-
protocols for communication across the network.
-
-
Corresponding Source conveyed, and Installation Information provided,
-
in accord with this section must be in a format that is publicly
-
documented (and with an implementation available to the public in
-
source code form), and must require no special password or key for
-
unpacking, reading or copying.
-
-
7. Additional Terms.
-
-
"Additional permissions" are terms that supplement the terms of this
-
License by making exceptions from one or more of its conditions.
-
Additional permissions that are applicable to the entire Program shall
-
be treated as though they were included in this License, to the extent
-
that they are valid under applicable law. If additional permissions
-
apply only to part of the Program, that part may be used separately
-
under those permissions, but the entire Program remains governed by
-
this License without regard to the additional permissions.
-
-
When you convey a copy of a covered work, you may at your option
-
remove any additional permissions from that copy, or from any part of
-
it. (Additional permissions may be written to require their own
-
removal in certain cases when you modify the work.) You may place
-
additional permissions on material, added by you to a covered work,
-
for which you have or can give appropriate copyright permission.
-
-
Notwithstanding any other provision of this License, for material you
-
add to a covered work, you may (if authorized by the copyright holders of
-
that material) supplement the terms of this License with terms:
-
-
a) Disclaiming warranty or limiting liability differently from the
-
terms of sections 15 and 16 of this License; or
-
-
b) Requiring preservation of specified reasonable legal notices or
-
author attributions in that material or in the Appropriate Legal
-
Notices displayed by works containing it; or
-
-
c) Prohibiting misrepresentation of the origin of that material, or
-
requiring that modified versions of such material be marked in
-
reasonable ways as different from the original version; or
-
-
d) Limiting the use for publicity purposes of names of licensors or
-
authors of the material; or
-
-
e) Declining to grant rights under trademark law for use of some
-
trade names, trademarks, or service marks; or
-
-
f) Requiring indemnification of licensors and authors of that
-
material by anyone who conveys the material (or modified versions of
-
it) with contractual assumptions of liability to the recipient, for
-
any liability that these contractual assumptions directly impose on
-
those licensors and authors.
-
-
All other non-permissive additional terms are considered "further
-
restrictions" within the meaning of section 10. If the Program as you
-
received it, or any part of it, contains a notice stating that it is
-
governed by this License along with a term that is a further
-
restriction, you may remove that term. If a license document contains
-
a further restriction but permits relicensing or conveying under this
-
License, you may add to a covered work material governed by the terms
-
of that license document, provided that the further restriction does
-
not survive such relicensing or conveying.
+
This version of the GNU Lesser General Public License incorporates
+
the terms and conditions of version 3 of the GNU General Public
+
License, supplemented by the additional permissions listed below.
-
If you add terms to a covered work in accord with this section, you
-
must place, in the relevant source files, a statement of the
-
additional terms that apply to those files, or a notice indicating
-
where to find the applicable terms.
+
0. Additional Definitions.
-
Additional terms, permissive or non-permissive, may be stated in the
-
form of a separately written license, or stated as exceptions;
-
the above requirements apply either way.
+
As used herein, "this License" refers to version 3 of the GNU Lesser
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
+
General Public License.
-
8. Termination.
+
"The Library" refers to a covered work governed by this License,
+
other than an Application or a Combined Work as defined below.
-
You may not propagate or modify a covered work except as expressly
-
provided under this License. Any attempt otherwise to propagate or
-
modify it is void, and will automatically terminate your rights under
-
this License (including any patent licenses granted under the third
-
paragraph of section 11).
+
An "Application" is any work that makes use of an interface provided
+
by the Library, but which is not otherwise based on the Library.
+
Defining a subclass of a class defined by the Library is deemed a mode
+
of using an interface provided by the Library.
-
However, if you cease all violation of this License, then your
-
license from a particular copyright holder is reinstated (a)
-
provisionally, unless and until the copyright holder explicitly and
-
finally terminates your license, and (b) permanently, if the copyright
-
holder fails to notify you of the violation by some reasonable means
-
prior to 60 days after the cessation.
+
A "Combined Work" is a work produced by combining or linking an
+
Application with the Library. The particular version of the Library
+
with which the Combined Work was made is also called the "Linked
+
Version".
-
Moreover, your license from a particular copyright holder is
-
reinstated permanently if the copyright holder notifies you of the
-
violation by some reasonable means, this is the first time you have
-
received notice of violation of this License (for any work) from that
-
copyright holder, and you cure the violation prior to 30 days after
-
your receipt of the notice.
+
The "Minimal Corresponding Source" for a Combined Work means the
+
Corresponding Source for the Combined Work, excluding any source code
+
for portions of the Combined Work that, considered in isolation, are
+
based on the Application, and not on the Linked Version.
-
Termination of your rights under this section does not terminate the
-
licenses of parties who have received copies or rights from you under
-
this License. If your rights have been terminated and not permanently
-
reinstated, you do not qualify to receive new licenses for the same
-
material under section 10.
+
The "Corresponding Application Code" for a Combined Work means the
+
object code and/or source code for the Application, including any data
+
and utility programs needed for reproducing the Combined Work from the
+
Application, but excluding the System Libraries of the Combined Work.
-
9. Acceptance Not Required for Having Copies.
+
1. Exception to Section 3 of the GNU GPL.
-
You are not required to accept this License in order to receive or
-
run a copy of the Program. Ancillary propagation of a covered work
-
occurring solely as a consequence of using peer-to-peer transmission
-
to receive a copy likewise does not require acceptance. However,
-
nothing other than this License grants you permission to propagate or
-
modify any covered work. These actions infringe copyright if you do
-
not accept this License. Therefore, by modifying or propagating a
-
covered work, you indicate your acceptance of this License to do so.
+
You may convey a covered work under sections 3 and 4 of this License
+
without being bound by section 3 of the GNU GPL.
-
10. Automatic Licensing of Downstream Recipients.
+
2. Conveying Modified Versions.
-
Each time you convey a covered work, the recipient automatically
-
receives a license from the original licensors, to run, modify and
-
propagate that work, subject to this License. You are not responsible
-
for enforcing compliance by third parties with this License.
+
If you modify a copy of the Library, and, in your modifications, a
+
facility refers to a function or data to be supplied by an Application
+
that uses the facility (other than as an argument passed when the
+
facility is invoked), then you may convey a copy of the modified
+
version:
-
An "entity transaction" is a transaction transferring control of an
-
organization, or substantially all assets of one, or subdividing an
-
organization, or merging organizations. If propagation of a covered
-
work results from an entity transaction, each party to that
-
transaction who receives a copy of the work also receives whatever
-
licenses to the work the party's predecessor in interest had or could
-
give under the previous paragraph, plus a right to possession of the
-
Corresponding Source of the work from the predecessor in interest, if
-
the predecessor has it or can get it with reasonable efforts.
+
a) under this License, provided that you make a good faith effort to
+
ensure that, in the event an Application does not supply the
+
function or data, the facility still operates, and performs
+
whatever part of its purpose remains meaningful, or
-
You may not impose any further restrictions on the exercise of the
-
rights granted or affirmed under this License. For example, you may
-
not impose a license fee, royalty, or other charge for exercise of
-
rights granted under this License, and you may not initiate litigation
-
(including a cross-claim or counterclaim in a lawsuit) alleging that
-
any patent claim is infringed by making, using, selling, offering for
-
sale, or importing the Program or any portion of it.
+
b) under the GNU GPL, with none of the additional permissions of
+
this License applicable to that copy.
-
11. Patents.
+
3. Object Code Incorporating Material from Library Header Files.
-
A "contributor" is a copyright holder who authorizes use under this
-
License of the Program or a work on which the Program is based. The
-
work thus licensed is called the contributor's "contributor version".
+
The object code form of an Application may incorporate material from
+
a header file that is part of the Library. You may convey such object
+
code under terms of your choice, provided that, if the incorporated
+
material is not limited to numerical parameters, data structure
+
layouts and accessors, or small macros, inline functions and templates
+
(ten or fewer lines in length), you do both of the following:
-
A contributor's "essential patent claims" are all patent claims
-
owned or controlled by the contributor, whether already acquired or
-
hereafter acquired, that would be infringed by some manner, permitted
-
by this License, of making, using, or selling its contributor version,
-
but do not include claims that would be infringed only as a
-
consequence of further modification of the contributor version. For
-
purposes of this definition, "control" includes the right to grant
-
patent sublicenses in a manner consistent with the requirements of
-
this License.
+
a) Give prominent notice with each copy of the object code that the
+
Library is used in it and that the Library and its use are
+
covered by this License.
-
Each contributor grants you a non-exclusive, worldwide, royalty-free
-
patent license under the contributor's essential patent claims, to
-
make, use, sell, offer for sale, import and otherwise run, modify and
-
propagate the contents of its contributor version.
+
b) Accompany the object code with a copy of the GNU GPL and this license
+
document.
-
In the following three paragraphs, a "patent license" is any express
-
agreement or commitment, however denominated, not to enforce a patent
-
(such as an express permission to practice a patent or covenant not to
-
sue for patent infringement). To "grant" such a patent license to a
-
party means to make such an agreement or commitment not to enforce a
-
patent against the party.
+
4. Combined Works.
-
If you convey a covered work, knowingly relying on a patent license,
-
and the Corresponding Source of the work is not available for anyone
-
to copy, free of charge and under the terms of this License, through a
-
publicly available network server or other readily accessible means,
-
then you must either (1) cause the Corresponding Source to be so
-
available, or (2) arrange to deprive yourself of the benefit of the
-
patent license for this particular work, or (3) arrange, in a manner
-
consistent with the requirements of this License, to extend the patent
-
license to downstream recipients. "Knowingly relying" means you have
-
actual knowledge that, but for the patent license, your conveying the
-
covered work in a country, or your recipient's use of the covered work
-
in a country, would infringe one or more identifiable patents in that
-
country that you have reason to believe are valid.
+
You may convey a Combined Work under terms of your choice that,
+
taken together, effectively do not restrict modification of the
+
portions of the Library contained in the Combined Work and reverse
+
engineering for debugging such modifications, if you also do each of
+
the following:
-
If, pursuant to or in connection with a single transaction or
-
arrangement, you convey, or propagate by procuring conveyance of, a
-
covered work, and grant a patent license to some of the parties
-
receiving the covered work authorizing them to use, propagate, modify
-
or convey a specific copy of the covered work, then the patent license
-
you grant is automatically extended to all recipients of the covered
-
work and works based on it.
+
a) Give prominent notice with each copy of the Combined Work that
+
the Library is used in it and that the Library and its use are
+
covered by this License.
-
A patent license is "discriminatory" if it does not include within
-
the scope of its coverage, prohibits the exercise of, or is
-
conditioned on the non-exercise of one or more of the rights that are
-
specifically granted under this License. You may not convey a covered
-
work if you are a party to an arrangement with a third party that is
-
in the business of distributing software, under which you make payment
-
to the third party based on the extent of your activity of conveying
-
the work, and under which the third party grants, to any of the
-
parties who would receive the covered work from you, a discriminatory
-
patent license (a) in connection with copies of the covered work
-
conveyed by you (or copies made from those copies), or (b) primarily
-
for and in connection with specific products or compilations that
-
contain the covered work, unless you entered into that arrangement,
-
or that patent license was granted, prior to 28 March 2007.
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
+
document.
-
Nothing in this License shall be construed as excluding or limiting
-
any implied license or other defenses to infringement that may
-
otherwise be available to you under applicable patent law.
-
-
12. No Surrender of Others' Freedom.
-
-
If conditions are imposed on you (whether by court order, agreement or
-
otherwise) that contradict the conditions of this License, they do not
-
excuse you from the conditions of this License. If you cannot convey a
-
covered work so as to satisfy simultaneously your obligations under this
-
License and any other pertinent obligations, then as a consequence you may
-
not convey it at all. For example, if you agree to terms that obligate you
-
to collect a royalty for further conveying from those to whom you convey
-
the Program, the only way you could satisfy both those terms and this
-
License would be to refrain entirely from conveying the Program.
-
-
13. Remote Network Interaction; Use with the GNU General Public License.
-
-
Notwithstanding any other provision of this License, if you modify the
-
Program, your modified version must prominently offer all users
-
interacting with it remotely through a computer network (if your version
-
supports such interaction) an opportunity to receive the Corresponding
-
Source of your version by providing access to the Corresponding Source
-
from a network server at no charge, through some standard or customary
-
means of facilitating copying of software. This Corresponding Source
-
shall include the Corresponding Source for any work covered by version 3
-
of the GNU General Public License that is incorporated pursuant to the
-
following paragraph.
-
-
Notwithstanding any other provision of this License, you have
-
permission to link or combine any covered work with a work licensed
-
under version 3 of the GNU General Public License into a single
-
combined work, and to convey the resulting work. The terms of this
-
License will continue to apply to the part which is the covered work,
-
but the work with which it is combined will remain governed by version
-
3 of the GNU General Public License.
-
-
14. Revised Versions of this License.
-
-
The Free Software Foundation may publish revised and/or new versions of
-
the GNU Affero General Public License from time to time. Such new versions
-
will be similar in spirit to the present version, but may differ in detail to
-
address new problems or concerns.
+
c) For a Combined Work that displays copyright notices during
+
execution, include the copyright notice for the Library among
+
these notices, as well as a reference directing the user to the
+
copies of the GNU GPL and this license document.
-
Each version is given a distinguishing version number. If the
-
Program specifies that a certain numbered version of the GNU Affero General
-
Public License "or any later version" applies to it, you have the
-
option of following the terms and conditions either of that numbered
-
version or of any later version published by the Free Software
-
Foundation. If the Program does not specify a version number of the
-
GNU Affero General Public License, you may choose any version ever published
-
by the Free Software Foundation.
-
-
If the Program specifies that a proxy can decide which future
-
versions of the GNU Affero General Public License can be used, that proxy's
-
public statement of acceptance of a version permanently authorizes you
-
to choose that version for the Program.
-
-
Later license versions may give you additional or different
-
permissions. However, no additional obligations are imposed on any
-
author or copyright holder as a result of your choosing to follow a
-
later version.
-
-
15. Disclaimer of Warranty.
-
-
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-
16. Limitation of Liability.
-
-
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-
SUCH DAMAGES.
-
-
17. Interpretation of Sections 15 and 16.
-
-
If the disclaimer of warranty and limitation of liability provided
-
above cannot be given local legal effect according to their terms,
-
reviewing courts shall apply local law that most closely approximates
-
an absolute waiver of all civil liability in connection with the
-
Program, unless a warranty or assumption of liability accompanies a
-
copy of the Program in return for a fee.
+
d) Do one of the following:
-
END OF TERMS AND CONDITIONS
+
0) Convey the Minimal Corresponding Source under the terms of this
+
License, and the Corresponding Application Code in a form
+
suitable for, and under terms that permit, the user to
+
recombine or relink the Application with a modified version of
+
the Linked Version to produce a modified Combined Work, in the
+
manner specified by section 6 of the GNU GPL for conveying
+
Corresponding Source.
-
How to Apply These Terms to Your New Programs
+
1) Use a suitable shared library mechanism for linking with the
+
Library. A suitable mechanism is one that (a) uses at run time
+
a copy of the Library already present on the user's computer
+
system, and (b) will operate properly with a modified version
+
of the Library that is interface-compatible with the Linked
+
Version.
-
If you develop a new program, and you want it to be of the greatest
-
possible use to the public, the best way to achieve this is to make it
-
free software which everyone can redistribute and change under these terms.
+
e) Provide Installation Information, but only if you would otherwise
+
be required to provide such information under section 6 of the
+
GNU GPL, and only to the extent that such information is
+
necessary to install and execute a modified version of the
+
Combined Work produced by recombining or relinking the
+
Application with a modified version of the Linked Version. (If
+
you use option 4d0, the Installation Information must accompany
+
the Minimal Corresponding Source and Corresponding Application
+
Code. If you use option 4d1, you must provide the Installation
+
Information in the manner specified by section 6 of the GNU GPL
+
for conveying Corresponding Source.)
-
To do so, attach the following notices to the program. It is safest
-
to attach them to the start of each source file to most effectively
-
state the exclusion of warranty; and each file should have at least
-
the "copyright" line and a pointer to where the full notice is found.
+
5. Combined Libraries.
-
<one line to give the program's name and a brief idea of what it does.>
-
Copyright (C) <year> <name of author>
+
You may place library facilities that are a work based on the
+
Library side by side in a single library together with other library
+
facilities that are not Applications and are not covered by this
+
License, and convey such a combined library under terms of your
+
choice, if you do both of the following:
-
This program is free software: you can redistribute it and/or modify
-
it under the terms of the GNU Affero General Public License as published by
-
the Free Software Foundation, either version 3 of the License, or
-
(at your option) any later version.
+
a) Accompany the combined library with a copy of the same work based
+
on the Library, uncombined with any other library facilities,
+
conveyed under the terms of this License.
-
This program is distributed in the hope that it will be useful,
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-
GNU Affero General Public License for more details.
+
b) Give prominent notice with the combined library that part of it
+
is a work based on the Library, and explaining where to find the
+
accompanying uncombined form of the same work.
-
You should have received a copy of the GNU Affero General Public License
-
along with this program. If not, see <https://www.gnu.org/licenses/>.
+
6. Revised Versions of the GNU Lesser General Public License.
-
Also add information on how to contact you by electronic and paper mail.
+
The Free Software Foundation may publish revised and/or new versions
+
of the GNU Lesser General Public License from time to time. Such new
+
versions will be similar in spirit to the present version, but may
+
differ in detail to address new problems or concerns.
-
If your software can interact with users remotely through a computer
-
network, you should also make sure that it provides a way for users to
-
get its source. For example, if your program is a web application, its
-
interface could display a "Source" link that leads users to an archive
-
of the code. There are many ways you could offer source, and different
-
solutions will be better for different programs; see section 13 for the
-
specific requirements.
+
Each version is given a distinguishing version number. If the
+
Library as you received it specifies that a certain numbered version
+
of the GNU Lesser General Public License "or any later version"
+
applies to it, you have the option of following the terms and
+
conditions either of that published version or of any later version
+
published by the Free Software Foundation. If the Library as you
+
received it does not specify a version number of the GNU Lesser
+
General Public License, you may choose any version of the GNU Lesser
+
General Public License ever published by the Free Software Foundation.
-
You should also get your employer (if you work as a programmer) or school,
-
if any, to sign a "copyright disclaimer" for the program, if necessary.
-
For more information on this, and how to apply and follow the GNU AGPL, see
-
<https://www.gnu.org/licenses/>.
+
If the Library as you received it specifies that a proxy can decide
+
whether future versions of the GNU Lesser General Public License shall
+
apply, that proxy's public statement of acceptance of any version is
+
permanent authorization for you to choose that version for the
+
Library.
+16 -5
README.md
···
<h3 align="center">
-
<img src="./img/wordmark.png" alt="moonlight" />
+
<picture>
+
<source media="(prefers-color-scheme: dark)" srcset="./img/wordmark-light.png">
+
<source media="(prefers-color-scheme: light)" srcset="./img/wordmark.png">
+
<img src="./img/wordmark.png" alt="moonlight" />
+
</picture>
-
<a href="https://discord.gg/FdZBTFCP6F">Discord server</a>
+
<a href="https://moonlight-mod.github.io/using/install">Install</a>
+
\- <a href="https://moonlight-mod.github.io/ext-dev/getting-started">Docs</a>
+
\- <a href="https://discord.gg/FdZBTFCP6F">Discord server</a>
\- <a href="https://github.com/moonlight-mod/moonlight">GitHub</a>
-
\- <a href="https://moonlight-mod.github.io/">Docs</a>
<hr />
+
+
<picture>
+
<source media="(prefers-color-scheme: dark)" srcset="https://moonlight-mod.github.io/moonbase.png">
+
<source media="(prefers-color-scheme: light)" srcset="https://moonlight-mod.github.io/moonbase-light.png">
+
<img src="https://moonlight-mod.github.io/moonbase.png" alt="A screenshot of Moonbase, the moonlight UI" />
+
</picture>
</h3>
**moonlight** is yet another Discord client mod, focused on providing a decent user and developer experience.
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.
-
**_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.
+
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.
-
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.
+
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
···
const prod = process.env.NODE_ENV === "production";
const watch = process.argv.includes("--watch");
+
const browser = process.argv.includes("--browser");
+
const mv2 = process.argv.includes("--mv2");
+
const clean = process.argv.includes("--clean");
+
+
const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev";
+
const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev";
const external = [
"electron",
"fs",
"path",
"module",
-
"events",
-
"original-fs", // wtf asar?
+
"discord", // mappings
// Silence an esbuild warning
"./node-preload.js"
···
name: "build-log",
setup(build) {
build.onEnd((result) => {
-
console.log(
-
`[${timeFormatter.format(new Date())}] [${tag}] build finished`
-
);
+
console.log(`[${timeFormatter.format(new Date())}] [${tag}] build finished`);
});
}
});
async function build(name, entry) {
-
const outfile = path.join("./dist", name + ".js");
+
let outfile = path.join("./dist", name + ".js");
+
const browserDir = mv2 ? "browser-mv2" : "browser";
+
if (name === "browser") outfile = path.join("./dist", browserDir, "index.js");
const dropLabels = [];
-
if (name !== "injector") dropLabels.push("injector");
-
if (name !== "node-preload") dropLabels.push("nodePreload");
-
if (name !== "web-preload") dropLabels.push("webPreload");
+
const labels = {
+
injector: ["injector"],
+
nodePreload: ["node-preload"],
+
webPreload: ["web-preload"],
+
browser: ["browser"],
+
+
webTarget: ["web-preload", "browser"],
+
nodeTarget: ["node-preload", "injector"]
+
};
+
for (const [label, targets] of Object.entries(labels)) {
+
if (!targets.includes(name)) {
+
dropLabels.push(label);
+
}
+
}
const define = {
MOONLIGHT_ENV: `"${name}"`,
-
MOONLIGHT_PROD: prod.toString()
+
MOONLIGHT_PROD: prod.toString(),
+
MOONLIGHT_BRANCH: `"${buildBranch}"`,
+
MOONLIGHT_VERSION: `"${buildVersion}"`
};
-
for (const iterName of Object.keys(config)) {
+
for (const iterName of ["injector", "node-preload", "web-preload", "browser"]) {
const snake = iterName.replace(/-/g, "_").toUpperCase();
define[`MOONLIGHT_${snake}`] = (name === iterName).toString();
}
···
const nodeDependencies = ["glob"];
const ignoredExternal = name === "web-preload" ? nodeDependencies : [];
+
const plugins = [deduplicatedLogging, taggedBuildLog(name)];
+
if (name === "browser") {
+
plugins.push(
+
copyStaticFiles({
+
src: mv2 ? "./packages/browser/manifestv2.json" : "./packages/browser/manifest.json",
+
dest: `./dist/${browserDir}/manifest.json`
+
})
+
);
+
+
if (!mv2) {
+
plugins.push(
+
copyStaticFiles({
+
src: "./packages/browser/modifyResponseHeaders.json",
+
dest: `./dist/${browserDir}/modifyResponseHeaders.json`
+
})
+
);
+
plugins.push(
+
copyStaticFiles({
+
src: "./packages/browser/blockLoading.json",
+
dest: `./dist/${browserDir}/blockLoading.json`
+
})
+
);
+
}
+
+
plugins.push(
+
copyStaticFiles({
+
src: mv2 ? "./packages/browser/src/background-mv2.js" : "./packages/browser/src/background.js",
+
dest: `./dist/${browserDir}/background.js`
+
})
+
);
+
}
+
/** @type {import("esbuild").BuildOptions} */
const esbuildConfig = {
entryPoints: [entry],
outfile,
-
format: "cjs",
-
platform: name === "web-preload" ? "browser" : "node",
+
format: "iife",
+
globalName: "module.exports",
+
+
platform: ["web-preload", "browser"].includes(name) ? "browser" : "node",
treeShaking: true,
bundle: true,
···
dropLabels,
logLevel: "silent",
-
plugins: [deduplicatedLogging, taggedBuildLog(name)]
+
plugins,
+
+
// https://github.com/evanw/esbuild/issues/3944
+
footer:
+
name === "web-preload"
+
? {
+
js: `\n//# sourceURL=${name}.js`
+
}
+
: undefined
};
+
if (name === "browser") {
+
const coreExtensionsJson = {};
+
+
function readDir(dir) {
+
const files = fs.readdirSync(dir);
+
for (const file of files) {
+
const filePath = dir + "/" + file;
+
const normalizedPath = filePath.replace("./dist/core-extensions/", "");
+
if (fs.statSync(filePath).isDirectory()) {
+
readDir(filePath);
+
} else {
+
coreExtensionsJson[normalizedPath] = fs.readFileSync(filePath, "utf8");
+
}
+
}
+
}
+
+
readDir("./dist/core-extensions");
+
+
esbuildConfig.banner = {
+
js: `window._moonlight_coreExtensionsStr = ${JSON.stringify(JSON.stringify(coreExtensionsJson))};`
+
};
+
}
+
if (watch) {
const ctx = await esbuild.context(esbuildConfig);
await ctx.watch();
···
}
}
-
async function buildExt(ext, side, copyManifest, fileExt) {
+
async function buildExt(ext, side, fileExt) {
const outdir = path.join("./dist", "core-extensions", ext);
if (!fs.existsSync(outdir)) {
fs.mkdirSync(outdir, { recursive: true });
}
-
const entryPoints = [
-
`packages/core-extensions/src/${ext}/${side}.${fileExt}`
-
];
+
const entryPoints = [`packages/core-extensions/src/${ext}/${side}.${fileExt}`];
const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`;
if (fs.existsSync(wpModulesDir) && side === "index") {
-
const wpModules = fs.readdirSync(wpModulesDir);
-
for (const wpModule of wpModules) {
-
entryPoints.push(
-
`packages/core-extensions/src/${ext}/webpackModules/${wpModule}`
-
);
+
const wpModules = fs.opendirSync(wpModulesDir);
+
for await (const wpModule of wpModules) {
+
if (wpModule.isFile()) {
+
entryPoints.push(`packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`);
+
} else {
+
for (const fileExt of ["ts", "tsx"]) {
+
const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`;
+
if (fs.existsSync(path)) {
+
entryPoints.push({
+
in: path,
+
out: `webpackModules/${wpModule.name}`
+
});
+
}
+
}
+
}
}
}
···
}
};
+
const styleInput = `packages/core-extensions/src/${ext}/style.css`;
+
const styleOutput = `dist/core-extensions/${ext}/style.css`;
+
const esbuildConfig = {
entryPoints,
outdir,
-
format: "cjs",
+
format: "iife",
+
globalName: "module.exports",
platform: "node",
treeShaking: true,
···
},
logLevel: "silent",
plugins: [
-
...(copyManifest
+
copyStaticFiles({
+
src: `./packages/core-extensions/src/${ext}/manifest.json`,
+
dest: `./dist/core-extensions/${ext}/manifest.json`
+
}),
+
...(fs.existsSync(styleInput)
? [
copyStaticFiles({
-
src: `./packages/core-extensions/src/${ext}/manifest.json`,
-
dest: `./dist/core-extensions/${ext}/manifest.json`
+
src: styleInput,
+
dest: styleOutput
})
]
: []),
···
const promises = [];
-
for (const [name, entry] of Object.entries(config)) {
-
promises.push(build(name, entry));
-
}
-
-
const coreExtensions = fs.readdirSync("./packages/core-extensions/src");
-
for (const ext of coreExtensions) {
-
let copiedManifest = false;
+
if (clean) {
+
fs.rmSync("./dist", { recursive: true, force: true });
+
} else if (browser) {
+
build("browser", "packages/browser/src/index.ts");
+
} else {
+
for (const [name, entry] of Object.entries(config)) {
+
promises.push(build(name, entry));
+
}
-
for (const fileExt of ["ts", "tsx"]) {
-
for (const type of ["index", "node", "host"]) {
-
if (
-
fs.existsSync(
-
`./packages/core-extensions/src/${ext}/${type}.${fileExt}`
-
)
-
) {
-
promises.push(buildExt(ext, type, !copiedManifest, fileExt));
-
copiedManifest = true;
+
const coreExtensions = fs.readdirSync("./packages/core-extensions/src");
+
for (const ext of coreExtensions) {
+
for (const fileExt of ["ts", "tsx"]) {
+
for (const type of ["index", "node", "host"]) {
+
if (fs.existsSync(`./packages/core-extensions/src/${ext}/${type}.${fileExt}`)) {
+
promises.push(buildExt(ext, type, fileExt));
+
}
}
}
}
-1
env.d.ts
···
-
/// <reference types="./packages/types/src/index" />
+25
eslint.config.mjs
···
+
import config from "@moonlight-mod/eslint-config";
+
+
export default [
+
...config,
+
{
+
rules: {
+
// baseUrl being set to ./packages/ makes language server suggest "types/src" instead of "@moonlight-mod/types"
+
"no-restricted-imports": [
+
"error",
+
{
+
patterns: [
+
{
+
group: ["types/*"],
+
message: "Use @moonlight-mod/types instead"
+
},
+
{
+
group: ["core/*"],
+
message: "Use @moonlight-mod/core instead"
+
}
+
]
+
}
+
]
+
}
+
}
+
];
+61
flake.lock
···
+
{
+
"nodes": {
+
"flake-utils": {
+
"inputs": {
+
"systems": "systems"
+
},
+
"locked": {
+
"lastModified": 1701680307,
+
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+
"type": "github"
+
},
+
"original": {
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"type": "github"
+
}
+
},
+
"nixpkgs": {
+
"locked": {
+
"lastModified": 1744232761,
+
"narHash": "sha256-gbl9hE39nQRpZaLjhWKmEu5ejtQsgI5TWYrIVVJn30U=",
+
"owner": "NixOS",
+
"repo": "nixpkgs",
+
"rev": "f675531bc7e6657c10a18b565cfebd8aa9e24c14",
+
"type": "github"
+
},
+
"original": {
+
"owner": "NixOS",
+
"ref": "nixos-unstable",
+
"repo": "nixpkgs",
+
"type": "github"
+
}
+
},
+
"root": {
+
"inputs": {
+
"flake-utils": "flake-utils",
+
"nixpkgs": "nixpkgs"
+
}
+
},
+
"systems": {
+
"locked": {
+
"lastModified": 1681028828,
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+
"owner": "nix-systems",
+
"repo": "default",
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+
"type": "github"
+
},
+
"original": {
+
"owner": "nix-systems",
+
"repo": "default",
+
"type": "github"
+
}
+
}
+
},
+
"root": "root",
+
"version": 7
+
}
+31
flake.nix
···
+
{
+
description = "Yet another Discord mod";
+
+
inputs = {
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
flake-utils.url = "github:numtide/flake-utils";
+
};
+
+
outputs = { self, nixpkgs, flake-utils }:
+
let overlay = import ./nix/overlay.nix { };
+
in flake-utils.lib.eachDefaultSystem (system:
+
let
+
pkgs = import nixpkgs {
+
inherit system;
+
config.allowUnfree = true;
+
overlays = [ overlay ];
+
};
+
in {
+
# Don't use these unless you're testing things
+
packages.default = pkgs.moonlight-mod;
+
packages.moonlight-mod = pkgs.moonlight-mod;
+
+
packages.discord = pkgs.discord;
+
packages.discord-ptb = pkgs.discord-ptb;
+
packages.discord-canary = pkgs.discord-canary;
+
packages.discord-development = pkgs.discord-development;
+
}) // {
+
overlays.default = overlay;
+
homeModules.default = ./nix/home-manager.nix;
+
};
+
}
img/wordmark-light.png

This is a binary file and will not be displayed.

+57
nix/default.nix
···
+
{
+
lib,
+
stdenv,
+
nodejs_22,
+
pnpm_10,
+
}:
+
+
stdenv.mkDerivation (finalAttrs: {
+
pname = "moonlight";
+
version = (builtins.fromJSON (builtins.readFile ./../package.json)).version;
+
+
src = ./..;
+
+
outputs = [ "out" "firefox" ];
+
+
nativeBuildInputs = [
+
nodejs_22
+
pnpm_10.configHook
+
];
+
+
pnpmDeps = pnpm_10.fetchDeps {
+
inherit (finalAttrs) pname version src;
+
hash = "sha256-I+zRCUqJabpGJRFBGW0NrM9xzyzeCjioF54zlCpynBU=";
+
};
+
+
env = {
+
NODE_ENV = "production";
+
MOONLIGHT_VERSION = "v${finalAttrs.version}";
+
};
+
+
buildPhase = ''
+
runHook preBuild
+
+
pnpm run build
+
pnpm run browser-mv2
+
+
runHook postBuild
+
'';
+
+
installPhase = ''
+
runHook preInstall
+
+
cp -r dist $out
+
+
mkdir -p $firefox/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/
+
mv $out/browser-mv2 $firefox/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/{0fb6d66f-f22d-4555-a87b-34ef4bea5e2a}
+
+
runHook postInstall
+
'';
+
+
meta = with lib; {
+
description = "Yet another Discord mod";
+
homepage = "https://moonlight-mod.github.io/";
+
license = licenses.lgpl3;
+
maintainers = with maintainers; [ notnite ];
+
};
+
})
+56
nix/home-manager.nix
···
+
{ config, lib, pkgs, ... }:
+
+
let cfg = config.programs.moonlight-mod;
+
in {
+
options.programs.moonlight-mod = {
+
enable = lib.mkEnableOption "Yet another Discord mod";
+
+
configs = let
+
# TODO: type this
+
type = lib.types.nullOr (lib.types.attrs);
+
default = null;
+
in {
+
stable = lib.mkOption {
+
inherit type default;
+
description = "Configuration for Discord Stable";
+
};
+
+
ptb = lib.mkOption {
+
inherit type default;
+
description = "Configuration for Discord PTB";
+
};
+
+
canary = lib.mkOption {
+
inherit type default;
+
description = "Configuration for Discord Canary";
+
};
+
+
development = lib.mkOption {
+
inherit type default;
+
description = "Configuration for Discord Development";
+
};
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
xdg.configFile."moonlight-mod/stable.json" =
+
lib.mkIf (cfg.configs.stable != null) {
+
text = builtins.toJSON cfg.configs.stable;
+
};
+
+
xdg.configFile."moonlight-mod/ptb.json" =
+
lib.mkIf (cfg.configs.ptb != null) {
+
text = builtins.toJSON cfg.configs.ptb;
+
};
+
+
xdg.configFile."moonlight-mod/canary.json" =
+
lib.mkIf (cfg.configs.canary != null) {
+
text = builtins.toJSON cfg.configs.canary;
+
};
+
+
xdg.configFile."moonlight-mod/development.json" =
+
lib.mkIf (cfg.configs.development != null) {
+
text = builtins.toJSON cfg.configs.development;
+
};
+
};
+
}
+57
nix/overlay.nix
···
+
{ ... }:
+
+
let
+
nameTable = {
+
discord = "Discord";
+
discord-ptb = "DiscordPTB";
+
discord-canary = "DiscordCanary";
+
discord-development = "DiscordDevelopment";
+
};
+
+
darwinNameTable = {
+
discord = "Discord";
+
discord-ptb = "Discord PTB";
+
discord-canary = "Discord Canary";
+
discord-development = "Discord Development";
+
};
+
+
mkOverride = prev: moonlight: name:
+
let discord = prev.${name};
+
in discord.overrideAttrs (old: {
+
installPhase = let
+
folderName = nameTable.${name};
+
darwinFolderName = darwinNameTable.${name};
+
+
injected = ''
+
require("${moonlight}/injector").inject(
+
require("path").join(__dirname, "../_app.asar")
+
);
+
'';
+
+
packageJson = ''
+
{"name":"${name}","main":"./injector.js","private":true}
+
'';
+
+
in old.installPhase + "\n" + ''
+
resources="$out/opt/${folderName}/resources"
+
if [ ! -d "$resources" ]; then
+
resources="$out/Applications/${darwinFolderName}.app/Contents/Resources"
+
fi
+
+
mv "$resources/app.asar" "$resources/_app.asar"
+
mkdir -p "$resources/app"
+
+
cat > "$resources/app/injector.js" <<EOF
+
${injected}
+
EOF
+
+
echo '${packageJson}' > "$resources/app/package.json"
+
'';
+
});
+
in final: prev: rec {
+
moonlight-mod = final.callPackage ./default.nix { };
+
discord = mkOverride prev moonlight-mod "discord";
+
discord-ptb = mkOverride prev moonlight-mod "discord-ptb";
+
discord-canary = mkOverride prev moonlight-mod "discord-canary";
+
discord-development = mkOverride prev moonlight-mod "discord-development";
+
}
+28 -14
package.json
···
{
"name": "moonlight",
-
"version": "1.0.3",
+
"version": "1.3.14",
+
"packageManager": "pnpm@10.7.1",
"description": "Yet another Discord mod",
+
"license": "LGPL-3.0-or-later",
"homepage": "https://moonlight-mod.github.io/",
"repository": {
"type": "git",
···
},
"bugs": {
"url": "https://github.com/moonlight-mod/moonlight/issues"
+
},
+
"engineStrict": true,
+
"engines": {
+
"node": ">=22",
+
"pnpm": ">=10",
+
"npm": "pnpm",
+
"yarn": "pnpm"
},
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
+
"clean": "node build.mjs --clean",
+
"browser": "node build.mjs --browser",
+
"browser-mv2": "node build.mjs --browser --mv2",
"lint": "eslint packages",
-
"lint:fix": "eslint packages",
-
"lint:report": "eslint --output-file eslint_report.json --format json packages",
-
"typecheck": "tsc --noEmit"
+
"lint:fix": "pnpm lint --fix",
+
"lint:report": "pnpm lint --output-file eslint_report.json --format json",
+
"typecheck": "tsc --noEmit",
+
"check": "pnpm run lint && pnpm run typecheck",
+
"prepare": "husky install",
+
"updates": "pnpm taze -r"
},
"devDependencies": {
-
"@typescript-eslint/eslint-plugin": "^6.13.2",
-
"@typescript-eslint/parser": "^6.13.2",
-
"esbuild": "^0.19.3",
-
"esbuild-copy-static-files": "^0.1.0",
-
"eslint": "^8.55.0",
-
"eslint-config-prettier": "^9.1.0",
-
"eslint-plugin-prettier": "^5.0.1",
-
"eslint-plugin-react": "^7.33.2",
-
"prettier": "^3.1.0",
-
"typescript": "^5.3.2"
+
"@moonlight-mod/eslint-config": "catalog:dev",
+
"@types/node": "catalog:dev",
+
"esbuild": "catalog:dev",
+
"esbuild-copy-static-files": "catalog:dev",
+
"eslint": "catalog:dev",
+
"husky": "catalog:dev",
+
"prettier": "catalog:dev",
+
"taze": "catalog:dev",
+
"typescript": "catalog:dev"
}
}
+14
packages/browser/blockLoading.json
···
+
[
+
{
+
"id": 2,
+
"priority": 1,
+
"action": {
+
"type": "block"
+
},
+
"condition": {
+
"requestDomains": ["discord.com", "discordapp.com"],
+
"urlFilter": "*/assets/*.js",
+
"resourceTypes": ["script"]
+
}
+
}
+
]
+46
packages/browser/manifest.json
···
+
{
+
"$schema": "https://json.schemastore.org/chrome-manifest",
+
"manifest_version": 3,
+
"name": "moonlight",
+
"description": "Yet another Discord mod",
+
"version": "1.3.14",
+
"permissions": ["declarativeNetRequestWithHostAccess", "webRequest", "scripting", "webNavigation"],
+
"host_permissions": [
+
"https://moonlight-mod.github.io/*",
+
"https://api.github.com/*",
+
"https://*.discord.com/*",
+
"https://*.discordapp.com/*"
+
],
+
"content_scripts": [
+
{
+
"js": ["index.js"],
+
"matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"],
+
"run_at": "document_start",
+
"world": "MAIN"
+
}
+
],
+
"declarative_net_request": {
+
"rule_resources": [
+
{
+
"id": "modifyResponseHeaders",
+
"enabled": true,
+
"path": "modifyResponseHeaders.json"
+
},
+
{
+
"id": "blockLoading",
+
"enabled": true,
+
"path": "blockLoading.json"
+
}
+
]
+
},
+
"background": {
+
"service_worker": "background.js",
+
"type": "module"
+
},
+
"web_accessible_resources": [
+
{
+
"resources": ["index.js"],
+
"matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"]
+
}
+
]
+
}
+33
packages/browser/manifestv2.json
···
+
{
+
"$schema": "https://json.schemastore.org/chrome-manifest",
+
"manifest_version": 2,
+
"name": "moonlight",
+
"description": "Yet another Discord mod",
+
"version": "1.3.14",
+
"permissions": [
+
"webRequest",
+
"webRequestBlocking",
+
"scripting",
+
"webNavigation",
+
"https://*.discord.com/*",
+
"https://*.discordapp.com/*",
+
"https://moonlight-mod.github.io/*",
+
"https://api.github.com/*"
+
],
+
"background": {
+
"scripts": ["background.js"]
+
},
+
"content_scripts": [
+
{
+
"js": ["index.js"],
+
"matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"],
+
"run_at": "document_start",
+
"world": "MAIN"
+
}
+
],
+
"browser_specific_settings": {
+
"gecko": {
+
"id": "{0fb6d66f-f22d-4555-a87b-34ef4bea5e2a}"
+
}
+
}
+
}
+19
packages/browser/modifyResponseHeaders.json
···
+
[
+
{
+
"id": 1,
+
"priority": 2,
+
"action": {
+
"type": "modifyHeaders",
+
"responseHeaders": [
+
{
+
"header": "Content-Security-Policy",
+
"operation": "remove"
+
}
+
]
+
},
+
"condition": {
+
"resourceTypes": ["main_frame"],
+
"requestDomains": ["discord.com"]
+
}
+
}
+
]
+21
packages/browser/package.json
···
+
{
+
"name": "@moonlight-mod/browser",
+
"private": true,
+
"engines": {
+
"node": ">=22",
+
"pnpm": ">=10",
+
"npm": "pnpm",
+
"yarn": "pnpm"
+
},
+
"dependencies": {
+
"@moonlight-mod/core": "workspace:*",
+
"@moonlight-mod/types": "workspace:*",
+
"@moonlight-mod/web-preload": "workspace:*",
+
"@zenfs/core": "catalog:prod",
+
"@zenfs/dom": "catalog:prod"
+
},
+
"engineStrict": true,
+
"devDependencies": {
+
"@types/chrome": "catalog:dev"
+
}
+
}
+84
packages/browser/src/background-mv2.js
···
+
/* eslint-disable no-console */
+
/* eslint-disable no-undef */
+
+
const scriptUrls = ["web.", "sentry."];
+
let blockedScripts = new Set();
+
+
chrome.webRequest.onBeforeRequest.addListener(
+
async (details) => {
+
if (details.tabId === -1) return;
+
+
const url = new URL(details.url);
+
const hasUrl = scriptUrls.some((scriptUrl) => {
+
return (
+
details.url.includes(scriptUrl) &&
+
!url.searchParams.has("inj") &&
+
(url.host.endsWith("discord.com") || url.host.endsWith("discordapp.com"))
+
);
+
});
+
if (hasUrl) blockedScripts.add(details.url);
+
+
if (blockedScripts.size === scriptUrls.length) {
+
const blockedScriptsCopy = Array.from(blockedScripts);
+
blockedScripts.clear();
+
+
setTimeout(async () => {
+
console.log("Starting moonlight");
+
await chrome.scripting.executeScript({
+
target: { tabId: details.tabId },
+
world: "MAIN",
+
args: [blockedScriptsCopy],
+
func: async (blockedScripts) => {
+
console.log("Initializing moonlight");
+
try {
+
await window._moonlightBrowserInit();
+
} catch (e) {
+
console.error(e);
+
}
+
+
console.log("Readding scripts");
+
try {
+
const scripts = [...document.querySelectorAll("script")].filter(
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
+
);
+
+
blockedScripts.reverse();
+
for (const url of blockedScripts) {
+
if (url.includes("/sentry.")) continue;
+
+
const script = scripts.find((script) => url.includes(script.src));
+
const newScript = document.createElement("script");
+
for (const attr of script.attributes) {
+
if (attr.name === "src") attr.value += "?inj";
+
newScript.setAttribute(attr.name, attr.value);
+
}
+
script.remove();
+
document.documentElement.appendChild(newScript);
+
}
+
} catch (e) {
+
console.error(e);
+
}
+
}
+
});
+
}, 0);
+
}
+
+
if (hasUrl) return { cancel: true };
+
},
+
{
+
urls: ["https://*.discord.com/assets/*.js", "https://*.discordapp.com/assets/*.js"]
+
},
+
["blocking"]
+
);
+
+
chrome.webRequest.onHeadersReceived.addListener(
+
(details) => {
+
return {
+
responseHeaders: details.responseHeaders.filter(
+
(header) => header.name.toLowerCase() !== "content-security-policy"
+
)
+
};
+
},
+
{ urls: ["https://*.discord.com/*", "https://*.discordapp.com/*"] },
+
["blocking", "responseHeaders"]
+
);
+111
packages/browser/src/background.js
···
+
/* eslint-disable no-console */
+
/* eslint-disable no-undef */
+
+
const scriptUrls = ["web.", "sentry."];
+
let blockedScripts = new Set();
+
+
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
+
const url = new URL(details.url);
+
if (
+
!url.searchParams.has("inj") &&
+
(url.hostname.endsWith("discord.com") || url.hostname.endsWith("discordapp.com"))
+
) {
+
console.log("Enabling block ruleset");
+
await chrome.declarativeNetRequest.updateEnabledRulesets({
+
enableRulesetIds: ["modifyResponseHeaders", "blockLoading"]
+
});
+
}
+
});
+
+
chrome.webRequest.onBeforeRequest.addListener(
+
async (details) => {
+
if (details.tabId === -1) return;
+
+
const url = new URL(details.url);
+
const hasUrl = scriptUrls.some((scriptUrl) => {
+
return (
+
details.url.includes(scriptUrl) &&
+
!url.searchParams.has("inj") &&
+
(url.hostname.endsWith("discord.com") || url.hostname.endsWith("discordapp.com"))
+
);
+
});
+
+
if (hasUrl) blockedScripts.add(details.url);
+
+
if (blockedScripts.size === scriptUrls.length) {
+
const blockedScriptsCopy = Array.from(blockedScripts);
+
blockedScripts.clear();
+
+
console.log("Running moonlight script");
+
try {
+
await chrome.scripting.executeScript({
+
target: { tabId: details.tabId },
+
world: "MAIN",
+
files: ["index.js"]
+
});
+
} catch (e) {
+
console.error(e);
+
}
+
+
console.log("Initializing moonlight");
+
try {
+
await chrome.scripting.executeScript({
+
target: { tabId: details.tabId },
+
world: "MAIN",
+
func: async () => {
+
try {
+
await window._moonlightBrowserInit();
+
} catch (e) {
+
console.error(e);
+
}
+
}
+
});
+
} catch (e) {
+
console.log(e);
+
}
+
+
console.log("Disabling block ruleset");
+
try {
+
await chrome.declarativeNetRequest.updateEnabledRulesets({
+
disableRulesetIds: ["blockLoading"],
+
enableRulesetIds: ["modifyResponseHeaders"]
+
});
+
} catch (e) {
+
console.error(e);
+
}
+
+
console.log("Readding scripts");
+
try {
+
await chrome.scripting.executeScript({
+
target: { tabId: details.tabId },
+
world: "MAIN",
+
args: [blockedScriptsCopy],
+
func: async (blockedScripts) => {
+
const scripts = [...document.querySelectorAll("script")].filter(
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
+
);
+
+
blockedScripts.reverse();
+
for (const url of blockedScripts) {
+
if (url.includes("/sentry.")) continue;
+
+
const script = scripts.find((script) => url.includes(script.src));
+
const newScript = document.createElement("script");
+
for (const attr of script.attributes) {
+
if (attr.name === "src") attr.value += "?inj";
+
newScript.setAttribute(attr.name, attr.value);
+
}
+
script.remove();
+
document.documentElement.appendChild(newScript);
+
}
+
}
+
});
+
} catch (e) {
+
console.error(e);
+
}
+
}
+
},
+
{
+
urls: ["*://*.discord.com/assets/*.js", "*://*.discordapp.com/assets/*.js"]
+
}
+
);
+161
packages/browser/src/index.ts
···
+
import "@moonlight-mod/web-preload";
+
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
+
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
+
import { getExtensions } from "@moonlight-mod/core/extension";
+
import { loadExtensions } from "@moonlight-mod/core/extension/loader";
+
import { MoonlightBranch, MoonlightNode } from "@moonlight-mod/types";
+
import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
+
import { IndexedDB } from "@zenfs/dom";
+
import { configureSingle } from "@zenfs/core";
+
import * as fs from "@zenfs/core/promises";
+
import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event";
+
import { createEventEmitter } from "@moonlight-mod/core/util/event";
+
+
function getParts(path: string) {
+
if (path.startsWith("/")) path = path.substring(1);
+
return path.split("/");
+
}
+
+
window._moonlightBrowserInit = async () => {
+
delete window._moonlightBrowserInit;
+
+
// Set up a virtual filesystem with IndexedDB
+
await configureSingle({
+
backend: IndexedDB,
+
storeName: "moonlight-fs"
+
});
+
+
window.moonlightNodeSandboxed = {
+
fs: {
+
async readFile(path) {
+
return new Uint8Array(await fs.readFile(path));
+
},
+
async readFileString(path) {
+
const file = await this.readFile(path);
+
return new TextDecoder().decode(file);
+
},
+
async writeFile(path, data) {
+
await fs.writeFile(path, data);
+
},
+
async writeFileString(path, data) {
+
const file = new TextEncoder().encode(data);
+
await this.writeFile(path, file);
+
},
+
async unlink(path) {
+
await fs.unlink(path);
+
},
+
+
async readdir(path) {
+
return await fs.readdir(path);
+
},
+
async mkdir(path) {
+
const parts = getParts(path);
+
for (let i = 0; i < parts.length; i++) {
+
const path = this.join(...parts.slice(0, i + 1));
+
if (!(await this.exists(path))) await fs.mkdir(path);
+
}
+
},
+
+
async rmdir(path) {
+
const entries = await this.readdir(path);
+
+
for (const entry of entries) {
+
const fullPath = this.join(path, entry);
+
const isFile = await this.isFile(fullPath);
+
if (isFile) {
+
await this.unlink(fullPath);
+
} else {
+
await this.rmdir(fullPath);
+
}
+
}
+
+
await fs.rmdir(path);
+
},
+
+
async exists(path) {
+
return await fs.exists(path);
+
},
+
async isFile(path) {
+
return (await fs.stat(path)).isFile();
+
},
+
async isDir(path) {
+
return (await fs.stat(path)).isDirectory();
+
},
+
+
join(...parts) {
+
let str = parts.join("/");
+
if (!str.startsWith("/")) str = "/" + str;
+
return str;
+
},
+
dirname(path) {
+
const parts = getParts(path);
+
return "/" + parts.slice(0, parts.length - 1).join("/");
+
},
+
basename(path) {
+
const parts = getParts(path);
+
return parts[parts.length - 1];
+
}
+
},
+
// TODO
+
addCors(url) {},
+
addBlocked(url) {}
+
};
+
+
// Actual loading begins here
+
let config = await readConfig();
+
initLogger(config);
+
+
const extensions = await getExtensions();
+
const processedExtensions = await loadExtensions(extensions);
+
+
const moonlightNode: MoonlightNode = {
+
get config() {
+
return config;
+
},
+
extensions,
+
processedExtensions,
+
nativesCache: {},
+
isBrowser: true,
+
events: createEventEmitter<NodeEventType, NodeEventPayloads>(),
+
+
version: MOONLIGHT_VERSION,
+
branch: MOONLIGHT_BRANCH as MoonlightBranch,
+
+
getConfig(ext) {
+
return getConfig(ext, config);
+
},
+
getConfigOption(ext, name) {
+
const manifest = getManifest(extensions, ext);
+
return getConfigOption(ext, name, config, manifest?.settings);
+
},
+
async setConfigOption(ext, name, value) {
+
setConfigOption(config, ext, name, value);
+
await this.writeConfig(config);
+
},
+
+
getNatives: () => {},
+
getLogger: (id: string) => {
+
return new Logger(id);
+
},
+
+
getMoonlightDir() {
+
return "/";
+
},
+
getExtensionDir: (ext: string) => {
+
return `/extensions/${ext}`;
+
},
+
+
async writeConfig(newConfig) {
+
await writeConfig(newConfig);
+
config = newConfig;
+
this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig);
+
}
+
};
+
+
Object.assign(window, {
+
moonlightNode
+
});
+
+
// This is set by web-preload for us
+
await window._moonlightWebLoad!();
+
};
+7
packages/browser/tsconfig.json
···
+
{
+
"extends": "../../tsconfig.json",
+
"compilerOptions": {
+
"lib": ["DOM", "ESNext", "ESNext.AsyncIterable"],
+
"module": "ES2022"
+
}
+
}
+7
packages/core/package.json
···
"exports": {
"./*": "./src/*.ts"
},
+
"engineStrict": true,
+
"engines": {
+
"node": ">=22",
+
"pnpm": ">=10",
+
"npm": "pnpm",
+
"yarn": "pnpm"
+
},
"dependencies": {
"@moonlight-mod/types": "workspace:*"
}
+57
packages/core/src/asar.ts
···
+
// https://github.com/electron/asar
+
// http://formats.kaitai.io/python_pickle/
+
import { BinaryReader } from "./util/binary";
+
+
/*
+
The asar format is kinda bad, especially because it uses multiple pickle
+
entries. It spams sizes, expecting us to read small buffers and parse those,
+
but we can just take it all through at once without having to create multiple
+
BinaryReaders. This implementation might be wrong, though.
+
+
This either has size/offset or files but I can't get the type to cooperate,
+
so pretend this is a union.
+
*/
+
+
type AsarEntry = {
+
size: number;
+
offset: `${number}`; // who designed this
+
+
files?: Record<string, AsarEntry>;
+
};
+
+
export default function extractAsar(file: ArrayBuffer) {
+
const array = new Uint8Array(file);
+
const br = new BinaryReader(array);
+
+
// two uints, one containing the number '4', to signify that the other uint takes up 4 bytes
+
// bravo, electron, bravo
+
const _payloadSize = br.readUInt32();
+
const _headerSize = br.readInt32();
+
+
const headerStringStart = br.position;
+
const headerStringSize = br.readUInt32(); // How big the block is
+
const actualStringSize = br.readUInt32(); // How big the string in that block is
+
+
const base = headerStringStart + headerStringSize + 4;
+
+
const string = br.readString(actualStringSize);
+
const header: AsarEntry = JSON.parse(string);
+
+
const ret: Record<string, Uint8Array> = {};
+
function addDirectory(dir: AsarEntry, path: string) {
+
for (const [name, data] of Object.entries(dir.files!)) {
+
const fullName = path + "/" + name;
+
if (data.files != null) {
+
addDirectory(data, fullName);
+
} else {
+
br.position = base + parseInt(data.offset);
+
const file = br.read(data.size);
+
ret[fullName] = file;
+
}
+
}
+
}
+
+
addDirectory(header, "");
+
+
return ret;
+
}
+31 -31
packages/core/src/config.ts
···
import { Config } from "@moonlight-mod/types";
-
import requireImport from "./util/import";
import { getConfigPath } from "./util/data";
+
import * as constants from "@moonlight-mod/types/constants";
+
import Logger from "./util/logger";
+
+
const logger = new Logger("core/config");
const defaultConfig: Config = {
+
// If you're updating this, update `builtinExtensions` in constants as well
extensions: {
moonbase: true,
disableSentry: true,
noTrack: true,
noHideToken: true
},
-
repositories: ["https://moonlight-mod.github.io/extensions-dist/repo.json"]
+
repositories: [constants.mainRepo]
};
-
export function writeConfig(config: Config) {
-
const fs = requireImport("fs");
-
const configPath = getConfigPath();
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
-
}
-
-
function readConfigNode(): Config {
-
const fs = requireImport("fs");
-
const configPath = getConfigPath();
-
-
if (!fs.existsSync(configPath)) {
-
writeConfig(defaultConfig);
-
return defaultConfig;
+
export async function writeConfig(config: Config) {
+
try {
+
const configPath = await getConfigPath();
+
await moonlightNodeSandboxed.fs.writeFileString(configPath, JSON.stringify(config, null, 2));
+
} catch (e) {
+
logger.error("Failed to write config", e);
}
-
-
let config: Config = JSON.parse(fs.readFileSync(configPath, "utf8"));
-
-
// Assign the default values if they don't exist (newly added)
-
config = { ...defaultConfig, ...config };
-
writeConfig(config);
-
-
return config;
}
-
export function readConfig(): Config {
+
export async function readConfig(): Promise<Config> {
webPreload: {
return moonlightNode.config;
}
-
nodePreload: {
-
return readConfigNode();
-
}
+
const configPath = await getConfigPath();
+
if (!(await moonlightNodeSandboxed.fs.exists(configPath))) {
+
await writeConfig(defaultConfig);
+
return defaultConfig;
+
} else {
+
try {
+
let config: Config = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(configPath));
+
// Assign the default values if they don't exist (newly added)
+
config = { ...defaultConfig, ...config };
+
await writeConfig(config);
-
injector: {
-
return readConfigNode();
+
return config;
+
} catch (e) {
+
logger.error("Failed to read config, falling back to defaults", e);
+
// We don't want to write the default config here - if a user is manually
+
// editing their config and messes it up, we'll delete it all instead of
+
// letting them fix it
+
return defaultConfig;
+
}
}
-
-
throw new Error("Called readConfig() in an impossible environment");
}
+17
packages/core/src/cors.ts
···
+
const cors: string[] = [];
+
const blocked: string[] = [];
+
+
export function registerCors(url: string) {
+
cors.push(url);
+
}
+
+
export function registerBlocked(url: string) {
+
blocked.push(url);
+
}
+
+
export function getDynamicCors() {
+
return {
+
cors,
+
blocked
+
};
+
}
+127 -113
packages/core/src/extension/loader.ts
···
ExtensionWebExports,
DetectedExtension,
ProcessedExtensions,
-
WebpackModuleFunc
+
WebpackModuleFunc,
+
constants,
+
ExtensionManifest,
+
ExtensionEnvironment
} from "@moonlight-mod/types";
import { readConfig } from "../config";
import Logger from "../util/logger";
import { registerPatch, registerWebpackModule } from "../patch";
import calculateDependencies from "../util/dependency";
import { createEventEmitter } from "../util/event";
+
import { registerStyles } from "../styles";
+
import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event";
const logger = new Logger("core/extension/loader");
-
async function loadExt(ext: DetectedExtension) {
-
webPreload: {
-
if (ext.scripts.web != null) {
-
const source =
-
ext.scripts.web + "\n//# sourceURL=file:///" + ext.scripts.webPath;
-
const fn = new Function("require", "module", "exports", source);
+
function evalIIFE(id: string, source: string): ExtensionWebExports {
+
const fn = new Function("require", "module", "exports", source);
-
const module = { id: ext.id, exports: {} };
-
fn.apply(window, [
-
() => {
-
logger.warn("Attempted to require() from web");
-
},
-
module,
-
module.exports
-
]);
+
const module = { id, exports: {} };
+
fn.apply(window, [
+
() => {
+
logger.warn("Attempted to require() from web");
+
},
+
module,
+
module.exports
+
]);
-
const exports: ExtensionWebExports = module.exports;
-
if (exports.patches != null) {
-
let idx = 0;
-
for (const patch of exports.patches) {
-
if (Array.isArray(patch.replace)) {
-
for (const replacement of patch.replace) {
-
const newPatch = Object.assign({}, patch, {
-
replace: replacement
-
});
+
return module.exports;
+
}
-
registerPatch({ ...newPatch, ext: ext.id, id: idx });
-
idx++;
-
}
-
} else {
-
registerPatch({ ...patch, ext: ext.id, id: idx });
-
idx++;
-
}
+
async function evalEsm(source: string): Promise<ExtensionWebExports> {
+
// Data URLs (`data:`) don't seem to work under the CSP, but object URLs do
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
+
+
const module = await import(url);
+
+
URL.revokeObjectURL(url);
+
+
return module;
+
}
+
+
async function loadExtWeb(ext: DetectedExtension) {
+
if (ext.scripts.web != null) {
+
const source = ext.scripts.web + `\n//# sourceURL=${ext.id}/web.js`;
+
+
let exports: ExtensionWebExports;
+
+
try {
+
exports = evalIIFE(ext.id, source);
+
} catch {
+
logger.trace(`Failed to load IIFE for extension ${ext.id}, trying ESM loading`);
+
exports = await evalEsm(source);
+
}
+
+
if (exports.patches != null) {
+
let idx = 0;
+
for (const patch of exports.patches) {
+
if (Array.isArray(patch.replace)) {
+
registerPatch({ ...patch, ext: ext.id, id: idx });
+
} else {
+
registerPatch({ ...patch, replace: [patch.replace], ext: ext.id, id: idx });
}
+
idx++;
}
+
}
-
if (exports.webpackModules != null) {
-
for (const [name, wp] of Object.entries(exports.webpackModules)) {
-
if (wp.run == null && ext.scripts.webpackModules?.[name] != null) {
-
const func = new Function(
-
"module",
-
"exports",
-
"require",
-
ext.scripts.webpackModules[name]!
-
) as WebpackModuleFunc;
-
registerWebpackModule({
-
...wp,
-
ext: ext.id,
-
id: name,
-
run: func
-
});
-
} else {
-
registerWebpackModule({ ...wp, ext: ext.id, id: name });
-
}
+
if (exports.webpackModules != null) {
+
for (const [name, wp] of Object.entries(exports.webpackModules)) {
+
if (wp.run == null && ext.scripts.webpackModules?.[name] != null) {
+
const source = ext.scripts.webpackModules[name]! + `\n//# sourceURL=${ext.id}/webpackModules/${name}.js`;
+
const func = new Function("module", "exports", "require", source) as WebpackModuleFunc;
+
registerWebpackModule({
+
...wp,
+
ext: ext.id,
+
id: name,
+
run: func
+
});
+
} else {
+
registerWebpackModule({ ...wp, ext: ext.id, id: name });
}
}
+
}
+
+
if (exports.styles != null) {
+
registerStyles(exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`));
+
}
+
if (ext.scripts.style != null) {
+
registerStyles([`/* ${ext.id}#style.css */ ${ext.scripts.style}`]);
+
}
+
}
+
}
+
+
async function loadExt(ext: DetectedExtension) {
+
webTarget: {
+
try {
+
await loadExtWeb(ext);
+
} catch (e) {
+
logger.error(`Failed to load extension "${ext.id}"`, e);
}
}
···
}
}
+
export enum ExtensionCompat {
+
Compatible,
+
InvalidApiLevel,
+
InvalidEnvironment
+
}
+
+
export function checkExtensionCompat(manifest: ExtensionManifest): ExtensionCompat {
+
let environment;
+
webTarget: {
+
environment = ExtensionEnvironment.Web;
+
}
+
nodeTarget: {
+
environment = ExtensionEnvironment.Desktop;
+
}
+
+
if (manifest.apiLevel !== constants.apiLevel) return ExtensionCompat.InvalidApiLevel;
+
if ((manifest.environment ?? "both") !== "both" && manifest.environment !== environment)
+
return ExtensionCompat.InvalidEnvironment;
+
return ExtensionCompat.Compatible;
+
}
+
/*
This function resolves extensions and loads them, split into a few stages:
···
extensions fires an event on completion, which allows us to await the loading
of another extension, resolving dependencies & load order effectively.
*/
-
export async function loadExtensions(
-
exts: DetectedExtension[]
-
): Promise<ProcessedExtensions> {
+
export async function loadExtensions(exts: DetectedExtension[]): Promise<ProcessedExtensions> {
+
exts = exts.filter((ext) => checkExtensionCompat(ext.manifest) === ExtensionCompat.Compatible);
+
+
const config = await readConfig();
const items = exts
.map((ext) => {
return {
···
})
.sort((a, b) => a.id.localeCompare(b.id));
-
const [sorted, dependencyGraph] = calculateDependencies(
-
items,
-
-
function fetchDep(id) {
+
const [sorted, dependencyGraph] = calculateDependencies(items, {
+
fetchDep: (id) => {
return exts.find((x) => x.id === id) ?? null;
},
-
function getDeps(item) {
+
getDeps: (item) => {
return item.data.manifest.dependencies ?? [];
},
-
function getIncompatible(item) {
+
getIncompatible: (item) => {
return item.data.manifest.incompatible ?? [];
-
}
-
);
-
exts = sorted.map((x) => x.data);
-
-
logger.debug(
-
"Implicit dependency stage - extension list:",
-
exts.map((x) => x.id)
-
);
-
const config = readConfig();
-
const implicitlyEnabled: string[] = [];
-
-
function isEnabledInConfig(ext: DetectedExtension) {
-
if (implicitlyEnabled.includes(ext.id)) return true;
-
-
const entry = config.extensions[ext.id];
-
if (entry == null) return false;
-
-
if (entry === true) return true;
-
if (typeof entry === "object" && entry.enabled === true) return true;
-
-
return false;
-
}
-
-
function validateDeps(ext: DetectedExtension) {
-
if (isEnabledInConfig(ext)) {
-
const deps = dependencyGraph.get(ext.id)!;
-
for (const dep of deps.values()) {
-
validateDeps(exts.find((e) => e.id === dep)!);
-
}
-
} else {
-
const dependsOnMe = Array.from(dependencyGraph.entries()).filter(
-
([, v]) => v?.has(ext.id)
-
);
+
},
-
if (dependsOnMe.length > 0) {
-
logger.debug("Implicitly enabling extension", ext.id);
-
implicitlyEnabled.push(ext.id);
-
}
+
getEnabled: (item) => {
+
const entry = config.extensions[item.id];
+
if (entry == null) return false;
+
if (entry === true) return true;
+
if (typeof entry === "object" && entry.enabled === true) return true;
+
return false;
}
-
}
-
-
for (const ext of exts) validateDeps(ext);
-
exts = exts.filter((e) => isEnabledInConfig(e));
+
});
return {
-
extensions: exts,
+
extensions: sorted.map((x) => x.data),
dependencyGraph
};
}
-
export async function loadProcessedExtensions({
-
extensions,
-
dependencyGraph
-
}: ProcessedExtensions) {
-
const eventEmitter = createEventEmitter();
+
export async function loadProcessedExtensions({ extensions, dependencyGraph }: ProcessedExtensions) {
+
const eventEmitter = createEventEmitter<WebEventType, WebEventPayloads>();
const finished: Set<string> = new Set();
-
logger.debug(
+
logger.trace(
"Load stage - extension list:",
extensions.map((x) => x.id)
);
···
}
function done() {
-
eventEmitter.removeEventListener("ext-ready", cb);
+
eventEmitter.removeEventListener(WebEventType.ExtensionLoad, cb);
r();
}
-
eventEmitter.addEventListener("ext-ready", cb);
+
eventEmitter.addEventListener(WebEventType.ExtensionLoad, cb);
if (finished.has(dep)) done();
})
);
if (waitPromises.length > 0) {
-
logger.debug(
-
`Waiting on ${waitPromises.length} dependencies for "${ext.id}"`
-
);
+
logger.debug(`Waiting on ${waitPromises.length} dependencies for "${ext.id}"`);
await Promise.all(waitPromises);
}
···
await loadExt(ext);
finished.add(ext.id);
-
eventEmitter.dispatchEvent("ext-ready", ext.id);
+
eventEmitter.dispatchEvent(WebEventType.ExtensionLoad, ext.id);
logger.debug(`Loaded "${ext.id}"`);
}
-
webPreload: {
+
webTarget: {
for (const ext of extensions) {
moonlight.enabledExtensions.add(ext.id);
}
+131 -86
packages/core/src/extension.ts
···
-
import {
-
ExtensionManifest,
-
DetectedExtension,
-
ExtensionLoadSource,
-
constants
-
} from "@moonlight-mod/types";
+
import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, constants } from "@moonlight-mod/types";
import { readConfig } from "./config";
-
import requireImport from "./util/import";
import { getCoreExtensionsPath, getExtensionsPath } from "./util/data";
+
import Logger from "./util/logger";
+
+
const logger = new Logger("core/extension");
-
function findManifests(dir: string): string[] {
-
const fs = requireImport("fs");
-
const path = requireImport("path");
+
async function findManifests(dir: string): Promise<string[]> {
const ret = [];
-
for (const file of fs.readdirSync(dir)) {
-
if (file === "manifest.json") {
-
ret.push(path.join(dir, file));
-
}
+
if (await moonlightNodeSandboxed.fs.exists(dir)) {
+
for (const file of await moonlightNodeSandboxed.fs.readdir(dir)) {
+
const path = moonlightNodeSandboxed.fs.join(dir, file);
+
if (file === "manifest.json") {
+
ret.push(path);
+
}
-
if (fs.statSync(path.join(dir, file)).isDirectory()) {
-
ret.push(...findManifests(path.join(dir, file)));
+
if (!(await moonlightNodeSandboxed.fs.isFile(path))) {
+
ret.push(...(await findManifests(path)));
+
}
}
}
return ret;
}
-
function loadDetectedExtensions(
+
async function loadDetectedExtensions(
dir: string,
-
type: ExtensionLoadSource
-
): DetectedExtension[] {
-
const fs = requireImport("fs");
-
const path = requireImport("path");
+
type: ExtensionLoadSource,
+
seen: Set<string>
+
): Promise<DetectedExtension[]> {
const ret: DetectedExtension[] = [];
-
const manifests = findManifests(dir);
+
const manifests = await findManifests(dir);
for (const manifestPath of manifests) {
-
if (!fs.existsSync(manifestPath)) continue;
-
const dir = path.dirname(manifestPath);
+
try {
+
if (!(await moonlightNodeSandboxed.fs.exists(manifestPath))) continue;
+
const dir = moonlightNodeSandboxed.fs.dirname(manifestPath);
+
+
const manifest: ExtensionManifest = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(manifestPath));
+
if (seen.has(manifest.id)) {
+
logger.warn(`Duplicate extension found, skipping: ${manifest.id}`);
+
continue;
+
}
+
seen.add(manifest.id);
+
+
const webPath = moonlightNodeSandboxed.fs.join(dir, "index.js");
+
const nodePath = moonlightNodeSandboxed.fs.join(dir, "node.js");
+
const hostPath = moonlightNodeSandboxed.fs.join(dir, "host.js");
-
const manifest: ExtensionManifest = JSON.parse(
-
fs.readFileSync(manifestPath, "utf8")
-
);
+
// if none exist (empty manifest) don't give a shit
+
if (
+
!moonlightNodeSandboxed.fs.exists(webPath) &&
+
!moonlightNodeSandboxed.fs.exists(nodePath) &&
+
!moonlightNodeSandboxed.fs.exists(hostPath)
+
) {
+
continue;
+
}
+
+
const web = (await moonlightNodeSandboxed.fs.exists(webPath))
+
? await moonlightNodeSandboxed.fs.readFileString(webPath)
+
: undefined;
-
const webPath = path.join(dir, "index.js");
-
const nodePath = path.join(dir, "node.js");
-
const hostPath = path.join(dir, "host.js");
+
let url: string | undefined = undefined;
+
const urlPath = moonlightNodeSandboxed.fs.join(dir, constants.repoUrlFile);
+
if (type === ExtensionLoadSource.Normal && (await moonlightNodeSandboxed.fs.exists(urlPath))) {
+
url = await moonlightNodeSandboxed.fs.readFileString(urlPath);
+
}
-
// if none exist (empty manifest) don't give a shit
-
if (
-
!fs.existsSync(webPath) &&
-
!fs.existsSync(nodePath) &&
-
!fs.existsSync(hostPath)
-
) {
-
continue;
+
const wpModules: Record<string, string> = {};
+
const wpModulesPath = moonlightNodeSandboxed.fs.join(dir, "webpackModules");
+
if (await moonlightNodeSandboxed.fs.exists(wpModulesPath)) {
+
const wpModulesFile = await moonlightNodeSandboxed.fs.readdir(wpModulesPath);
+
+
for (const wpModuleFile of wpModulesFile) {
+
if (wpModuleFile.endsWith(".js")) {
+
wpModules[wpModuleFile.replace(".js", "")] = await moonlightNodeSandboxed.fs.readFileString(
+
moonlightNodeSandboxed.fs.join(wpModulesPath, wpModuleFile)
+
);
+
}
+
}
+
}
+
+
const stylePath = moonlightNodeSandboxed.fs.join(dir, "style.css");
+
+
ret.push({
+
id: manifest.id,
+
manifest,
+
source: {
+
type,
+
url
+
},
+
scripts: {
+
web,
+
webPath: web != null ? webPath : undefined,
+
webpackModules: wpModules,
+
nodePath: (await moonlightNodeSandboxed.fs.exists(nodePath)) ? nodePath : undefined,
+
hostPath: (await moonlightNodeSandboxed.fs.exists(hostPath)) ? hostPath : undefined,
+
style: (await moonlightNodeSandboxed.fs.exists(stylePath))
+
? await moonlightNodeSandboxed.fs.readFileString(stylePath)
+
: undefined
+
}
+
});
+
} catch (err) {
+
logger.error(`Failed to load extension from "${manifestPath}":`, err);
}
+
}
-
const web = fs.existsSync(webPath)
-
? fs.readFileSync(webPath, "utf8")
-
: undefined;
+
return ret;
+
}
-
let url: string | undefined = undefined;
-
const urlPath = path.join(dir, constants.repoUrlFile);
-
if (type === ExtensionLoadSource.Normal && fs.existsSync(urlPath)) {
-
url = fs.readFileSync(urlPath, "utf8");
-
}
+
async function getExtensionsNative(): Promise<DetectedExtension[]> {
+
const config = await readConfig();
+
const res = [];
+
const seen = new Set<string>();
-
const wpModules: Record<string, string> = {};
-
const wpModulesPath = path.join(dir, "webpackModules");
-
if (fs.existsSync(wpModulesPath)) {
-
const wpModulesFile = fs.readdirSync(wpModulesPath);
+
res.push(...(await loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core, seen)));
+
+
for (const devSearchPath of config.devSearchPaths ?? []) {
+
res.push(...(await loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer, seen)));
+
}
+
+
res.push(...(await loadDetectedExtensions(await getExtensionsPath(), ExtensionLoadSource.Normal, seen)));
+
+
return res;
+
}
+
+
async function getExtensionsBrowser(): Promise<DetectedExtension[]> {
+
const ret: DetectedExtension[] = [];
+
const seen = new Set<string>();
-
for (const wpModuleFile of wpModulesFile) {
-
if (wpModuleFile.endsWith(".js")) {
-
wpModules[wpModuleFile.replace(".js", "")] = fs.readFileSync(
-
path.join(wpModulesPath, wpModuleFile),
-
"utf8"
-
);
-
}
+
const coreExtensionsFs: Record<string, string> = JSON.parse(_moonlight_coreExtensionsStr);
+
const coreExtensions = Array.from(new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0])));
+
+
for (const ext of coreExtensions) {
+
if (!coreExtensionsFs[`${ext}/index.js`]) continue;
+
const manifest = JSON.parse(coreExtensionsFs[`${ext}/manifest.json`]);
+
const web = coreExtensionsFs[`${ext}/index.js`];
+
+
const wpModules: Record<string, string> = {};
+
const wpModulesPath = `${ext}/webpackModules`;
+
for (const wpModuleFile of Object.keys(coreExtensionsFs)) {
+
if (wpModuleFile.startsWith(wpModulesPath)) {
+
wpModules[wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "")] = coreExtensionsFs[wpModuleFile];
}
}
···
id: manifest.id,
manifest,
source: {
-
type,
-
url
+
type: ExtensionLoadSource.Core
},
scripts: {
web,
-
webPath: web != null ? webPath : undefined,
webpackModules: wpModules,
-
nodePath: fs.existsSync(nodePath) ? nodePath : undefined,
-
hostPath: fs.existsSync(hostPath) ? hostPath : undefined
+
style: coreExtensionsFs[`${ext}/style.css`]
}
});
+
seen.add(manifest.id);
}
-
return ret;
-
}
-
-
function getExtensionsNative(): DetectedExtension[] {
-
const config = readConfig();
-
const res = [];
-
-
res.push(
-
...loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core)
-
);
-
-
res.push(
-
...loadDetectedExtensions(getExtensionsPath(), ExtensionLoadSource.Normal)
-
);
-
-
for (const devSearchPath of config.devSearchPaths ?? []) {
-
res.push(
-
...loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer)
-
);
+
if (await moonlightNodeSandboxed.fs.exists("/extensions")) {
+
ret.push(...(await loadDetectedExtensions("/extensions", ExtensionLoadSource.Normal, seen)));
}
-
return res;
+
return ret;
}
-
export function getExtensions(): DetectedExtension[] {
+
export async function getExtensions(): Promise<DetectedExtension[]> {
webPreload: {
return moonlightNode.extensions;
}
-
nodePreload: {
-
return getExtensionsNative();
+
browser: {
+
return await getExtensionsBrowser();
}
-
injector: {
-
return getExtensionsNative();
+
nodeTarget: {
+
return await getExtensionsNative();
}
throw new Error("Called getExtensions() outside of node-preload/web-preload");
+56
packages/core/src/fs.ts
···
+
import type { MoonlightFS } from "@moonlight-mod/types";
+
import requireImport from "./util/import";
+
+
export default function createFS(): MoonlightFS {
+
const fs = requireImport("fs");
+
const path = requireImport("path");
+
+
return {
+
async readFile(path) {
+
const file = fs.readFileSync(path);
+
return new Uint8Array(file);
+
},
+
async readFileString(path) {
+
return fs.readFileSync(path, "utf8");
+
},
+
async writeFile(path, data) {
+
fs.writeFileSync(path, Buffer.from(data));
+
},
+
async writeFileString(path, data) {
+
fs.writeFileSync(path, data, "utf8");
+
},
+
async unlink(path) {
+
fs.unlinkSync(path);
+
},
+
+
async readdir(path) {
+
return fs.readdirSync(path);
+
},
+
async mkdir(path) {
+
fs.mkdirSync(path, { recursive: true });
+
},
+
async rmdir(path) {
+
fs.rmSync(path, { recursive: true });
+
},
+
+
async exists(path) {
+
return fs.existsSync(path);
+
},
+
async isFile(path) {
+
return fs.statSync(path).isFile();
+
},
+
async isDir(path) {
+
return fs.statSync(path).isDirectory();
+
},
+
+
join(...parts) {
+
return path.join(...parts);
+
},
+
dirname(dir) {
+
return path.dirname(dir);
+
},
+
basename(dir) {
+
return path.basename(dir);
+
}
+
};
+
}
+270 -119
packages/core/src/patch.ts
···
IdentifiedWebpackModule,
WebpackJsonp,
WebpackJsonpEntry,
-
WebpackModuleFunc
+
WebpackModuleFunc,
+
WebpackRequireType
} from "@moonlight-mod/types";
import Logger from "./util/logger";
import calculateDependencies, { Dependency } from "./util/dependency";
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
+
import { WebEventType } from "@moonlight-mod/types/core/event";
+
import { processFind, processReplace, testFind } from "./util/patch";
const logger = new Logger("core/patch");
// Can't be Set because we need splice
const patches: IdentifiedPatch[] = [];
let webpackModules: Set<IdentifiedWebpackModule> = new Set();
+
let webpackRequire: WebpackRequireType | null = null;
+
+
const moduleLoadSubscriptions: Map<string, ((moduleId: string) => void)[]> = new Map();
export function registerPatch(patch: IdentifiedPatch) {
+
patch.find = processFind(patch.find);
+
processReplace(patch.replace);
+
patches.push(patch);
+
moonlight.unpatched.add(patch);
}
export function registerWebpackModule(wp: IdentifiedWebpackModule) {
webpackModules.add(wp);
+
if (wp.dependencies?.length) {
+
moonlight.pendingModules.add(wp);
+
}
+
}
+
+
export function onModuleLoad(module: string | string[], callback: (moduleId: string) => void): void {
+
let moduleIds = module;
+
+
if (typeof module === "string") {
+
moduleIds = [module];
+
}
+
+
for (const moduleId of moduleIds) {
+
if (moduleLoadSubscriptions.has(moduleId)) {
+
moduleLoadSubscriptions.get(moduleId)?.push(callback);
+
} else {
+
moduleLoadSubscriptions.set(moduleId, [callback]);
+
}
+
}
}
/*
···
const moduleCache: Record<string, string> = {};
const patched: Record<string, Array<string>> = {};
+
function createSourceURL(id: string) {
+
const remapped = Object.entries(moonlight.moonmap.modules).find((m) => m[1] === id)?.[0];
+
+
if (remapped) {
+
return `// Webpack Module: ${id}\n//# sourceURL=${remapped}`;
+
}
+
+
return `//# sourceURL=Webpack-Module/${id.slice(0, 3)}/${id}`;
+
}
+
+
function patchModule(id: string, patchId: string, replaced: string, entry: WebpackJsonpEntry[1]) {
+
// Store what extensions patched what modules for easier debugging
+
patched[id] = patched[id] ?? [];
+
patched[id].push(patchId);
+
+
// Webpack module arguments are minified, so we replace them with consistent names
+
// We have to wrap it so things don't break, though
+
const patchedStr = patched[id].sort().join(", ");
+
+
const wrapped =
+
`(${replaced}).apply(this, arguments)\n` + `// Patched by moonlight: ${patchedStr}\n` + createSourceURL(id);
+
+
try {
+
const func = new Function("module", "exports", "require", wrapped) as WebpackModuleFunc;
+
entry[id] = func;
+
entry[id].__moonlight = true;
+
return true;
+
} catch (e) {
+
logger.warn("Error constructing function for patch", patchId, e);
+
patched[id].pop();
+
return false;
+
}
+
}
+
function patchModules(entry: WebpackJsonpEntry[1]) {
+
// Populate the module cache
+
for (const [id, func] of Object.entries(entry)) {
+
if (!Object.hasOwn(moduleCache, id) && func.__moonlight !== true) {
+
moduleCache[id] = func.toString().replace(/\n/g, "");
+
moonlight.moonmap.parseScript(id, moduleCache[id]);
+
}
+
}
+
for (const [id, func] of Object.entries(entry)) {
-
let moduleString = Object.prototype.hasOwnProperty.call(moduleCache, id)
-
? moduleCache[id]
-
: func.toString().replace(/\n/g, "");
+
if (func.__moonlight === true) continue;
-
for (const patch of patches) {
+
// Clone the module string so finds don't get messed up by other extensions
+
const origModuleString = moduleCache[id];
+
let moduleString = origModuleString;
+
const patchedStr = [];
+
const mappedName = Object.entries(moonlight.moonmap.modules).find((m) => m[1] === id)?.[0];
+
let modified = false;
+
let swappedModule = false;
+
+
const exts = new Set<string>();
+
+
for (let i = 0; i < patches.length; i++) {
+
const patch = patches[i];
if (patch.prerequisite != null && !patch.prerequisite()) {
+
moonlight.unpatched.delete(patch);
continue;
}
···
patch.find.lastIndex = 0;
}
-
// indexOf is faster than includes by 0.25% lmao
-
const match =
-
typeof patch.find === "string"
-
? moduleString.indexOf(patch.find) !== -1
-
: patch.find.test(moduleString);
+
const match = testFind(origModuleString, patch.find) || patch.find === mappedName;
// Global regexes apply to all modules
-
const shouldRemove =
-
typeof patch.find === "string" ? true : !patch.find.global;
+
const shouldRemove = typeof patch.find === "string" ? true : !patch.find.global;
+
let replaced = moduleString;
+
let hardFailed = false;
if (match) {
-
moonlight.unpatched.delete(patch);
+
// We ensured normal PatchReplace objects get turned into arrays on register
+
const replaces = patch.replace as PatchReplace[];
-
// We ensured all arrays get turned into normal PatchReplace objects on register
-
const replace = patch.replace as PatchReplace;
+
let isPatched = true;
+
for (let i = 0; i < replaces.length; i++) {
+
const replace = replaces[i];
+
let patchId = `${patch.ext}#${patch.id}`;
+
if (replaces.length > 1) patchId += `#${i}`;
+
patchedStr.push(patchId);
-
if (
-
replace.type === undefined ||
-
replace.type === PatchReplaceType.Normal
-
) {
-
// tsc fails to detect the overloads for this, so I'll just do this
-
// Verbose, but it works
-
let replaced;
-
if (typeof replace.replacement === "string") {
-
replaced = moduleString.replace(replace.match, replace.replacement);
-
} else {
-
replaced = moduleString.replace(replace.match, replace.replacement);
-
}
+
if (replace.type === undefined || replace.type === PatchReplaceType.Normal) {
+
// tsc fails to detect the overloads for this, so I'll just do this
+
// Verbose, but it works
+
if (typeof replace.replacement === "string") {
+
replaced = replaced.replace(replace.match, replace.replacement);
+
} else {
+
replaced = replaced.replace(replace.match, replace.replacement);
+
}
-
if (replaced === moduleString) {
-
logger.warn("Patch replacement failed", id, patch);
-
continue;
+
if (replaced === moduleString) {
+
logger.warn("Patch replacement failed", id, patchId, patch);
+
isPatched = false;
+
if (patch.hardFail) {
+
hardFailed = true;
+
break;
+
} else {
+
continue;
+
}
+
}
+
} else if (replace.type === PatchReplaceType.Module) {
+
// Directly replace the module with a new one
+
const newModule = replace.replacement(replaced);
+
entry[id] = newModule;
+
entry[id].__moonlight = true;
+
replaced = newModule.toString().replace(/\n/g, "");
+
swappedModule = true;
}
+
}
-
// Store what extensions patched what modules for easier debugging
-
patched[id] = patched[id] || [];
-
patched[id].push(`${patch.ext}#${patch.id}`);
+
if (!hardFailed) {
+
moduleString = replaced;
+
modified = true;
+
exts.add(patch.ext);
+
}
-
// Webpack module arguments are minified, so we replace them with consistent names
-
// We have to wrap it so things don't break, though
-
const patchedStr = patched[id].sort().join(", ");
+
if (isPatched) moonlight.unpatched.delete(patch);
+
if (shouldRemove) patches.splice(i--, 1);
+
}
+
}
-
const wrapped =
-
`(${replaced}).apply(this, arguments)\n` +
-
`// Patched by moonlight: ${patchedStr}\n` +
-
`//# sourceURL=Webpack-Module-${id}`;
+
if (modified) {
+
let shouldCache = true;
+
if (!swappedModule) shouldCache = patchModule(id, patchedStr.join(", "), moduleString, entry);
+
if (shouldCache) moduleCache[id] = moduleString;
+
moonlight.patched.set(id, exts);
+
}
-
try {
-
const func = new Function(
-
"module",
-
"exports",
-
"require",
-
wrapped
-
) as WebpackModuleFunc;
-
entry[id] = func;
-
entry[id].__moonlight = true;
-
moduleString = replaced;
-
} catch (e) {
-
logger.warn("Error constructing function for patch", e);
+
try {
+
const parsed = moonlight.lunast.parseScript(id, moduleString);
+
if (parsed != null) {
+
for (const [parsedId, parsedScript] of Object.entries(parsed)) {
+
if (patchModule(parsedId, "lunast", parsedScript, entry)) {
+
moduleCache[parsedId] = parsedScript;
}
-
} else if (replace.type === PatchReplaceType.Module) {
-
// Directly replace the module with a new one
-
const newModule = replace.replacement(moduleString);
-
entry[id] = newModule;
-
entry[id].__moonlight = true;
-
moduleString =
-
newModule.toString().replace(/\n/g, "") +
-
`//# sourceURL=Webpack-Module-${id}`;
-
}
-
-
if (shouldRemove) {
-
patches.splice(
-
patches.findIndex((p) => p.ext === patch.ext && p.id === patch.id),
-
1
-
);
}
}
+
} catch (e) {
+
logger.error("Failed to parse script for LunAST", id, e);
}
if (moonlightNode.config.patchAll === true) {
-
if (
-
(typeof id !== "string" || !id.includes("_")) &&
-
!entry[id].__moonlight
-
) {
-
const wrapped =
-
`(${moduleString}).apply(this, arguments)\n` +
-
`//# sourceURL=Webpack-Module-${id}`;
-
entry[id] = new Function(
-
"module",
-
"exports",
-
"require",
-
wrapped
-
) as WebpackModuleFunc;
+
if ((typeof id !== "string" || !id.includes("_")) && !entry[id].__moonlight) {
+
const wrapped = `(${moduleCache[id]}).apply(this, arguments)\n` + createSourceURL(id);
+
entry[id] = new Function("module", "exports", "require", wrapped) as WebpackModuleFunc;
entry[id].__moonlight = true;
}
+
}
+
+
// Dispatch module load event subscription
+
if (moduleLoadSubscriptions.has(id)) {
+
const loadCallbacks = moduleLoadSubscriptions.get(id)!;
+
for (const callback of loadCallbacks) {
+
try {
+
callback(id);
+
} catch (e) {
+
logger.error("Error in module load subscription: " + e);
+
}
+
}
+
moduleLoadSubscriptions.delete(id);
}
moduleCache[id] = moduleString;
···
*/
let chunkId = Number.MAX_SAFE_INTEGER;
+
function depToString(x: ExplicitExtensionDependency) {
+
return x.ext != null ? `${x.ext}_${x.id}` : x.id;
+
}
+
function handleModuleDependencies() {
const modules = Array.from(webpackModules.values());
-
const dependencies: Dependency<string, IdentifiedWebpackModule>[] =
-
modules.map((wp) => {
-
return {
-
id: `${wp.ext}_${wp.id}`,
-
data: wp
-
};
-
});
-
-
const [sorted, _] = calculateDependencies(
-
dependencies,
+
const dependencies: Dependency<string, IdentifiedWebpackModule>[] = modules.map((wp) => {
+
return {
+
id: depToString(wp),
+
data: wp
+
};
+
});
-
function fetchDep(id) {
-
return modules.find((x) => id === `${x.ext}_${x.id}`) ?? null;
+
const [sorted, _] = calculateDependencies(dependencies, {
+
fetchDep: (id) => {
+
return modules.find((x) => id === depToString(x)) ?? null;
},
-
function getDeps(item) {
+
getDeps: (item) => {
const deps = item.data?.dependencies ?? [];
return (
deps.filter(
-
(dep) => !(dep instanceof RegExp || typeof dep === "string")
+
(dep) => !(dep instanceof RegExp || typeof dep === "string") && dep.ext != null
) as ExplicitExtensionDependency[]
-
).map((x) => `${x.ext}_${x.id}`);
+
).map(depToString);
}
-
);
+
});
webpackModules = new Set(sorted.map((x) => x.data));
}
···
for (const [_modId, mod] of Object.entries(entry)) {
const modStr = mod.toString();
-
const wpModules = Array.from(webpackModules.values());
-
for (const wpModule of wpModules) {
-
const id = wpModule.ext + "_" + wpModule.id;
+
for (const wpModule of webpackModules) {
+
const id = depToString(wpModule);
if (wpModule.dependencies) {
const deps = new Set(wpModule.dependencies);
// FIXME: This dependency resolution might fail if the things we want
// got injected earlier. If weird dependencies fail, this is likely why.
if (deps.size) {
-
for (const dep of deps.values()) {
+
for (const dep of deps) {
if (typeof dep === "string") {
if (modStr.includes(dep)) deps.delete(dep);
} else if (dep instanceof RegExp) {
if (dep.test(modStr)) deps.delete(dep);
} else if (
-
injectedWpModules.find(
-
(x) => x.ext === dep.ext && x.id === dep.id
-
)
+
dep.ext != null
+
? injectedWpModules.find((x) => x.ext === dep.ext && x.id === dep.id)
+
: injectedWpModules.find((x) => x.id === dep.id)
) {
deps.delete(dep);
}
}
+
wpModule.dependencies = Array.from(deps);
if (deps.size !== 0) {
-
// Update the deps that have passed
-
webpackModules.delete(wpModule);
-
wpModule.dependencies = Array.from(deps);
-
webpackModules.add(wpModule);
continue;
}
-
-
wpModule.dependencies = Array.from(deps);
}
}
webpackModules.delete(wpModule);
+
moonlight.pendingModules.delete(wpModule);
injectedWpModules.push(wpModule);
inject = true;
-
if (wpModule.run) modules[id] = wpModule.run;
-
if (wpModule.entrypoint) entrypoints.push(id);
+
if (wpModule.run) {
+
modules[id] = wpModule.run;
+
wpModule.run.__moonlight = true;
+
// @ts-expect-error hacks
+
wpModule.run.call = function (self, module, exports, require) {
+
try {
+
wpModule.run!.apply(self, [module, exports, require]);
+
} catch (err) {
+
logger.error(`Failed to run module "${id}":`, err);
+
}
+
};
+
if (wpModule.entrypoint) entrypoints.push(id);
+
}
}
if (!webpackModules.size) break;
}
+
for (const [name, func] of Object.entries(moonlight.moonmap.getWebpackModules("window.moonlight.moonmap"))) {
+
// @ts-expect-error probably should fix the type on this idk
+
func.__moonlight = true;
+
injectedWpModules.push({ id: name, run: func });
+
modules[name] = func;
+
inject = true;
+
}
+
+
if (webpackRequire != null) {
+
for (const id of moonlight.moonmap.getLazyModules()) {
+
webpackRequire.e(id);
+
}
+
}
+
if (inject) {
logger.debug("Injecting modules:", modules, entrypoints);
window.webpackChunkdiscord_app.push([
[--chunkId],
modules,
-
(require: typeof WebpackRequire) => entrypoints.map(require)
+
(require: WebpackRequireType) =>
+
entrypoints.map((id) => {
+
try {
+
if (require.m[id] == null) {
+
logger.error(`Failing to load entrypoint module "${id}" because it's not found in Webpack.`);
+
} else {
+
require(id);
+
}
+
} catch (err) {
+
logger.error(`Failed to load entrypoint module "${id}":`, err);
+
}
+
})
]);
}
}
···
}
}
+
function moduleSourceGetter(id: string) {
+
return moduleCache[id] ?? null;
+
}
+
/*
Webpack modules are bundled into an array of arrays that hold each function.
Since we run code before Discord, we can create our own Webpack array and
···
export async function installWebpackPatcher() {
await handleModuleDependencies();
+
moonlight.lunast.setModuleSourceGetter(moduleSourceGetter);
+
moonlight.moonmap.setModuleSourceGetter(moduleSourceGetter);
+
+
const wpRequireFetcher: WebpackModuleFunc = (module, exports, require) => {
+
webpackRequire = require;
+
};
+
wpRequireFetcher.__moonlight = true;
+
webpackModules.add({
+
id: "moonlight",
+
entrypoint: true,
+
run: wpRequireFetcher
+
});
+
let realWebpackJsonp: WebpackJsonp | null = null;
Object.defineProperty(window, "webpackChunkdiscord_app", {
set: (jsonp: WebpackJsonp) => {
···
const realPush = jsonp.push;
if (jsonp.push.__moonlight !== true) {
jsonp.push = (items) => {
+
moonlight.events.dispatchEvent(WebEventType.ChunkLoad, {
+
chunkId: items[0],
+
modules: items[1],
+
require: items[2]
+
});
+
patchModules(items[1]);
try {
···
}
});
-
registerWebpackModule({
-
ext: "moonlight",
-
id: "fix_rspack_init_modules",
-
entrypoint: true,
-
run: function (module, exports, require) {
-
patchModules(require.m);
+
Object.defineProperty(Function.prototype, "m", {
+
configurable: true,
+
set(modules: any) {
+
const { stack } = new Error();
+
if (stack!.includes("/assets/") && !Array.isArray(modules)) {
+
moonlight.events.dispatchEvent(WebEventType.ChunkLoad, {
+
modules: modules
+
});
+
patchModules(modules);
+
+
if (!window.webpackChunkdiscord_app) window.webpackChunkdiscord_app = [];
+
injectModules(modules);
+
}
+
+
Object.defineProperty(this, "m", {
+
value: modules,
+
configurable: true,
+
enumerable: true,
+
writable: true
+
});
}
});
}
+49
packages/core/src/persist.ts
···
+
import { join, dirname } from "node:path";
+
import { mkdirSync, renameSync, existsSync, copyFileSync, readdirSync } from "node:fs";
+
import Logger from "./util/logger";
+
+
const logger = new Logger("core/persist");
+
+
export default function persist(asarPath: string) {
+
try {
+
if (process.platform === "win32") {
+
persistWin32(asarPath);
+
}
+
} catch (e) {
+
logger.error(`Failed to persist moonlight: ${e}`);
+
}
+
}
+
+
function persistWin32(asarPath: string) {
+
const updaterModule = require(join(asarPath, "common", "updater"));
+
const updater = updaterModule.Updater;
+
+
const currentAppDir = join(dirname(asarPath), "app");
+
+
const realEmit = updater.prototype.emit;
+
updater.prototype.emit = function (event: string, ...args: any[]) {
+
if (event === "host-updated") {
+
const versions = this.queryCurrentVersionsSync();
+
+
const newRootDir = join(this.rootPath, "app-" + versions.current_host.map((v: number) => v.toString()).join("."));
+
logger.info(`Persisting moonlight - new root dir: ${newRootDir}`);
+
+
const newResources = join(newRootDir, "resources");
+
+
// app.asar -> _app.asar
+
const newAsar = join(newResources, "app.asar");
+
const newRenamedAsar = join(newResources, "_app.asar");
+
if (!existsSync(newRenamedAsar)) renameSync(newAsar, newRenamedAsar);
+
+
// copy the already existing app dir so we don't have to figure out the moonlight dir
+
const newAppDir = join(newResources, "app");
+
if (!existsSync(newAppDir)) mkdirSync(newAppDir);
+
+
for (const file of readdirSync(currentAppDir)) {
+
copyFileSync(join(currentAppDir, file), join(newAppDir, file));
+
}
+
}
+
+
return realEmit.call(this, event, ...args);
+
};
+
}
+13
packages/core/src/styles.ts
···
+
const styles: string[] = [];
+
+
export function registerStyles(style: string[]) {
+
styles.push(...style);
+
}
+
+
export function installStyles() {
+
for (const style of styles) {
+
const el = document.createElement("style");
+
el.textContent = style;
+
document.documentElement.appendChild(el);
+
}
+
}
+63
packages/core/src/util/binary.ts
···
+
// https://github.com/NotNite/brc-save-editor/blob/main/src/lib/binary.ts
+
export interface BinaryInterface {
+
data: Uint8Array;
+
view: DataView;
+
length: number;
+
position: number;
+
}
+
+
export class BinaryReader implements BinaryInterface {
+
data: Uint8Array;
+
view: DataView;
+
length: number;
+
position: number;
+
+
constructor(data: Uint8Array) {
+
this.data = data;
+
this.view = new DataView(data.buffer);
+
+
this.length = data.length;
+
this.position = 0;
+
}
+
+
readByte() {
+
return this._read(this.view.getInt8, 1);
+
}
+
+
readBoolean() {
+
return this.readByte() !== 0;
+
}
+
+
readInt32() {
+
return this._read(this.view.getInt32, 4);
+
}
+
+
readUInt32() {
+
return this._read(this.view.getUint32, 4);
+
}
+
+
readSingle() {
+
return this._read(this.view.getFloat32, 4);
+
}
+
+
readInt64() {
+
return this._read(this.view.getBigInt64, 8);
+
}
+
+
readString(length: number) {
+
const result = this.read(length);
+
return new TextDecoder().decode(result);
+
}
+
+
read(length: number) {
+
const data = this.data.subarray(this.position, this.position + length);
+
this.position += length;
+
return data;
+
}
+
+
private _read<T>(func: (position: number, littleEndian?: boolean) => T, length: number): T {
+
const result = func.call(this.view, this.position, true);
+
this.position += length;
+
return result;
+
}
+
}
packages/core/src/util/clone.ts

This is a binary file and will not be displayed.

+39
packages/core/src/util/config.ts
···
+
import type { Config, DetectedExtension, ExtensionManifest } from "@moonlight-mod/types";
+
+
export function getManifest(extensions: DetectedExtension[], ext: string) {
+
return extensions.find((x) => x.id === ext)?.manifest;
+
}
+
+
export function getConfig(ext: string, config: Config) {
+
const val = config.extensions[ext];
+
if (val == null || typeof val === "boolean") return undefined;
+
return val.config;
+
}
+
+
export function getConfigOption<T>(
+
ext: string,
+
key: string,
+
config: Config,
+
settings?: ExtensionManifest["settings"]
+
): T | undefined {
+
const defaultValue: T | undefined = structuredClone(settings?.[key]?.default);
+
const cfg = getConfig(ext, config);
+
if (cfg == null || typeof cfg === "boolean") return defaultValue;
+
return cfg?.[key] ?? defaultValue;
+
}
+
+
export function setConfigOption<T>(config: Config, ext: string, key: string, value: T) {
+
const oldConfig = config.extensions[ext];
+
const newConfig =
+
typeof oldConfig === "boolean"
+
? {
+
enabled: oldConfig,
+
config: { [key]: value }
+
}
+
: {
+
...oldConfig,
+
config: { ...(oldConfig?.config ?? {}), [key]: value }
+
};
+
+
config.extensions[ext] = newConfig;
+
}
+29 -26
packages/core/src/util/data.ts
···
import { constants } from "@moonlight-mod/types";
-
import requireImport from "./import";
+
+
export async function getMoonlightDir() {
+
browser: {
+
return "/";
+
}
-
export function getMoonlightDir(): string {
const electron = require("electron");
-
const fs = requireImport("fs");
-
const path = requireImport("path");
let appData = "";
injector: {
···
appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData);
}
-
const dir = path.join(appData, "moonlight-mod");
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
+
const dir = moonlightNodeSandboxed.fs.join(appData, "moonlight-mod");
+
if (!(await moonlightNodeSandboxed.fs.exists(dir))) await moonlightNodeSandboxed.fs.mkdir(dir);
return dir;
}
···
version: string;
};
-
export function getConfigPath(): string {
-
const dir = getMoonlightDir();
-
const fs = requireImport("fs");
-
const path = requireImport("path");
+
export async function getConfigPath() {
+
browser: {
+
return "/config.json";
+
}
-
const buildInfoPath = path.join(process.resourcesPath, "build_info.json");
-
const buildInfo: BuildInfo = JSON.parse(
-
fs.readFileSync(buildInfoPath, "utf8")
-
);
+
const dir = await getMoonlightDir();
+
+
let configPath = "";
+
+
const buildInfoPath = moonlightNodeSandboxed.fs.join(process.resourcesPath, "build_info.json");
+
if (!(await moonlightNodeSandboxed.fs.exists(buildInfoPath))) {
+
configPath = moonlightNodeSandboxed.fs.join(dir, "desktop.json");
+
} else {
+
const buildInfo: BuildInfo = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(buildInfoPath));
+
configPath = moonlightNodeSandboxed.fs.join(dir, buildInfo.releaseChannel + ".json");
+
}
-
const configPath = path.join(dir, buildInfo.releaseChannel + ".json");
return configPath;
}
-
function getPathFromMoonlight(...names: string[]): string {
-
const dir = getMoonlightDir();
-
const fs = requireImport("fs");
-
const path = requireImport("path");
+
async function getPathFromMoonlight(...names: string[]) {
+
const dir = await getMoonlightDir();
-
const target = path.join(dir, ...names);
-
if (!fs.existsSync(target)) fs.mkdirSync(target);
+
const target = moonlightNodeSandboxed.fs.join(dir, ...names);
+
if (!(await moonlightNodeSandboxed.fs.exists(target))) await moonlightNodeSandboxed.fs.mkdir(target);
return target;
}
-
export function getExtensionsPath(): string {
-
return getPathFromMoonlight(constants.extensionsDir);
+
export async function getExtensionsPath() {
+
return await getPathFromMoonlight(constants.extensionsDir);
}
export function getCoreExtensionsPath(): string {
-
const path = requireImport("path");
-
const a = path.join(__dirname, constants.coreExtensionsDir);
-
return a;
+
return moonlightNodeSandboxed.fs.join(__dirname, constants.coreExtensionsDir);
}
+104 -31
packages/core/src/util/dependency.ts
···
import Logger from "./logger";
+
const logger = new Logger("core/util/dependency");
+
export type Dependency<T, D> = {
id: T;
data: D;
};
+
type Dependencies<T, D> = Dependency<T, D>[];
+
type DependencyGraph<T> = Map<T, Set<T> | null>;
-
const logger = new Logger("core/util/dependency");
-
-
export default function calculateDependencies<T, D>(
-
origItems: Dependency<T, D>[],
-
fetchDep: (id: T) => D | null,
-
getDeps: (item: Dependency<T, D>) => T[],
-
getIncompatible?: (item: Dependency<T, D>) => T[]
-
): [Dependency<T, D>[], Map<T, Set<T> | null>] {
-
logger.trace("sortDependencies begin", origItems);
-
let items = [...origItems];
-
-
if (getIncompatible != null) {
-
for (const item of items) {
-
const incompatibleItems = getIncompatible(item);
-
for (const incompatibleItem of incompatibleItems) {
-
if (items.find((x) => x.id === incompatibleItem) != null) {
-
logger.warn(
-
`Incompatible dependency detected: "${item.id}" and "${incompatibleItem}" - removing "${incompatibleItem}"`
-
);
+
type FetchDep<T, D> = (id: T) => D | null;
+
type GetDeps<T, D> = (item: Dependency<T, D>) => T[];
+
type GetIncompatible<T, D> = (item: Dependency<T, D>) => T[];
+
type GetEnabled<T, D> = (item: Dependency<T, D>) => boolean;
-
items = items.filter((x) => x.id !== incompatibleItem);
-
}
-
}
-
}
+
function buildDependencyGraph<T, D>(
+
origItems: Dependencies<T, D>,
+
{
+
fetchDep,
+
getDeps,
+
getIncompatible,
+
getEnabled
+
}: {
+
fetchDep: FetchDep<T, D>;
+
getDeps: GetDeps<T, D>;
+
getIncompatible?: GetIncompatible<T, D>;
+
getEnabled?: GetEnabled<T, D>;
}
+
): [Dependencies<T, D>, DependencyGraph<T>] {
+
let items = [...origItems];
+
const dependencyGraph: DependencyGraph<T> = new Map();
-
const dependencyGraph = new Map<T, Set<T> | null>();
for (const item of items) {
const fullDeps: Set<T> = new Set();
let failed = false;
-
// eslint-disable-next-line no-inner-declarations
function resolveDeps(id: T, root: boolean) {
if (id === item.id && !root) {
logger.warn(`Circular dependency detected: "${item.id}"`);
···
items = items.filter((item) => !failed.includes(item));
}
+
return [items, dependencyGraph];
+
}
+
+
export default function calculateDependencies<T, D>(
+
origItems: Dependencies<T, D>,
+
{
+
fetchDep,
+
getDeps,
+
getIncompatible,
+
getEnabled
+
}: {
+
fetchDep: FetchDep<T, D>;
+
getDeps: GetDeps<T, D>;
+
getIncompatible?: GetIncompatible<T, D>;
+
getEnabled?: GetEnabled<T, D>;
+
}
+
): [Dependencies<T, D>, DependencyGraph<T>] {
+
logger.trace("sortDependencies begin", origItems);
+
// eslint-disable-next-line prefer-const
+
let [itemsOrig, dependencyGraphOrig] = buildDependencyGraph(origItems, {
+
fetchDep,
+
getDeps,
+
getIncompatible,
+
getEnabled
+
});
+
+
if (getEnabled != null) {
+
logger.trace("Enabled stage", itemsOrig);
+
const implicitlyEnabled: T[] = [];
+
+
function validateDeps(dep: Dependency<T, D>) {
+
if (getEnabled!(dep)) {
+
const deps = dependencyGraphOrig.get(dep.id)!;
+
for (const id of deps.values()) {
+
const data = fetchDep(id)!;
+
validateDeps({ id, data });
+
}
+
} else {
+
const dependsOnMe = Array.from(dependencyGraphOrig.entries()).filter(([, v]) => v?.has(dep.id));
+
+
if (dependsOnMe.length > 0) {
+
logger.debug("Implicitly enabling dependency", dep.id);
+
implicitlyEnabled.push(dep.id);
+
}
+
}
+
}
+
+
for (const dep of itemsOrig) validateDeps(dep);
+
itemsOrig = itemsOrig.filter((x) => getEnabled(x) || implicitlyEnabled.includes(x.id));
+
}
+
+
if (getIncompatible != null) {
+
logger.trace("Incompatible stage", itemsOrig);
+
+
for (const item of itemsOrig) {
+
// JavaScript iterator moment
+
if (!itemsOrig.includes(item)) continue;
+
+
const incompatibleItems = getIncompatible(item);
+
for (const incompatibleItem of incompatibleItems) {
+
if (itemsOrig.find((x) => x.id === incompatibleItem) != null) {
+
logger.warn(
+
`Incompatible dependency detected: "${item.id}" and "${incompatibleItem}" - removing "${incompatibleItem}"`
+
);
+
+
itemsOrig = itemsOrig.filter((x) => x.id !== incompatibleItem);
+
}
+
}
+
}
+
}
+
+
logger.trace("Verification stage", itemsOrig);
+
const [items, dependencyGraph] = buildDependencyGraph(itemsOrig, {
+
fetchDep,
+
getDeps,
+
getIncompatible,
+
getEnabled
+
});
+
logger.trace("Sorting stage", items);
const sorted: Dependency<T, D>[] = [];
···
dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id)));
}
-
while (
-
Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0
-
) {
-
const noDependents = items.filter(
-
(e) => dependencyGraph.get(e.id)?.size === 0
-
);
+
while (Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0) {
+
const noDependents = items.filter((e) => dependencyGraph.get(e.id)?.size === 0);
if (noDependents.length === 0) {
logger.warn("Stuck dependency graph detected", dependencyGraph);
+48 -54
packages/core/src/util/event.ts
···
-
export type MoonlightEventCallback = (data: string) => void;
+
import { MoonlightEventEmitter } from "@moonlight-mod/types/core/event";
-
export interface MoonlightEventEmitter {
-
dispatchEvent: (id: string, data: string) => void;
-
addEventListener: (id: string, cb: MoonlightEventCallback) => void;
-
removeEventListener: (id: string, cb: MoonlightEventCallback) => void;
-
}
+
export function createEventEmitter<
+
EventId extends string = string,
+
EventData = Record<EventId, any>
+
>(): MoonlightEventEmitter<EventId, EventData> {
+
webTarget: {
+
const eventEmitter = new EventTarget();
+
const listeners = new Map<(data: EventData) => void, (e: Event) => void>();
-
function nodeMethod(): MoonlightEventEmitter {
-
const EventEmitter = require("events");
-
const eventEmitter = new EventEmitter();
-
const listeners = new Map<MoonlightEventCallback, (...args: any[]) => void>();
+
return {
+
dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => {
+
eventEmitter.dispatchEvent(new CustomEvent(id as string, { detail: data }));
+
},
-
return {
-
dispatchEvent: (id: string, data: string) => {
-
eventEmitter.emit(id, data);
-
},
+
addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
+
const untyped = cb as (data: EventData) => void;
+
if (listeners.has(untyped)) return;
-
addEventListener: (id: string, cb: (data: string) => void) => {
-
if (listeners.has(cb)) return;
+
function listener(e: Event) {
+
const event = e as CustomEvent<string>;
+
cb(event.detail as EventData[Id]);
+
}
-
function listener(data: string) {
-
cb(data);
-
}
+
listeners.set(untyped, listener);
+
eventEmitter.addEventListener(id as string, listener);
+
},
-
listeners.set(cb, listener);
-
eventEmitter.on(id, listener);
-
},
-
-
removeEventListener: (id: string, cb: (data: string) => void) => {
-
const listener = listeners.get(cb);
-
if (listener == null) return;
-
listeners.delete(cb);
-
eventEmitter.off(id, listener);
-
}
-
};
-
}
+
removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
+
const untyped = cb as (data: EventData) => void;
+
const listener = listeners.get(untyped);
+
if (listener == null) return;
+
listeners.delete(untyped);
+
eventEmitter.removeEventListener(id as string, listener);
+
}
+
};
+
}
-
export function createEventEmitter(): MoonlightEventEmitter {
-
webPreload: {
-
const eventEmitter = new EventTarget();
-
const listeners = new Map<MoonlightEventCallback, (e: Event) => void>();
+
nodeTarget: {
+
const EventEmitter = require("events");
+
const eventEmitter = new EventEmitter();
+
const listeners = new Map<(data: EventData) => void, (e: Event) => void>();
return {
-
dispatchEvent: (id: string, data: string) => {
-
eventEmitter.dispatchEvent(new CustomEvent(id, { detail: data }));
+
dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => {
+
eventEmitter.emit(id as string, data);
},
-
addEventListener: (id: string, cb: (data: string) => void) => {
-
if (listeners.has(cb)) return;
+
addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
+
const untyped = cb as (data: EventData) => void;
+
if (listeners.has(untyped)) return;
function listener(e: Event) {
const event = e as CustomEvent<string>;
-
cb(event.detail);
+
cb(event as EventData[Id]);
}
-
listeners.set(cb, listener);
-
eventEmitter.addEventListener(id, listener);
+
listeners.set(untyped, listener);
+
eventEmitter.on(id as string, listener);
},
-
removeEventListener: (id: string, cb: (data: string) => void) => {
-
const listener = listeners.get(cb);
+
removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
+
const untyped = cb as (data: EventData) => void;
+
const listener = listeners.get(untyped);
if (listener == null) return;
-
listeners.delete(cb);
-
eventEmitter.removeEventListener(id, listener);
+
listeners.delete(untyped);
+
eventEmitter.off(id as string, listener);
}
};
-
}
-
-
nodePreload: {
-
return nodeMethod();
-
}
-
-
injector: {
-
return nodeMethod();
}
throw new Error("Called createEventEmitter() in an impossible environment");
+3 -5
packages/core/src/util/import.ts
···
cemented if import is passed a string literal.
*/
-
const canRequire = ["path", "fs"] as const;
-
type CanRequire = (typeof canRequire)[number];
+
const _canRequire = ["path", "fs"] as const;
+
type CanRequire = (typeof _canRequire)[number];
type ImportTypes = {
path: typeof import("path");
fs: typeof import("fs");
};
-
export default function requireImport<T extends CanRequire>(
-
type: T
-
): Awaited<ImportTypes[T]> {
+
export default function requireImport<T extends CanRequire>(type: T): Awaited<ImportTypes[T]> {
return require(type);
}
+12 -16
packages/core/src/util/logger.ts
···
/* eslint-disable no-console */
import { LogLevel } from "@moonlight-mod/types/logger";
-
import { readConfig } from "../config";
+
import { Config } from "@moonlight-mod/types";
const colors = {
[LogLevel.SILLY]: "#EDD3E9",
···
[LogLevel.ERROR]: "#FF0000"
};
-
const config = readConfig();
let maxLevel = LogLevel.INFO;
-
if (config.loggerLevel != null) {
-
const enumValue =
-
LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel];
-
if (enumValue != null) {
-
maxLevel = enumValue;
-
}
-
}
export default class Logger {
private name: string;
···
const logLevel = LogLevel[level].toUpperCase();
if (maxLevel > level) return;
-
if (MOONLIGHT_WEB_PRELOAD) {
-
args = [
-
`%c[${logLevel}]`,
-
`background-color: ${colors[level]}; color: #FFFFFF;`,
-
`[${this.name}]`,
-
...obj
-
];
+
if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) {
+
args = [`%c[${logLevel}]`, `background-color: ${colors[level]}; color: #FFFFFF;`, `[${this.name}]`, ...obj];
} else {
args = [`[${logLevel}]`, `[${this.name}]`, ...obj];
}
···
}
}
}
+
+
export function initLogger(config: Config) {
+
if (config.loggerLevel != null) {
+
const enumValue = LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel];
+
if (enumValue != null) {
+
maxLevel = enumValue;
+
}
+
}
+
}
+30
packages/core/src/util/patch.ts
···
+
import { PatchReplace, PatchReplaceType } from "@moonlight-mod/types";
+
+
type SingleFind = string | RegExp;
+
type Find = SingleFind | SingleFind[];
+
+
export function processFind<T extends Find>(find: T): T {
+
if (Array.isArray(find)) {
+
return find.map(processFind) as T;
+
} else if (find instanceof RegExp) {
+
// Add support for \i to match rspack's minified names
+
return new RegExp(find.source.replace(/\\i/g, "[A-Za-z_$][\\w$]*"), find.flags) as T;
+
} else {
+
return find;
+
}
+
}
+
+
export function processReplace(replace: PatchReplace | PatchReplace[]) {
+
if (Array.isArray(replace)) {
+
replace.forEach(processReplace);
+
} else {
+
if (replace.type === undefined || replace.type === PatchReplaceType.Normal) {
+
replace.match = processFind(replace.match);
+
}
+
}
+
}
+
+
export function testFind(src: string, find: SingleFind) {
+
// indexOf is faster than includes by 0.25% lmao
+
return typeof find === "string" ? src.indexOf(find) !== -1 : find.test(src);
+
}
+4 -1
packages/core/tsconfig.json
···
{
-
"extends": "../../tsconfig.json"
+
"extends": "../../tsconfig.json",
+
"compilerOptions": {
+
"lib": ["ESNext", "DOM"]
+
}
}
+11 -2
packages/core-extensions/package.json
···
{
"name": "@moonlight-mod/core-extensions",
"private": true,
+
"engineStrict": true,
+
"engines": {
+
"node": ">=22",
+
"pnpm": ">=10",
+
"npm": "pnpm",
+
"yarn": "pnpm"
+
},
"dependencies": {
-
"@electron/asar": "^3.2.5",
-
"@moonlight-mod/types": "workspace:*"
+
"@moonlight-mod/core": "workspace:*",
+
"@moonlight-mod/types": "workspace:*",
+
"microdiff": "catalog:prod",
+
"nanotar": "catalog:prod"
}
}
+19
packages/core-extensions/src/appPanels/index.ts
···
+
import type { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
+
+
export const patches: Patch[] = [
+
{
+
find: 'setProperty("--custom-app-panels-height"',
+
replace: [
+
{
+
match: /\(0,.\.jsx\)\((.\..),{section:/,
+
replacement: (prev, el) => `...require("appPanels_appPanels").default.getPanels(${el}),${prev}`
+
}
+
]
+
}
+
];
+
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
+
appPanels: {
+
dependencies: [{ id: "react" }]
+
}
+
};
+11
packages/core-extensions/src/appPanels/manifest.json
···
+
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
+
"id": "appPanels",
+
"apiLevel": 2,
+
"meta": {
+
"name": "App Panels",
+
"tagline": "An API for adding panels around the user/voice controls",
+
"authors": ["NotNite"],
+
"tags": ["library"]
+
}
+
}
+23
packages/core-extensions/src/appPanels/webpackModules/appPanels.ts
···
+
import type { AppPanels as AppPanelsType } from "@moonlight-mod/types/coreExtensions/appPanels";
+
import React from "@moonlight-mod/wp/react";
+
+
const panels: Record<string, React.FC<any>> = {};
+
+
export const AppPanels: AppPanelsType = {
+
addPanel(section, element) {
+
panels[section] = element;
+
},
+
getPanels(panel) {
+
return Object.entries(panels).map(([section, element]) =>
+
React.createElement(
+
panel,
+
{
+
section
+
},
+
React.createElement(element)
+
)
+
);
+
}
+
};
+
+
export default AppPanels;
+85
packages/core-extensions/src/commands/index.ts
···
+
import { Patch, ExtensionWebpackModule } from "@moonlight-mod/types";
+
import { APPLICATION_ID } from "@moonlight-mod/types/coreExtensions/commands";
+
+
export const patches: Patch[] = [
+
{
+
find: ".fI5MTU)", // COMMAND_SECTION_BUILT_IN_NAME
+
replace: [
+
// inject commands
+
{
+
match: /return (\i)\.filter/,
+
replacement: (orig, commands) =>
+
`return [...${commands},...require("commands_commands").default._getCommands()].filter`
+
},
+
+
// section
+
{
+
match: /(?<=\i={)(?=\[\i\.\i\.BUILT_IN]:{id:\i\.\i\.BUILT_IN,type:(\i.\i\.BUILT_IN))/,
+
replacement: (_, type) =>
+
`"${APPLICATION_ID}":{id:"${APPLICATION_ID}",type:${type},get name(){return "moonlight"}},`
+
}
+
]
+
},
+
+
// index our section
+
{
+
find: '"ApplicationCommandIndexStore"',
+
replace: {
+
match: /(?<=let \i=(\i)\((\i\.\i)\[\i\.\i\.BUILT_IN\],(\i),!0,!0,(\i)\);)null!=(\i)&&(\i)\.push\(\i\)/,
+
replacement: (_, createSection, sections, deny, props, section, commands) =>
+
`null!=${section}&&(${section}.data=${section}.data.filter(c=>c.applicationId=="-1"));
+
null!=${section}&&${commands}.push(${section});
+
const moonlightCommands=${createSection}(${sections}["${APPLICATION_ID}"],${deny},!0,!0,${props});
+
null!=moonlightCommands&&(moonlightCommands.data=moonlightCommands.data.filter(c=>c.applicationId=="${APPLICATION_ID}"));
+
null!=moonlightCommands&&${commands}.push(moonlightCommands)`
+
}
+
},
+
+
// grab legacy commands (needed for adding actions that act like sed/plus reacting)
+
{
+
find: "={tts:{action:",
+
replace: {
+
match: /Object\.setPrototypeOf\((\i),null\)/,
+
replacement: (_, legacyCommands) => `require("commands_commands")._getLegacyCommands(${legacyCommands})`
+
}
+
},
+
+
// add icon
+
{
+
find: ",hasSpaceTerminator:",
+
replace: {
+
match: /(\i)\.type===/,
+
replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}`
+
}
+
},
+
{
+
find: ".icon,bot:null==",
+
replace: {
+
match: /(\.useMemo\(\(\)=>{(var \i;)?)((return |if\()(\i)\.type)/,
+
replacement: (_, before, beforeVar, after, afterIf, section) => `${before}
+
if (${section}.id==="${APPLICATION_ID}") return "https://moonlight-mod.github.io/favicon.png";
+
${after}`
+
}
+
},
+
// fix icon sizing because they expect built in to be 24 and others to be 32
+
{
+
find: ".builtInSeparator}):null]",
+
replace: {
+
match: /(\i)\.type===\i\.\i\.BUILT_IN/,
+
replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}`
+
}
+
},
+
+
// tell it this app id is authorized
+
{
+
find: /let{customInstallUrl:\i,installParams:\i,integrationTypesConfig:\i}/,
+
replace: {
+
match: /\|\|(\i)===\i\.\i\.BUILT_IN/,
+
replacement: (orig, id) => `${orig}||${id}==="${APPLICATION_ID}"`
+
}
+
}
+
];
+
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
+
commands: {}
+
};
+11
packages/core-extensions/src/commands/manifest.json
···
+
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
+
"id": "commands",
+
"apiLevel": 2,
+
"meta": {
+
"name": "Commands",
+
"tagline": "A library to add commands",
+
"authors": ["Cynosphere", "NotNite"],
+
"tags": ["library"]
+
}
+
}
+71
packages/core-extensions/src/commands/webpackModules/commands.ts
···
+
import {
+
APPLICATION_ID,
+
Commands,
+
LegacyCommand,
+
RegisteredCommand
+
} from "@moonlight-mod/types/coreExtensions/commands";
+
+
type LegacyCommands = Record<string, LegacyCommand>;
+
let legacyCommands: LegacyCommands | undefined;
+
let queuedLegacyCommands: Record<string, LegacyCommand> | null = {};
+
+
const registeredCommands: RegisteredCommand[] = [];
+
+
export function _getLegacyCommands(commands: LegacyCommands) {
+
legacyCommands = commands;
+
if (queuedLegacyCommands != null) {
+
for (const [key, value] of Object.entries(queuedLegacyCommands)) {
+
legacyCommands[key] = value;
+
}
+
queuedLegacyCommands = null;
+
}
+
}
+
+
export const commands: Commands = {
+
registerCommand(command) {
+
const registered: RegisteredCommand = {
+
...command,
+
untranslatedName: command.id,
+
displayName: command.id,
+
applicationId: APPLICATION_ID,
+
untranslatedDescription: command.description,
+
displayDescription: command.description,
+
options: command.options?.map((o) => ({
+
...o,
+
displayName: o.name,
+
displayDescription: o.description
+
}))
+
};
+
registeredCommands.push(registered);
+
},
+
+
registerLegacyCommand(id, command) {
+
if (command.match) {
+
if (command.match instanceof RegExp) {
+
command.match = this.anyScopeRegex(command.match);
+
} else if (command.match.regex && typeof command.match !== "function") {
+
command.match = this.anyScopeRegex(command.match.regex);
+
}
+
}
+
+
if (!legacyCommands) {
+
queuedLegacyCommands![id] = command;
+
} else {
+
legacyCommands[id] = command;
+
}
+
},
+
+
anyScopeRegex(regex) {
+
const out = function (str: string) {
+
return regex.exec(str);
+
};
+
out.regex = regex;
+
return out;
+
},
+
+
_getCommands() {
+
return [...registeredCommands];
+
}
+
};
+
+
export default commands;
+6 -32
packages/core-extensions/src/common/index.ts
···
import { ExtensionWebExports } from "@moonlight-mod/types";
export const webpackModules: ExtensionWebExports["webpackModules"] = {
-
components: {
-
dependencies: [
-
{ ext: "spacepack", id: "spacepack" },
-
"MasonryList:",
-
".flexGutterSmall,"
-
]
-
},
-
-
flux: {
-
dependencies: [
-
{ ext: "spacepack", id: "spacepack" },
-
"useStateFromStores:function"
-
]
-
},
-
-
fluxDispatcher: {
-
dependencies: [
-
{ ext: "spacepack", id: "spacepack" },
-
"isDispatching",
-
"dispatch"
-
]
+
stores: {
+
dependencies: [{ id: "discord/packages/flux" }]
},
-
-
react: {
-
dependencies: [
-
{ ext: "spacepack", id: "spacepack" },
-
"__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED",
-
/\.?version(?:=|:)/,
-
/\.?createElement(?:=|:)/
-
]
+
ErrorBoundary: {
+
dependencies: [{ id: "react" }]
},
-
-
stores: {
-
dependencies: [{ ext: "common", id: "flux" }]
+
icons: {
+
dependencies: [{ id: "react" }, { id: "discord/components/common/index" }]
}
};
+3 -1
packages/core-extensions/src/common/manifest.json
···
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "common",
+
"apiLevel": 2,
"meta": {
"name": "Common",
-
"tagline": "A *lot* of common clientmodding utilities from the Discord client",
+
"tagline": "Common client modding utilities for the Discord client",
"authors": ["Cynosphere", "NotNite"],
"tags": ["library"]
},
+27
packages/core-extensions/src/common/style.css
···
+
.moonlight-error-boundary {
+
margin: 0 0 15px;
+
padding: 10px;
+
border-radius: 5px;
+
font-size: 1rem;
+
font-weight: 300;
+
line-height: 22px;
+
color: var(--text-normal, white);
+
background: hsl(var(--red-400-hsl) / 0.1);
+
border: 2px solid hsl(var(--red-400-hsl) / 0.5);
+
+
.theme-light & {
+
color: var(--text-normal, black) !important;
+
}
+
+
& > h3 {
+
margin-bottom: 0.25rem;
+
}
+
+
& > .hljs {
+
background: var(--background-secondary);
+
border: 1px solid var(--background-tertiary);
+
white-space: pre-wrap;
+
font-family: var(--font-code);
+
user-select: text;
+
}
+
}
+47
packages/core-extensions/src/common/webpackModules/ErrorBoundary.tsx
···
+
import React from "@moonlight-mod/wp/react";
+
import { ErrorBoundaryProps, ErrorBoundaryState } from "@moonlight-mod/types/coreExtensions/common";
+
+
const logger = moonlight.getLogger("ErrorBoundary");
+
+
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
+
constructor(props: ErrorBoundaryProps) {
+
super(props);
+
this.state = {
+
errored: false,
+
error: undefined,
+
componentStack: undefined
+
};
+
}
+
+
static getDerivedStateFromError(error: Error) {
+
return {
+
errored: true,
+
error
+
};
+
}
+
+
componentDidCatch(error: Error, { componentStack }: { componentStack: string }) {
+
logger.error(`${error}\n\nComponent stack:\n${componentStack}`);
+
this.setState({ error, componentStack });
+
}
+
+
render() {
+
const { noop, fallback: FallbackComponent, children, message } = this.props;
+
const { errored, error, componentStack } = this.state;
+
+
if (FallbackComponent) return <FallbackComponent children={children} {...this.state} />;
+
+
if (errored) {
+
return noop ? null : (
+
<div className={`moonlight-error-boundary`}>
+
<h3>{message ?? "An error occurred rendering this component:"}</h3>
+
<code className="hljs">{`${error}\n\nComponent stack:\n${componentStack}`}</code>
+
</div>
+
);
+
}
+
+
return children;
+
}
+
}
+
+
export default ErrorBoundary;
-26
packages/core-extensions/src/common/webpackModules/components.ts
···
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
-
-
const Components = spacepack.findByCode("MasonryList:function")[0].exports;
-
const MarkdownParser = spacepack.findByCode(
-
"parseAutoModerationSystemMessage:"
-
)[0].exports.default;
-
const LegacyText = spacepack.findByCode(".selectable", ".colorStandard")[0]
-
.exports.default;
-
const Flex = spacepack.findByCode(".flex" + "GutterSmall,")[0].exports.default;
-
const CardClasses = spacepack.findByCode("card", "cardHeader", "inModal")[0]
-
.exports;
-
const ControlClasses = spacepack.findByCode(
-
"title",
-
"titleDefault",
-
"dividerDefault"
-
)[0].exports;
-
-
// We use CJS export here because merging the exports from Components is annoying as shit
-
module.exports = {
-
...Components,
-
MarkdownParser,
-
LegacyText,
-
Flex,
-
CardClasses,
-
ControlClasses
-
};
-5
packages/core-extensions/src/common/webpackModules/flux.ts
···
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
-
-
module.exports = spacepack.findByCode(
-
["useStateFromStores", ":function"].join("")
-
)[0].exports;
-6
packages/core-extensions/src/common/webpackModules/fluxDispatcher.ts
···
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
-
-
module.exports = spacepack.findByExports(
-
"isDispatching",
-
"dispatch"
-
)[0].exports.default;
+31
packages/core-extensions/src/common/webpackModules/icons.ts
···
+
import { Icons, IconSize } from "@moonlight-mod/types/coreExtensions/common";
+
import { tokens } from "@moonlight-mod/wp/discord/components/common/index";
+
+
// This is defined in a Webpack module but we copy it here to be less breakage-prone
+
const sizes: Partial<Record<IconSize, number>> = {
+
xxs: 12,
+
xs: 16,
+
sm: 18,
+
md: 24,
+
lg: 32,
+
refresh_sm: 20
+
};
+
+
export const icons: Icons = {
+
parseProps(props) {
+
// NOTE: var() fallback is non-standard behavior, just for safety reasons
+
const color = props?.color ?? tokens?.colors?.["INTERACTIVE_NORMAL"] ?? "var(--interactive-normal)";
+
+
const size = sizes[props?.size ?? "md"];
+
+
return {
+
// note: this default size is also non-standard behavior, just for safety
+
width: size ?? props?.width ?? sizes.md!,
+
height: size ?? props?.width ?? sizes.md!,
+
+
fill: typeof color === "string" ? color : color.css,
+
className: props?.colorClass ?? ""
+
};
+
}
+
};
+
export default icons;
-7
packages/core-extensions/src/common/webpackModules/react.ts
···
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
-
-
module.exports = spacepack.findByCode(
-
"__SECRET_INTERNALS_DO_NOT_USE" + "_OR_YOU_WILL_BE_FIRED",
-
/\.?version(?:=|:)/,
-
/\.?createElement(?:=|:)/
-
)[0].exports;
+2 -2
packages/core-extensions/src/common/webpackModules/stores.ts
···
-
import Flux from "@moonlight-mod/wp/common_flux";
+
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
module.exports = new Proxy(
{},
{
get: function (target, key, receiver) {
-
const allStores = Flux.Store.getAll();
+
const allStores = Store.getAll();
let targetStore;
for (const store of allStores) {
+84
packages/core-extensions/src/componentEditor/index.ts
···
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
+
+
export const patches: Patch[] = [
+
// dm list
+
{
+
find: ".interactiveSystemDM]:",
+
replace: [
+
{
+
match: /decorators:(\i\.isSystemDM\(\)\?\(0,\i\.jsx\)\(.+?verified:!0}\):null)/,
+
replacement: (_, decorators) =>
+
`decorators:require("componentEditor_dmList").default._patchDecorators([${decorators}],arguments[0])`
+
},
+
{
+
match: /(?<=selected:\i,)children:\[/,
+
replacement: 'children:require("componentEditor_dmList").default._patchItems(['
+
},
+
{
+
match: /(?<=(onMouseDown|nameplate):\i}\))]/,
+
replacement: "],arguments[0])"
+
}
+
],
+
hardFail: true
+
},
+
+
// member list
+
{
+
find: ".lostPermission",
+
replace: [
+
{
+
match:
+
/(?<=\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[\(0,\i\.jsx\)\(\i,{user:\i}\),.+?onClickPremiumGuildIcon:\i}\)])/,
+
replacement: (_, decorators) =>
+
`children:require("componentEditor_memberList").default._patchDecorators(${decorators},arguments[0])`
+
},
+
{
+
match: /name:null==\i\?\(0,\i\.jsx\)\("span"/,
+
replacement: (orig: string) =>
+
`children:require("componentEditor_memberList").default._patchItems([],arguments[0]),${orig}`
+
}
+
]
+
},
+
+
// messages
+
{
+
find: '},"new-member")),',
+
replace: [
+
{
+
match: /(?<=\.BADGES](=|:))(\i)(;|})/,
+
replacement: (_, leading, badges, trailing) =>
+
`require("componentEditor_messages").default._patchUsernameBadges(${badges},arguments[0])${trailing}`
+
},
+
{
+
match: /(?<=className:\i,)badges:(\i)/,
+
replacement: (_, badges) =>
+
`badges:require("componentEditor_messages").default._patchBadges(${badges},arguments[0])`
+
},
+
{
+
match: /(?<=username:\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[.+?])}\),usernameSpanId:/,
+
replacement: (_, elements) =>
+
`children:require("componentEditor_messages").default._patchUsername(${elements},arguments[0])}),usernameSpanId:`
+
}
+
]
+
},
+
{
+
find: '.provider&&"Discord"===',
+
replace: {
+
match: /(?<=\.container\),)children:(\[.+?this\.renderSuppressConfirmModal\(\),.+?\])}\)/,
+
replacement: (_, elements) =>
+
`children:require("componentEditor_messages").default._patchAccessories(${elements},this.props)})`
+
}
+
}
+
];
+
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
+
dmList: {
+
dependencies: [{ id: "react" }]
+
},
+
memberList: {
+
dependencies: [{ id: "react" }]
+
},
+
messages: {
+
dependencies: [{ id: "react" }]
+
}
+
};
+11
packages/core-extensions/src/componentEditor/manifest.json
···
+
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
+
"id": "componentEditor",
+
"apiLevel": 2,
+
"meta": {
+
"name": "Component Editor",
+
"tagline": "A library to add to commonly patched components",
+
"authors": ["Cynosphere"],
+
"tags": ["library"]
+
}
+
}
+61
packages/core-extensions/src/componentEditor/webpackModules/dmList.tsx
···
+
import {
+
DMList,
+
DMListItem,
+
DMListDecorator,
+
DMListAnchorIndicies,
+
DMListDecoratorAnchorIndicies
+
} from "@moonlight-mod/types/coreExtensions/componentEditor";
+
import React from "@moonlight-mod/wp/react";
+
+
const items: Record<string, DMListItem> = {};
+
const decorators: Record<string, DMListDecorator> = {};
+
+
function addEntries(
+
elements: React.ReactNode[],
+
entries: Record<string, DMListItem | DMListDecorator>,
+
indicies: Partial<Record<keyof typeof DMListAnchorIndicies | keyof typeof DMListDecoratorAnchorIndicies, number>>,
+
props: any
+
) {
+
const originalElements = [...elements];
+
for (const [id, entry] of Object.entries(entries)) {
+
const component = <entry.component {...props} key={id} />;
+
+
if (entry.anchor === undefined) {
+
if (entry.before) {
+
elements.splice(0, 0, component);
+
} else {
+
elements.push(component);
+
}
+
} else {
+
const index = elements.indexOf(originalElements[indicies[entry.anchor]!]);
+
elements.splice(index! + (entry.before ? 0 : 1), 0, component);
+
}
+
}
+
}
+
+
export const dmList: DMList = {
+
addItem(id, component, anchor, before = false) {
+
items[id] = {
+
component,
+
anchor,
+
before
+
};
+
},
+
addDecorator(id, component, anchor, before = false) {
+
decorators[id] = {
+
component,
+
anchor,
+
before
+
};
+
},
+
_patchItems(elements, props) {
+
addEntries(elements, items, DMListAnchorIndicies, props);
+
return elements;
+
},
+
_patchDecorators(elements, props) {
+
addEntries(elements, decorators, DMListDecoratorAnchorIndicies, props);
+
return elements;
+
}
+
};
+
+
export default dmList;
+50
packages/core-extensions/src/componentEditor/webpackModules/memberList.tsx
···
+
import {
+
MemberList,
+
MemberListDecorator,
+
MemberListDecoratorAnchorIndicies
+
} from "@moonlight-mod/types/coreExtensions/componentEditor";
+
import React from "@moonlight-mod/wp/react";
+
+
const items: Record<string, React.FC<any>> = {};
+
const decorators: Record<string, MemberListDecorator> = {};
+
+
export const memberList: MemberList = {
+
addItem(id, component) {
+
items[id] = component;
+
},
+
addDecorator(id, component, anchor, before = false) {
+
decorators[id] = {
+
component,
+
anchor,
+
before
+
};
+
},
+
_patchItems(elements, props) {
+
for (const [id, Component] of Object.entries(items)) {
+
elements.push(<Component {...props} key={id} />);
+
}
+
+
return elements;
+
},
+
_patchDecorators(elements, props) {
+
const originalElements = [...elements];
+
for (const [id, entry] of Object.entries(decorators)) {
+
const component = <entry.component {...props} key={id} />;
+
+
if (entry.anchor === undefined) {
+
if (entry.before) {
+
elements.splice(0, 0, component);
+
} else {
+
elements.push(component);
+
}
+
} else {
+
const index = elements.indexOf(originalElements[MemberListDecoratorAnchorIndicies[entry.anchor]!]);
+
elements.splice(index! + (entry.before ? 0 : 1), 0, component);
+
}
+
}
+
+
return elements;
+
}
+
};
+
+
export default memberList;
+97
packages/core-extensions/src/componentEditor/webpackModules/messages.tsx
···
+
import {
+
MessageBadge,
+
MessageBadgeIndicies,
+
Messages,
+
MessageUsername,
+
MessageUsernameBadge,
+
MessageUsernameBadgeIndicies,
+
MessageUsernameIndicies
+
} from "@moonlight-mod/types/coreExtensions/componentEditor";
+
import React from "@moonlight-mod/wp/react";
+
+
const username: Record<string, MessageUsername> = {};
+
const usernameBadges: Record<string, MessageUsernameBadge> = {};
+
const badges: Record<string, MessageBadge> = {};
+
const accessories: Record<string, React.FC<any>> = {};
+
+
function addEntries(
+
elements: React.ReactNode[],
+
entries: Record<string, MessageUsername | MessageUsernameBadge | MessageBadge>,
+
indicies: Partial<
+
Record<
+
| keyof typeof MessageUsernameIndicies
+
| keyof typeof MessageUsernameBadgeIndicies
+
| keyof typeof MessageBadgeIndicies,
+
number
+
>
+
>,
+
props: any
+
) {
+
const originalElements = [...elements];
+
for (const [id, entry] of Object.entries(entries)) {
+
const component = <entry.component {...props} key={id} />;
+
+
if (entry.anchor === undefined) {
+
if (entry.before) {
+
elements.splice(0, 0, component);
+
} else {
+
elements.push(component);
+
}
+
} else {
+
const index = elements.indexOf(originalElements[indicies[entry.anchor]!]);
+
elements.splice(index! + (entry.before ? 0 : 1), 0, component);
+
}
+
}
+
}
+
+
function addComponents(elements: React.ReactNode[], components: Record<string, React.FC<any>>, props: any) {
+
for (const [id, Component] of Object.entries(components)) {
+
const component = <Component {...props} key={id} />;
+
elements.push(component);
+
}
+
}
+
+
export const messages: Messages = {
+
addToUsername(id, component, anchor, before = false) {
+
username[id] = {
+
component,
+
anchor,
+
before
+
};
+
},
+
addUsernameBadge(id, component, anchor, before = false) {
+
usernameBadges[id] = {
+
component,
+
anchor,
+
before
+
};
+
},
+
addBadge(id, component, anchor, before = false) {
+
badges[id] = {
+
component,
+
anchor,
+
before
+
};
+
},
+
addAccessory(id, component) {
+
accessories[id] = component;
+
},
+
_patchUsername(elements, props) {
+
addEntries(elements, username, MessageUsernameIndicies, props);
+
return elements;
+
},
+
_patchUsernameBadges(elements, props) {
+
addEntries(elements, usernameBadges, MessageUsernameBadgeIndicies, props);
+
return elements;
+
},
+
_patchBadges(elements, props) {
+
addEntries(elements, badges, MessageBadgeIndicies, props);
+
return elements;
+
},
+
_patchAccessories(elements, props) {
+
addComponents(elements, accessories, props);
+
return elements;
+
}
+
};
+
+
export default messages;
+31
packages/core-extensions/src/contextMenu/index.tsx
···
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
+
+
export const patches: Patch[] = [
+
{
+
find: "Menu API only allows Items and groups of Items as children.",
+
replace: [
+
{
+
match: /(?<=let{navId[^}]+?}=(.),.=).+?(?=,)/,
+
replacement: (items, props) => `require("contextMenu_contextMenu")._patchMenu(${props},${items})`
+
}
+
]
+
},
+
{
+
find: ".getContextMenu(",
+
replace: [
+
{
+
match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/,
+
replacement: (render) => `require("contextMenu_contextMenu")._saveProps(this,${render})`
+
}
+
]
+
}
+
];
+
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
+
contextMenu: {
+
dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."]
+
},
+
evilMenu: {
+
dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."]
+
}
+
};
+11
packages/core-extensions/src/contextMenu/manifest.json
···
+
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
+
"id": "contextMenu",
+
"apiLevel": 2,
+
"meta": {
+
"name": "Context Menu",
+
"tagline": "A library for patching and creating context menus",
+
"authors": ["redstonekasi"],
+
"tags": ["library"]
+
}
+
}
+88
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
···
+
import { InternalItem, Menu, MenuElement } from "@moonlight-mod/types/coreExtensions/contextMenu";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import parser from "@moonlight-mod/wp/contextMenu_evilMenu";
+
+
// NOTE: We originally had item as a function that returned this, but it didn't
+
// quite know how to work out the type and thought it was a JSX element (it
+
// *technically* was). This has less type safety, but a @ts-expect-error has
+
// zero, so it's better than nothing.
+
type ReturnType = MenuElement | MenuElement[];
+
+
type Patch = {
+
navId: string;
+
item: React.FC<any>;
+
anchor: string | RegExp;
+
before: boolean;
+
};
+
+
function addItem<T = any>(navId: string, item: React.FC<T>, anchor: string | RegExp, before = false) {
+
if (anchor instanceof RegExp && anchor.flags.includes("g"))
+
throw new Error("anchor regular expression should not be global");
+
patches.push({ navId, item, anchor, before });
+
}
+
+
const patches: Patch[] = [];
+
function _patchMenu(props: React.ComponentProps<Menu>, items: InternalItem[]) {
+
const matches = patches.filter((p) => p.navId === props.navId);
+
if (!matches.length) return items;
+
+
for (const patch of matches) {
+
const idx = items.findIndex((i) =>
+
typeof patch.anchor === "string" ? i.key === patch.anchor : patch.anchor.test(i.key!)
+
);
+
if (idx === -1) continue;
+
items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps) as ReturnType));
+
}
+
+
return items;
+
}
+
+
let menuProps: any;
+
function _saveProps(self: any, el: any) {
+
menuProps = el.props;
+
+
const original = self.props.closeContextMenu;
+
self.props.closeContextMenu = function (...args: any[]) {
+
menuProps = undefined;
+
return original?.apply(this, args);
+
};
+
+
return el;
+
}
+
+
module.exports = {
+
patches,
+
addItem,
+
_patchMenu,
+
_saveProps
+
};
+
+
// Unmangle Menu elements
+
// spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString();
+
const code =
+
spacepack.require.m[
+
spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id
+
].toString();
+
+
let MangledMenu;
+
+
const typeRegex = /if\(.\.type===(.)\.(.+?)\).+?type:"(.+?)"/g;
+
const typeMap: Record<string, string | undefined> = {
+
checkbox: "MenuCheckboxItem",
+
control: "MenuControlItem",
+
groupstart: "MenuGroup",
+
customitem: "MenuItem",
+
radio: "MenuRadioItem",
+
separator: "MenuSeparator"
+
};
+
+
for (const [, modIdent, mangled, type] of code.matchAll(typeRegex)) {
+
if (!MangledMenu) {
+
const modId = code.match(new RegExp(`${modIdent}=.\\((\\d+?)\\)`))![1];
+
MangledMenu = spacepack.require(modId);
+
}
+
+
const prop = typeMap[type];
+
if (!prop) continue;
+
module.exports[prop] = MangledMenu[mangled];
+
}
+18
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
···
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
+
// spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString();
+
let code =
+
spacepack.require.m[
+
spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id
+
].toString();
+
+
const parserSym = code.match(/(?<=_patchMenu\(.,).+?(?=\()/)![0];
+
+
code = code.replace(/{(.):\(\)=>./, (orig, e) => `{${e}:()=>${parserSym}`);
+
const mod = new Function("module", "exports", "require", `(${code}).apply(this, arguments)`);
+
+
const exp: any = {};
+
mod({}, exp, require);
+
+
const parser = spacepack.findFunctionByStrings(exp, "Menu API only allows Items and groups of Items as children.")!;
+
module.exports = parser;
+19
packages/core-extensions/src/devToolsExtensions/host.ts
···
+
import { app, session } from "electron";
+
import { resolve } from "node:path";
+
import Logger from "@moonlight-mod/core/util/logger";
+
+
const logger = new Logger("DevTools Extensions");
+
+
app.whenReady().then(async () => {
+
const paths = moonlightHost.getConfigOption<string[]>("devToolsExtensions", "paths") ?? [];
+
+
for (const path of paths) {
+
const resolved = resolve(path);
+
+
try {
+
await session.defaultSession.loadExtension(resolved);
+
} catch (err) {
+
logger.error(`Failed to load an extension in "${resolved}":`, err);
+
}
+
}
+
});
+22
packages/core-extensions/src/devToolsExtensions/manifest.json
···
+
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
+
"id": "devToolsExtensions",
+
"meta": {
+
"name": "DevTools Extensions",
+
"tagline": "Loads Chrome extensions into Electron DevTools",
+
"authors": [
+
"Cynosphere"
+
],
+
"tags": [
+
"development"
+
]
+
},
+
"settings": {
+
"paths": {
+
"advice": "restart",
+
"displayName": "Extension Paths",
+
"type": "list"
+
}
+
},
+
"apiLevel": 2
+
}
+15 -32
packages/core-extensions/src/disableSentry/host.ts
···
import { join } from "node:path";
import { Module } from "node:module";
-
import { BrowserWindow } from "electron";
const logger = moonlightHost.getLogger("disableSentry");
-
try {
-
const hostSentryPath = require.resolve(
-
join(moonlightHost.asarPath, "node_modules", "@sentry", "electron")
-
);
-
require.cache[hostSentryPath] = new Module(
-
hostSentryPath,
-
require.cache[require.resolve(moonlightHost.asarPath)]
-
);
-
require.cache[hostSentryPath]!.exports = {
-
init: () => {},
-
captureException: () => {},
-
setTag: () => {},
-
setUser: () => {}
-
};
-
logger.debug("Stubbed Sentry host side!");
-
} catch (err) {
-
logger.error("Failed to stub Sentry host side:", err);
+
if (moonlightHost.asarPath !== "moonlightDesktop") {
+
try {
+
const hostSentryPath = require.resolve(join(moonlightHost.asarPath, "node_modules", "@sentry", "electron"));
+
require.cache[hostSentryPath] = new Module(hostSentryPath, require.cache[require.resolve(moonlightHost.asarPath)]);
+
require.cache[hostSentryPath]!.exports = {
+
init: () => {},
+
captureException: () => {},
+
setTag: () => {},
+
setUser: () => {},
+
captureMessage: () => {}
+
};
+
logger.debug("Stubbed Sentry host side!");
+
} catch (err) {
+
logger.error("Failed to stub Sentry host side:", err);
+
}
}
-
-
moonlightHost.events.on("window-created", (window: BrowserWindow) => {
-
window.webContents.session.webRequest.onBeforeRequest(
-
{
-
urls: [
-
"https://*.sentry.io/*",
-
"https://*.discord.com/error-reporting-proxy/*"
-
]
-
},
-
function (details, callback) {
-
callback({ cancel: true });
-
}
-
);
-
});
+8 -18
packages/core-extensions/src/disableSentry/index.ts
···
export const patches: Patch[] = [
{
-
find: "DSN:function",
+
find: "profiledRootComponent:",
replace: {
type: PatchReplaceType.Normal,
-
match: /default:function\(\){return .}/,
-
replacement:
-
'default:function(){return require("disableSentry_stub").proxy()}'
-
}
-
},
-
{
-
find: "window.DiscordSentry.addBreadcrumb",
-
replace: {
-
type: PatchReplaceType.Normal,
-
match: /default:function\(\){return .}/,
-
replacement:
-
'default:function(){return (...args)=>{moonlight.getLogger("disableSentry").debug("Sentry calling addBreadcrumb passthrough:", ...args);}}'
+
match: /Z:\(\)=>\i/,
+
replacement: 'Z:()=>require("disableSentry_stub").proxy()'
}
},
{
-
find: "initSentry:function",
+
find: "this._sentryUtils=",
replace: {
type: PatchReplaceType.Normal,
-
match: /initSentry:function\(\){return .}/,
-
replacement: "default:function(){return ()=>{}}"
+
match: /(?<=this._sentryUtils=)./,
+
replacement: "undefined"
}
},
{
find: "window.DiscordErrors=",
replace: {
type: PatchReplaceType.Normal,
-
match: /uses_client_mods:\(0,.\.usesClientMods\)\(\)/,
-
replacement: "uses_client_mods:false"
+
match: /(?<=uses_client_mods:)./,
+
replacement: "false"
}
}
];
+12 -1
packages/core-extensions/src/disableSentry/manifest.json
···
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "disableSentry",
+
"apiLevel": 2,
"meta": {
"name": "Disable Sentry",
"tagline": "Turns off Discord's error reporting systems",
"authors": ["Cynosphere", "NotNite"],
"tags": ["privacy"]
-
}
+
},
+
"blocked": [
+
"https://*.sentry.io/*",
+
"https://*.discord.com/error-reporting-proxy/*",
+
"https://discord.com/assets/sentry.*.js",
+
"https://*.discord.com/assets/sentry.*.js",
+
"https://*.discordapp.com/error-reporting-proxy/*",
+
"https://discordapp.com/assets/sentry.*.js",
+
"https://*.discordapp.com/assets/sentry.*.js"
+
]
}
+4 -8
packages/core-extensions/src/disableSentry/node.ts
···
const preloadPath = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
try {
-
const sentryPath = require.resolve(
-
resolve(preloadPath, "..", "node_modules", "@sentry", "electron")
-
);
-
require.cache[sentryPath] = new Module(
-
sentryPath,
-
require.cache[require.resolve(preloadPath)]
-
);
+
const sentryPath = require.resolve(resolve(preloadPath, "..", "node_modules", "@sentry", "electron"));
+
require.cache[sentryPath] = new Module(sentryPath, require.cache[require.resolve(preloadPath)]);
require.cache[sentryPath]!.exports = {
init: () => {},
setTag: () => {},
-
setUser: () => {}
+
setUser: () => {},
+
captureMessage: () => {}
};
logger.debug("Stubbed Sentry node side!");
} catch (err) {
+1 -2
packages/core-extensions/src/disableSentry/webpackModules/stub.ts
···
throw Error("crash");
};
} else if (keys.includes(prop.toString())) {
-
return (...args: any[]) =>
-
logger.debug(`Sentry calling "${prop.toString()}":`, ...args);
+
return (...args: any[]) => logger.debug(`Sentry calling "${prop.toString()}":`, ...args);
} else {
return undefined;
}
+45 -18
packages/core-extensions/src/experiments/index.ts
···
export const patches: Patch[] = [
{
-
find: /\.displayName="(Developer)?ExperimentStore"/,
+
find: "isStaffPersonal:",
+
replace: {
+
match: /&&null!=this\.personalConnectionId/,
+
replacement: "||!0"
+
}
+
},
+
{
+
find: '"scientist:triggered"', // Scientist? Triggered.
+
replace: {
+
match: ".personal_connection_id",
+
replacement: ".personal_connection_id || true"
+
}
+
},
+
+
// Enable staff help menu
+
{
+
find: ".HEADER_BAR)",
+
replace: {
+
match: /&&\((\i)\?\(0,/,
+
replacement: (_, isStaff) =>
+
`&&(((moonlight.getConfigOption("experiments","devtools")??false)?true:${isStaff})?(0,`
+
}
+
},
+
// staff help menu - visual refresh
+
{
+
find: '("AppTitleBar")',
+
replace: {
+
match: /{hasBugReporterAccess:(\i)}=\i\.\i\.useExperiment\({location:"HeaderBar"},{autoTrackExposure:!1}\);/,
+
replacement: (orig, isStaff) =>
+
`${orig}if(moonlight.getConfigOption("experiments","devtools")??false)${isStaff}=true;`
+
}
+
},
+
{
+
find: 'navId:"staff-help-popout",',
replace: {
-
match: "window.GLOBAL_ENV.RELEASE_CHANNEL",
-
replacement: '"staging"'
+
match: /isDiscordDeveloper:(\i)}\),/,
+
replacement: (_, isStaff) =>
+
`isDiscordDeveloper:(moonlight.getConfigOption("experiments","devtools")??false)||${isStaff}}),`
}
},
+
+
// Enable further staff-locked options
{
-
find: '.displayName="DeveloperExperimentStore"',
-
replace: [
-
{
-
match: /CONNECTION_OPEN:.,OVERLAY_INITIALIZE:.,CURRENT_USER_UPDATE:./,
-
replacement: ""
-
},
-
{
-
match: '"production"',
-
replacement: '"development"'
-
},
-
{
-
match: /(get:function\(\){return .}}}\);).\(\);/,
-
replacement: "$1"
-
}
-
]
+
find: "shouldShowLurkerModeUpsellPopout:",
+
replace: {
+
match: /\.useReducedMotion,isStaff:(\i)(,|})/,
+
replacement: (_, isStaff, trail) =>
+
`.useReducedMotion,isStaff:(moonlight.getConfigOption("experiments","staffSettings")??false)?true:${isStaff}${trail}`
+
}
}
];
+17 -1
packages/core-extensions/src/experiments/manifest.json
···
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "experiments",
+
"apiLevel": 2,
"meta": {
"name": "Experiments",
"tagline": "Allows you to configure Discord's internal A/B testing features",
-
"authors": ["NotNite"],
+
"authors": ["NotNite", "Cynosphere"],
"tags": ["dangerZone"]
+
},
+
"settings": {
+
"devtools": {
+
"advice": "reload",
+
"displayName": "Enable staff help menu (DevTools)",
+
"type": "boolean",
+
"default": false
+
},
+
"staffSettings": {
+
"advice": "reload",
+
"displayName": "Allow access to other staff settings elsewhere",
+
"type": "boolean",
+
"default": false
+
}
}
}
+43
packages/core-extensions/src/markdown/index.ts
···
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
+
+
export const patches: Patch[] = [
+
{
+
find: "/^(ยฏ\\\\_\\(ใƒ„\\)_\\/ยฏ)/.exec",
+
replace: [
+
{
+
match: /={newline:(.+?)},(.{1,2})=\(0,/,
+
replacement: (_, rules, RULES) => `=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,`
+
},
+
{
+
match: /(?<=;(.{1,2}\.Z)={RULES:.+?})/,
+
replacement: (_, rulesets) => `;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});`
+
}
+
]
+
},
+
{
+
find: "then you probably need to add it to this file so that the rich chat box understands it.",
+
replace: [
+
{
+
match: /(.)={link:{(.+?)},(.)=new Set/,
+
replacement: (_, rulesDef, rules, syntaxBefore) =>
+
`__slateRules,${rulesDef}=__slateRules=require("markdown_markdown")._addSlateRules({link:{${rules}}),${syntaxBefore}=new Set`
+
},
+
{
+
match: /(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"subtext":{(.+?)}default:/,
+
replacement: (_, start, rule, body, plaintextReturn, otherRules, inlineStyleBody) =>
+
`${start}if(${rule}.type.startsWith("__moonlight_")){if(__slateRules[${rule}.type].type=="inlineStyle"){${inlineStyleBody}}else{${plaintextReturn}}}${body}case"emoticon":${plaintextReturn}${otherRules}case"subtext":{${inlineStyleBody}}default:`
+
}
+
]
+
},
+
{
+
find: '"Slate: Unknown decoration attribute: "',
+
replace: {
+
match: /=({strong:.+?});/,
+
replacement: (_, rules) => `=require("markdown_markdown")._addSlateDecorators(${rules});`
+
}
+
}
+
];
+
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
+
markdown: {}
+
};
+11
packages/core-extensions/src/markdown/manifest.json
···
+
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
+
"id": "markdown",
+
"apiLevel": 2,
+
"meta": {
+
"name": "Markdown",
+
"tagline": "A library for adding new markdown rules",
+
"authors": ["Cynosphere"],
+
"tags": ["library"]
+
}
+
}
+68
packages/core-extensions/src/markdown/webpackModules/markdown.ts
···
+
import { MarkdownRule, Ruleset, SlateRule } from "@moonlight-mod/types/coreExtensions/markdown";
+
+
export const rules: Record<string, (rules: Record<string, MarkdownRule>) => MarkdownRule> = {};
+
export const slateRules: Record<string, (rules: Record<string, SlateRule>) => SlateRule> = {};
+
export const slateDecorators: Record<string, string> = {};
+
export const ruleBlacklists: Record<Ruleset, Record<string, boolean>> = {
+
RULES: {},
+
CHANNEL_TOPIC_RULES: {},
+
VOICE_CHANNEL_STATUS_RULES: {},
+
EMBED_TITLE_RULES: {},
+
INLINE_REPLY_RULES: {},
+
GUILD_VERIFICATION_FORM_RULES: {},
+
GUILD_EVENT_RULES: {},
+
PROFILE_BIO_RULES: {},
+
AUTO_MODERATION_SYSTEM_MESSAGE_RULES: {},
+
NATIVE_SEARCH_RESULT_LINK_RULES: {}
+
};
+
+
export function addRule(
+
name: string,
+
markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule,
+
slate: (rules: Record<string, SlateRule>) => SlateRule,
+
decorator?: string
+
) {
+
rules[name] = markdown;
+
slateRules[name] = slate;
+
if (decorator != null) slateDecorators[name] = decorator;
+
}
+
+
export function blacklistFromRuleset(ruleset: Ruleset, name: string) {
+
if (ruleBlacklists[ruleset] == null) ruleBlacklists[ruleset] = {};
+
ruleBlacklists[ruleset][name] = true;
+
}
+
+
export function _addRules(originalRules: Record<string, MarkdownRule>) {
+
for (const name in rules) {
+
originalRules["__moonlight_" + name] = rules[name](originalRules);
+
}
+
+
return originalRules;
+
}
+
+
export function _addSlateRules(originalRules: Record<string, SlateRule>) {
+
for (const name in slateRules) {
+
originalRules["__moonlight_" + name] = slateRules[name](originalRules);
+
}
+
+
return originalRules;
+
}
+
+
export function _addSlateDecorators(originalRules: Record<string, string>) {
+
for (const name in slateDecorators) {
+
originalRules["__moonlight_" + name] = slateDecorators[name];
+
}
+
+
return originalRules;
+
}
+
+
export function _applyRulesetBlacklist(rulesets: Record<Ruleset, Record<string, MarkdownRule>>) {
+
for (const ruleset of Object.keys(rulesets) as Ruleset[]) {
+
if (ruleset === "RULES") continue;
+
+
const rules = rulesets[ruleset];
+
for (const rule in ruleBlacklists[ruleset] || {}) {
+
delete rules["__moonlight_" + rule];
+
}
+
}
+
}
+108
packages/core-extensions/src/moonbase/host.ts
···
+
import * as electron from "electron";
+
import * as fs from "node:fs/promises";
+
import * as path from "node:path";
+
import getNatives from "./native";
+
import { MoonlightBranch } from "@moonlight-mod/types";
+
+
const natives = getNatives();
+
+
const confirm = (action: string) =>
+
electron.dialog
+
.showMessageBox({
+
title: "Are you sure?",
+
message: `Are you sure? This will ${action} and restart Discord.`,
+
type: "warning",
+
buttons: ["OK", "Cancel"]
+
})
+
.then((r) => r.response === 0);
+
+
async function updateAndRestart() {
+
if (!(await confirm("update moonlight"))) return;
+
const newVersion = await natives.checkForMoonlightUpdate();
+
+
if (newVersion === null) {
+
electron.dialog.showMessageBox({ message: "You are already on the latest version of moonlight." });
+
return;
+
}
+
+
try {
+
await natives.updateMoonlight();
+
await electron.dialog.showMessageBox({ message: "Update successful, restarting Discord." });
+
electron.app.relaunch();
+
electron.app.exit(0);
+
} catch {
+
await electron.dialog.showMessageBox({
+
message: "Failed to update moonlight. Please use the installer instead.",
+
type: "error"
+
});
+
}
+
}
+
+
async function resetConfig() {
+
if (!(await confirm("reset your configuration"))) return;
+
+
const config = await moonlightHost.getConfigPath();
+
const dir = path.dirname(config);
+
const branch = path.basename(config, ".json");
+
await fs.rename(config, path.join(dir, `${branch}-backup-${Math.floor(Date.now() / 1000)}.json`));
+
+
await electron.dialog.showMessageBox({ message: "Configuration reset, restarting Discord." });
+
electron.app.relaunch();
+
electron.app.exit(0);
+
}
+
+
async function changeBranch(branch: MoonlightBranch) {
+
if (moonlightHost.branch === branch) return;
+
if (!(await confirm("switch branches"))) return;
+
try {
+
await natives.updateMoonlight(branch);
+
await electron.dialog.showMessageBox({ message: "Branch switch successful, restarting Discord." });
+
electron.app.relaunch();
+
electron.app.exit(0);
+
} catch (e) {
+
await electron.dialog.showMessageBox({ message: "Failed to switch branches:\n" + e, type: "error" });
+
}
+
}
+
+
function showAbout() {
+
electron.dialog.showMessageBox({
+
title: "About moonlight",
+
message: `moonlight ${moonlightHost.branch} ${moonlightHost.version}`
+
});
+
}
+
+
electron.app.whenReady().then(() => {
+
const original = electron.Menu.buildFromTemplate;
+
electron.Menu.buildFromTemplate = function (entries) {
+
const i = entries.findIndex((e) => e.label === "Check for Updates...");
+
if (i === -1) return original.call(this, entries);
+
+
if (!entries.find((e) => e.label === "moonlight")) {
+
const options: Electron.MenuItemConstructorOptions[] = [
+
{ label: "Update and restart", click: updateAndRestart },
+
{ label: "Reset config", click: resetConfig }
+
];
+
+
if (moonlightHost.branch !== MoonlightBranch.DEV) {
+
options.push({
+
label: "Switch branch",
+
submenu: [MoonlightBranch.STABLE, MoonlightBranch.NIGHTLY].map((branch) => ({
+
label: branch,
+
type: "radio",
+
checked: moonlightHost.branch === branch,
+
click: () => changeBranch(branch)
+
}))
+
});
+
}
+
+
options.push({ label: "About", click: showAbout });
+
+
entries.splice(i + 1, 0, {
+
label: "moonlight",
+
submenu: options
+
});
+
}
+
+
return original.call(this, entries);
+
};
+
});
+87 -42
packages/core-extensions/src/moonbase/index.tsx
···
-
import { ExtensionWebExports } from "@moonlight-mod/types";
-
import ui from "./ui";
-
import { CircleXIconSVG, DownloadIconSVG, TrashIconSVG } from "./types";
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
+
+
export const patches: Patch[] = [
+
{
+
find: "window.DiscordErrors=",
+
replace: [
+
// replace reporting line with update status
+
{
+
// CvQlAA mapped to ERRORS_ACTION_TO_TAKE
+
// FIXME: Better patch find?
+
match: /,(\(0,(\i)\.jsx\))\("p",{children:\i\.\i\.string\(\i\.\i\.CvQlAA\)}\)/,
+
replacement: (_, createElement, ReactJSX) =>
+
`,${createElement}(require("moonbase_crashScreen")?.UpdateText??${ReactJSX}.Fragment,{state:this.state,setState:this.setState.bind(this)})`
+
},
+
+
// wrap actions field to display error details
+
{
+
match: /(?<=return(\(0,(\i)\.jsx\))\(.+?,)action:(\i),className:/,
+
replacement: (_, createElement, ReactJSX, action) =>
+
`action:require("moonbase_crashScreen")?.wrapAction?${createElement}(require("moonbase_crashScreen").wrapAction,{action:${action},state:this.state}):${action},className:`
+
},
+
+
// add update button
+
// +hivLS -> ERRORS_RELOAD
+
{
+
match: /(?<=\["\+hivLS"\]\)}\),(\(0,(\i)\.jsx\))\(\i,{}\))/,
+
replacement: (_, createElement, ReactJSX) =>
+
`,${createElement}(require("moonbase_crashScreen")?.UpdateButton??${ReactJSX}.Fragment,{state:this.state,setState:this.setState.bind(this)})`
+
}
+
]
+
}
+
];
-
export const webpackModules: ExtensionWebExports["webpackModules"] = {
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
stores: {
+
dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }]
+
},
+
+
ui: {
dependencies: [
-
{ ext: "common", id: "flux" },
-
{ ext: "common", id: "fluxDispatcher" }
+
{ ext: "spacepack", id: "spacepack" },
+
{ id: "react" },
+
{ id: "discord/components/common/index" },
+
{ ext: "moonbase", id: "stores" },
+
{ ext: "moonbase", id: "ThemeDarkIcon" },
+
{ id: "discord/modules/guild_settings/web/AppCard.css" },
+
{ ext: "contextMenu", id: "contextMenu" },
+
{ id: "discord/modules/modals/Modals" },
+
"Masks.PANEL_BUTTON",
+
'"Missing channel in Channel.openChannelContextMenu"',
+
".forumOrHome]:"
]
},
-
moonbase: {
+
ThemeDarkIcon: {
+
dependencies: [{ ext: "common", id: "icons" }, { id: "react" }]
+
},
+
+
settings: {
dependencies: [
{ ext: "spacepack", id: "spacepack" },
{ ext: "settings", id: "settings" },
-
{ ext: "common", id: "react" },
-
{ ext: "common", id: "components" },
+
{ id: "react" },
+
{ ext: "moonbase", id: "ui" },
+
{ ext: "contextMenu", id: "contextMenu" },
+
':"USER_SETTINGS_MODAL_SET_SECTION"'
+
],
+
entrypoint: true
+
},
+
+
updates: {
+
dependencies: [
+
{ id: "react" },
{ ext: "moonbase", id: "stores" },
-
DownloadIconSVG,
-
TrashIconSVG,
-
CircleXIconSVG,
-
"Masks.PANEL_BUTTON",
-
"removeButtonContainer:",
-
'"Missing channel in Channel.openChannelContextMenu"'
+
{ ext: "moonbase", id: "ThemeDarkIcon" },
+
{ ext: "notices", id: "notices" },
+
{
+
ext: "spacepack",
+
id: "spacepack"
+
},
+
{ id: "discord/Constants" },
+
{ id: "discord/components/common/index" }
],
-
entrypoint: true,
-
run: (module, exports, require) => {
-
const settings = require("settings_settings").Settings;
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("./webpackModules/stores");
+
entrypoint: true
+
},
-
settings.addSection("Moonbase", "Moonbase", ui(require), null, -2, {
-
stores: [MoonbaseSettingsStore],
-
element: () => {
-
// Require it here because lazy loading SUX
-
const SettingsNotice =
-
spacepack.findByCode("onSaveButtonColor")[0].exports.default;
-
return (
-
<SettingsNotice
-
submitting={MoonbaseSettingsStore.submitting}
-
onReset={() => {
-
MoonbaseSettingsStore.reset();
-
}}
-
onSave={() => {
-
MoonbaseSettingsStore.writeConfig();
-
}}
-
/>
-
);
-
}
-
});
-
}
+
moonbase: {
+
dependencies: [{ ext: "moonbase", id: "stores" }]
+
},
+
+
crashScreen: {
+
dependencies: [
+
{ ext: "spacepack", id: "spacepack" },
+
{ id: "react" },
+
{ ext: "moonbase", id: "stores" },
+
{ id: "discord/packages/flux" },
+
{ id: "discord/components/common/index" },
+
/tabBar:"tabBar_[a-z0-9]+",tabBarItem:"tabBarItem_[a-z0-9]+"/
+
]
}
};
+44 -2
packages/core-extensions/src/moonbase/manifest.json
···
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "moonbase",
+
"apiLevel": 2,
"meta": {
"name": "Moonbase",
"tagline": "The official settings UI for moonlight",
-
"authors": ["Cynosphere", "NotNite"]
+
"authors": ["Cynosphere", "NotNite", "redstonekasi"]
},
-
"dependencies": ["spacepack", "settings", "common"]
+
"dependencies": ["spacepack", "settings", "common", "notices", "contextMenu"],
+
"settings": {
+
"sections": {
+
"advice": "reload",
+
"displayName": "Split into sections",
+
"description": "Show the Moonbase tabs as separate sections",
+
"type": "boolean",
+
"default": false
+
},
+
"oldLocation": {
+
"advice": "reload",
+
"displayName": "Put Moonbase back at the bottom",
+
"type": "boolean",
+
"default": false
+
},
+
"saveFilter": {
+
"advice": "none",
+
"displayName": "Persist filter",
+
"description": "Save extension filter in config",
+
"type": "boolean",
+
"default": false
+
},
+
"updateChecking": {
+
"advice": "none",
+
"displayName": "Automatic update checking",
+
"description": "Checks for updates to moonlight",
+
"type": "boolean",
+
"default": true
+
},
+
"updateBanner": {
+
"advice": "none",
+
"displayName": "Show update banner",
+
"description": "Shows a banner for moonlight and extension updates",
+
"type": "boolean",
+
"default": true
+
}
+
},
+
"cors": [
+
"https://github.com/moonlight-mod/moonlight/releases/download/",
+
"https://objects.githubusercontent.com/github-production-release-asset-"
+
]
}
+178
packages/core-extensions/src/moonbase/native.ts
···
+
import { MoonlightBranch } from "@moonlight-mod/types";
+
import type { MoonbaseNatives, RepositoryManifest } from "./types";
+
import extractAsar from "@moonlight-mod/core/asar";
+
import { distDir, repoUrlFile, installedVersionFile } from "@moonlight-mod/types/constants";
+
import { parseTarGzip } from "nanotar";
+
+
const moonlightGlobal = globalThis.moonlightHost ?? globalThis.moonlightNode;
+
+
const githubRepo = "moonlight-mod/moonlight";
+
const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`;
+
const artifactName = "dist.tar.gz";
+
+
const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref";
+
const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz";
+
+
export const userAgent = `moonlight/${moonlightGlobal.version} (https://github.com/moonlight-mod/moonlight)`;
+
+
// User-Agent header causes trouble on Firefox
+
const isBrowser = globalThis.moonlightNode != null && globalThis.moonlightNode.isBrowser;
+
const sharedHeaders: Record<string, string> = {};
+
if (!isBrowser) sharedHeaders["User-Agent"] = userAgent;
+
+
async function getStableRelease(): Promise<{
+
name: string;
+
assets: {
+
name: string;
+
browser_download_url: string;
+
}[];
+
}> {
+
const req = await fetch(githubApiUrl, {
+
cache: "no-store",
+
headers: sharedHeaders
+
});
+
return await req.json();
+
}
+
+
export default function getNatives(): MoonbaseNatives {
+
const logger = moonlightGlobal.getLogger("moonbase/natives");
+
+
return {
+
async checkForMoonlightUpdate() {
+
try {
+
if (moonlightGlobal.branch === MoonlightBranch.STABLE) {
+
const json = await getStableRelease();
+
return json.name !== moonlightGlobal.version ? json.name : null;
+
} else if (moonlightGlobal.branch === MoonlightBranch.NIGHTLY) {
+
const req = await fetch(nightlyRefUrl, {
+
cache: "no-store",
+
headers: sharedHeaders
+
});
+
const ref = (await req.text()).split("\n")[0];
+
return ref !== moonlightGlobal.version ? ref : null;
+
}
+
+
return null;
+
} catch (e) {
+
logger.error("Error checking for moonlight update", e);
+
return null;
+
}
+
},
+
+
async updateMoonlight(overrideBranch?: MoonlightBranch) {
+
const branch = overrideBranch ?? moonlightGlobal.branch;
+
+
// Note: this won't do anything on browser, we should probably disable it
+
// entirely when running in browser.
+
async function downloadStable(): Promise<[ArrayBuffer, string]> {
+
const json = await getStableRelease();
+
const asset = json.assets.find((a) => a.name === artifactName);
+
if (!asset) throw new Error("Artifact not found");
+
+
logger.debug(`Downloading ${asset.browser_download_url}`);
+
const req = await fetch(asset.browser_download_url, {
+
cache: "no-store",
+
headers: sharedHeaders
+
});
+
+
return [await req.arrayBuffer(), json.name];
+
}
+
+
async function downloadNightly(): Promise<[ArrayBuffer, string]> {
+
logger.debug(`Downloading ${nightlyZipUrl}`);
+
const zipReq = await fetch(nightlyZipUrl, {
+
cache: "no-store",
+
headers: sharedHeaders
+
});
+
+
const refReq = await fetch(nightlyRefUrl, {
+
cache: "no-store",
+
headers: sharedHeaders
+
});
+
const ref = (await refReq.text()).split("\n")[0];
+
+
return [await zipReq.arrayBuffer(), ref];
+
}
+
+
const [tar, ref] =
+
branch === MoonlightBranch.STABLE
+
? await downloadStable()
+
: branch === MoonlightBranch.NIGHTLY
+
? await downloadNightly()
+
: [null, null];
+
+
if (!tar || !ref) return;
+
+
const dist = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), distDir);
+
if (await moonlightNodeSandboxed.fs.exists(dist)) await moonlightNodeSandboxed.fs.rmdir(dist);
+
await moonlightNodeSandboxed.fs.mkdir(dist);
+
+
logger.debug("Extracting update");
+
const files = await parseTarGzip(tar);
+
for (const file of files) {
+
if (!file.data) continue;
+
// @ts-expect-error What do you mean their own types are wrong
+
if (file.type !== "file") continue;
+
+
const fullFile = moonlightNodeSandboxed.fs.join(dist, file.name);
+
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
+
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
+
await moonlightNodeSandboxed.fs.writeFile(fullFile, file.data);
+
}
+
+
logger.debug("Writing version file:", ref);
+
const versionFile = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), installedVersionFile);
+
await moonlightNodeSandboxed.fs.writeFileString(versionFile, ref.trim());
+
+
logger.debug("Update extracted");
+
},
+
+
async fetchRepositories(repos) {
+
const ret: Record<string, RepositoryManifest[]> = {};
+
+
for (const repo of repos) {
+
try {
+
const req = await fetch(repo, {
+
cache: "no-store",
+
headers: sharedHeaders
+
});
+
const json = await req.json();
+
ret[repo] = json;
+
} catch (e) {
+
logger.error(`Error fetching repository ${repo}`, e);
+
}
+
}
+
+
return ret;
+
},
+
+
async installExtension(manifest, url, repo) {
+
const req = await fetch(url, {
+
cache: "no-store",
+
headers: sharedHeaders
+
});
+
+
const dir = moonlightGlobal.getExtensionDir(manifest.id);
+
// remake it in case of updates
+
if (await moonlightNodeSandboxed.fs.exists(dir)) await moonlightNodeSandboxed.fs.rmdir(dir);
+
await moonlightNodeSandboxed.fs.mkdir(dir);
+
+
const buffer = await req.arrayBuffer();
+
const files = extractAsar(buffer);
+
for (const [file, buf] of Object.entries(files)) {
+
const fullFile = moonlightNodeSandboxed.fs.join(dir, file);
+
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
+
+
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
+
await moonlightNodeSandboxed.fs.writeFile(moonlightNodeSandboxed.fs.join(dir, file), buf);
+
}
+
+
await moonlightNodeSandboxed.fs.writeFileString(moonlightNodeSandboxed.fs.join(dir, repoUrlFile), repo);
+
},
+
+
async deleteExtension(id) {
+
const dir = moonlightGlobal.getExtensionDir(id);
+
await moonlightNodeSandboxed.fs.rmdir(dir);
+
}
+
};
+
}
+2 -69
packages/core-extensions/src/moonbase/node.ts
···
-
import { MoonbaseNatives, RepositoryManifest } from "./types";
-
import asar from "@electron/asar";
-
import fs from "fs";
-
import path from "path";
-
import os from "os";
-
import { repoUrlFile } from "types/src/constants";
-
-
const logger = moonlightNode.getLogger("moonbase");
-
-
async function fetchRepositories(repos: string[]) {
-
const ret: Record<string, RepositoryManifest[]> = {};
-
-
for (const repo of repos) {
-
try {
-
const req = await fetch(repo);
-
const json = await req.json();
-
ret[repo] = json;
-
} catch (e) {
-
logger.error(`Error fetching repository ${repo}`, e);
-
}
-
}
-
-
return ret;
-
}
-
-
async function installExtension(
-
manifest: RepositoryManifest,
-
url: string,
-
repo: string
-
) {
-
const req = await fetch(url);
-
-
const dir = moonlightNode.getExtensionDir(manifest.id);
-
// remake it in case of updates
-
if (fs.existsSync(dir)) fs.rmdirSync(dir, { recursive: true });
-
fs.mkdirSync(dir, { recursive: true });
-
-
// for some reason i just can't .writeFileSync() a file that ends in .asar???
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "moonlight-"));
-
const tempFile = path.join(tempDir, "extension");
-
const buffer = await req.arrayBuffer();
-
fs.writeFileSync(tempFile, Buffer.from(buffer));
-
-
asar.extractAll(tempFile, dir);
-
fs.writeFileSync(path.join(dir, repoUrlFile), repo);
-
}
-
-
async function deleteExtension(id: string) {
-
const dir = moonlightNode.getExtensionDir(id);
-
fs.rmdirSync(dir, { recursive: true });
-
}
-
-
function getExtensionConfig(id: string, key: string): any {
-
const config = moonlightNode.config.extensions[id];
-
if (typeof config === "object") {
-
return config.config?.[key];
-
}
-
-
return undefined;
-
}
-
-
const exports: MoonbaseNatives = {
-
fetchRepositories,
-
installExtension,
-
deleteExtension,
-
getExtensionConfig
-
};
-
-
module.exports = exports;
+
import getNatives from "./native";
+
module.exports = getNatives();
+269
packages/core-extensions/src/moonbase/style.css
···
+
:root {
+
--moonbase-bg: #222034;
+
--moonbase-fg: #fffba6;
+
}
+
+
.moonbase-settings > :first-child {
+
margin-top: 0px;
+
}
+
+
.moonbase-retry-button {
+
padding: 8px;
+
margin-right: 8px;
+
}
+
+
textarea.moonbase-resizeable {
+
resize: vertical;
+
}
+
+
.moonbase-link-buttons {
+
border-bottom: 2px solid var(--background-modifier-accent);
+
margin-bottom: -2px;
+
margin-left: 0 !important;
+
padding-right: 20px;
+
gap: 1rem;
+
}
+
+
.moonbase-speen {
+
animation: moonbase-speen-animation 0.25s linear infinite;
+
}
+
+
@keyframes moonbase-speen-animation {
+
from {
+
transform: rotate(0deg);
+
}
+
to {
+
transform: rotate(360deg);
+
}
+
}
+
+
/* Update notice at the top of the client */
+
.moonbase-updates-notice {
+
background-color: var(--moonbase-bg);
+
color: var(--moonbase-fg);
+
--custom-notice-text: var(--moonbase-fg);
+
line-height: unset;
+
height: 36px;
+
}
+
+
.moonbase-updates-notice button {
+
color: var(--moonbase-fg);
+
border-color: var(--moonbase-fg);
+
}
+
+
.moonbase-updates-notice_text-wrapper {
+
display: inline-flex;
+
align-items: center;
+
line-height: 36px;
+
gap: 2px;
+
}
+
+
/* Help messages in Moonbase UI */
+
.moonbase-help-message {
+
display: flex;
+
flex-direction: row;
+
justify-content: space-between;
+
}
+
+
.moonbase-help-message-sticky {
+
position: sticky;
+
top: 24px;
+
z-index: 10;
+
background-color: var(--background-primary);
+
}
+
+
.moonbase-extension-update-section {
+
margin-top: 15px;
+
}
+
+
.moonbase-update-section {
+
background-color: var(--moonbase-bg);
+
--info-help-foreground: var(--moonbase-fg);
+
border: none !important;
+
color: var(--moonbase-fg);
+
}
+
+
.moonbase-update-section button {
+
--info-help-foreground: var(--moonbase-fg);
+
color: var(--moonbase-fg);
+
background-color: transparent;
+
border-color: var(--moonbase-fg);
+
}
+
+
.moonbase-help-message-buttons {
+
display: flex;
+
flex-direction: row;
+
gap: 8px;
+
align-items: center;
+
}
+
+
.moonbase-update-divider {
+
margin: 32px 0;
+
}
+
+
.moonlight-card-info-header {
+
margin-bottom: 0.25rem;
+
}
+
+
.moonlight-card-badge {
+
border-radius: 0.1875rem;
+
padding: 0 0.275rem;
+
margin-right: 0.4em;
+
background-color: var(--badge-color, var(--bg-mod-strong));
+
}
+
+
/* Crash screen */
+
.moonbase-crash-wrapper > [class^="buttons_"] {
+
gap: 1rem;
+
}
+
+
.moonbase-crash-wrapper {
+
display: flex;
+
flex-direction: column;
+
align-items: center;
+
gap: 1rem;
+
height: 50%;
+
width: 50vw;
+
max-height: 50%;
+
max-width: 50vw;
+
}
+
+
.moonbase-crash-tabs {
+
width: 100%;
+
}
+
+
.moonbase-crash-details-wrapper {
+
overflow-y: scroll;
+
color: var(--text-normal);
+
background: var(--background-secondary);
+
border: 1px solid var(--background-tertiary);
+
border-radius: 4px;
+
padding: 0.5em;
+
+
&::-webkit-scrollbar {
+
width: 8px;
+
height: 8px;
+
}
+
+
&::-webkit-scrollbar-thumb {
+
background-clip: padding-box;
+
border: 2px solid transparent;
+
border-radius: 4px;
+
background-color: var(--scrollbar-thin-thumb);
+
min-height: 40px;
+
}
+
+
&::-webkit-scrollbar-track {
+
border: 2px solid var(--scrollbar-thin-track);
+
background-color: var(--scrollbar-thin-track);
+
border-color: var(--scrollbar-thin-track);
+
}
+
}
+
+
.moonbase-crash-details {
+
box-sizing: border-box;
+
padding: 0;
+
font-family: var(--font-code);
+
font-size: 0.75rem;
+
line-height: 1rem;
+
margin: 6px;
+
white-space: pre-wrap;
+
background-clip: border-box;
+
+
& > code {
+
font-size: 0.875rem;
+
line-height: 1.125rem;
+
text-indent: 0;
+
white-space: pre-wrap;
+
text-size-adjust: none;
+
display: block;
+
user-select: text;
+
}
+
}
+
+
.moonbase-crash-extensions {
+
overflow-y: scroll;
+
display: grid;
+
grid-auto-columns: 25vw;
+
gap: 8px;
+
+
&::-webkit-scrollbar {
+
width: 8px;
+
height: 8px;
+
}
+
+
&::-webkit-scrollbar-thumb {
+
background-clip: padding-box;
+
border: 2px solid transparent;
+
border-radius: 4px;
+
background-color: var(--scrollbar-thin-thumb);
+
min-height: 40px;
+
}
+
+
&::-webkit-scrollbar-track {
+
border: 2px solid var(--scrollbar-thin-track);
+
background-color: var(--scrollbar-thin-track);
+
border-color: var(--scrollbar-thin-track);
+
}
+
}
+
+
.moonbase-crash-extensionCard {
+
color: var(--text-normal);
+
background: var(--background-secondary);
+
border: 1px solid var(--background-tertiary);
+
border-radius: 4px;
+
padding: 0.5em;
+
display: flex;
+
}
+
+
.moonbase-crash-extensionCard-meta {
+
display: flex;
+
flex-direction: column;
+
flex-grow: 1;
+
}
+
+
.moonbase-crash-extensionCard-title {
+
color: var(--text-normal);
+
font-family: var(--font-primary);
+
font-size: 16px;
+
line-height: 1.25;
+
font-weight: 600;
+
}
+
+
.moonbase-crash-extensionCard-version {
+
color: var(--text-muted);
+
font-family: var(--font-primary);
+
font-size: 14px;
+
line-height: 1.286;
+
font-weight: 400;
+
}
+
+
/* About page */
+
.moonbase-wordmark {
+
width: 100%;
+
}
+
+
.moonbase-devs {
+
width: 100%;
+
display: flex;
+
justify-content: center;
+
gap: 0rem 0.5rem;
+
padding-top: 0.5rem;
+
}
+
+
.moonbase-dev {
+
height: 4rem;
+
}
+
+
.moonbase-dev-avatar {
+
width: 2rem;
+
border-radius: 50%;
+
}
+
+
.moonbase-gap {
+
gap: 0.5rem;
+
}
+
+
.moonbase-about-page {
+
gap: 1rem;
+
}
+27 -25
packages/core-extensions/src/moonbase/types.ts
···
-
import { DetectedExtension, ExtensionManifest } from "types/src";
-
-
export const DownloadIconSVG =
-
"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";
-
export const TrashIconSVG =
-
"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";
-
export const CircleXIconSVG =
-
"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";
-
export const DangerIconSVG =
-
"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";
-
export const ChevronSmallDownIconSVG =
-
"M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z";
-
export const ChevronSmallUpIconSVG =
-
"M7.41 16.0001L12 11.4201L16.59 16.0001L18 14.5901L12 8.59006L6 14.5901L7.41 16.0001Z";
-
export const ArrowsUpDownIconSVG =
-
"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";
+
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
+
import { DetectedExtension, ExtensionManifest, MoonlightBranch } from "@moonlight-mod/types";
export type MoonbaseNatives = {
-
fetchRepositories(
-
repos: string[]
-
): Promise<Record<string, RepositoryManifest[]>>;
-
installExtension(
-
manifest: RepositoryManifest,
-
url: string,
-
repo: string
-
): Promise<void>;
+
checkForMoonlightUpdate(): Promise<string | null>;
+
updateMoonlight(overrideBranch?: MoonlightBranch): Promise<void>;
+
+
fetchRepositories(repos: string[]): Promise<Record<string, RepositoryManifest[]>>;
+
installExtension(manifest: RepositoryManifest, url: string, repo: string): Promise<void>;
deleteExtension(id: string): Promise<void>;
-
getExtensionConfig(id: string, key: string): any;
};
export type RepositoryManifest = ExtensionManifest & {
···
export type MoonbaseExtension = {
id: string;
+
uniqueId: number;
manifest: ExtensionManifest | RepositoryManifest;
source: DetectedExtension["source"];
state: ExtensionState;
+
compat: ExtensionCompat;
+
hasUpdate: boolean;
+
changelog?: string;
+
settingsOverride?: ExtensionManifest["settings"];
};
+
+
export enum UpdateState {
+
Ready,
+
Working,
+
Installed,
+
Failed
+
}
+
+
// Ordered in terms of priority
+
export enum RestartAdvice {
+
NotNeeded, // No action is needed
+
ReloadSuggested, // A reload might be needed
+
ReloadNeeded, // A reload is needed
+
RestartNeeded // A restart is needed
+
}
-221
packages/core-extensions/src/moonbase/ui/card.tsx
···
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
-
import {
-
DangerIconSVG,
-
DownloadIconSVG,
-
ExtensionState,
-
TrashIconSVG
-
} from "../types";
-
import { ExtensionLoadSource } from "types/src";
-
import info from "./info";
-
import settings from "./settings";
-
-
export enum ExtensionPage {
-
Info,
-
Description,
-
Settings
-
}
-
-
export default (require: typeof WebpackRequire) => {
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
const CommonComponents = require("common_components");
-
const Flux = require("common_flux");
-
-
const { ExtensionInfo } = info(require);
-
const Settings = settings(require);
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("../webpackModules/stores");
-
-
const UserProfileClasses = spacepack.findByCode(
-
"tabBarContainer",
-
"topSection"
-
)[0].exports;
-
-
const DownloadIcon = spacepack.findByCode(DownloadIconSVG)[0].exports.default;
-
const TrashIcon = spacepack.findByCode(TrashIconSVG)[0].exports.default;
-
const DangerIcon =
-
spacepack.findByCode(DangerIconSVG)[0].exports.CircleExclamationPointIcon;
-
-
const PanelButton =
-
spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.default;
-
-
return function ExtensionCard({ id }: { id: string }) {
-
const [tab, setTab] = React.useState(ExtensionPage.Info);
-
const [restartNeeded, setRestartNeeded] = React.useState(false);
-
-
const { ext, enabled, busy, update } = Flux.useStateFromStores(
-
[MoonbaseSettingsStore],
-
() => {
-
return {
-
ext: MoonbaseSettingsStore.getExtension(id),
-
enabled: MoonbaseSettingsStore.getExtensionEnabled(id),
-
busy: MoonbaseSettingsStore.busy,
-
update: MoonbaseSettingsStore.getExtensionUpdate(id)
-
};
-
}
-
);
-
-
// Why it work like that :sob:
-
if (ext == null) return <></>;
-
-
const {
-
Card,
-
CardClasses,
-
Flex,
-
Text,
-
MarkdownParser,
-
Switch,
-
TabBar,
-
Button
-
} = CommonComponents;
-
-
const tagline = ext.manifest?.meta?.tagline;
-
const settings = ext.manifest?.settings;
-
const description = ext.manifest?.meta?.description;
-
-
return (
-
<Card editable={true} className={CardClasses.card}>
-
<div className={CardClasses.cardHeader}>
-
<Flex direction={Flex.Direction.VERTICAL}>
-
<Flex direction={Flex.Direction.HORIZONTAL}>
-
<Text variant="text-md/semibold">
-
{ext.manifest?.meta?.name ?? ext.id}
-
</Text>
-
</Flex>
-
-
{tagline != null && (
-
<Text variant="text-sm/normal">
-
{MarkdownParser.parse(tagline)}
-
</Text>
-
)}
-
</Flex>
-
-
<Flex
-
direction={Flex.Direction.HORIZONTAL}
-
align={Flex.Align.END}
-
justify={Flex.Justify.END}
-
>
-
{ext.state === ExtensionState.NotDownloaded ? (
-
<Button
-
color={Button.Colors.BRAND}
-
submitting={busy}
-
onClick={() => {
-
MoonbaseSettingsStore.installExtension(id);
-
}}
-
>
-
Install
-
</Button>
-
) : (
-
<div
-
// too lazy to learn how <Flex /> works lmao
-
style={{
-
display: "flex",
-
alignItems: "center",
-
gap: "1rem"
-
}}
-
>
-
{ext.source.type === ExtensionLoadSource.Normal && (
-
<PanelButton
-
icon={TrashIcon}
-
tooltipText="Delete"
-
onClick={() => {
-
MoonbaseSettingsStore.deleteExtension(id);
-
}}
-
/>
-
)}
-
-
{update !== null && (
-
<PanelButton
-
icon={DownloadIcon}
-
tooltipText="Delete"
-
onClick={() => {
-
MoonbaseSettingsStore.installExtension(id);
-
}}
-
/>
-
)}
-
-
{restartNeeded && (
-
<PanelButton
-
icon={() => (
-
<DangerIcon
-
color={CommonComponents.tokens.colors.STATUS_DANGER}
-
/>
-
)}
-
tooltipText="You will need to reload/restart your client for this extension to work properly."
-
/>
-
)}
-
-
<Switch
-
checked={enabled}
-
onChange={() => {
-
setRestartNeeded(true);
-
MoonbaseSettingsStore.setExtensionEnabled(id, !enabled);
-
}}
-
/>
-
</div>
-
)}
-
</Flex>
-
</div>
-
-
<div className={UserProfileClasses.body}>
-
{(description != null || settings != null) && (
-
<div
-
className={UserProfileClasses.tabBarContainer}
-
style={{
-
padding: "0 10px"
-
}}
-
>
-
<TabBar
-
selectedItem={tab}
-
type="top"
-
onItemSelect={setTab}
-
className={UserProfileClasses.tabBar}
-
>
-
<TabBar.Item
-
className={UserProfileClasses.tabBarItem}
-
id={ExtensionPage.Info}
-
>
-
Info
-
</TabBar.Item>
-
-
{description != null && (
-
<TabBar.Item
-
className={UserProfileClasses.tabBarItem}
-
id={ExtensionPage.Description}
-
>
-
Description
-
</TabBar.Item>
-
)}
-
-
{settings != null && (
-
<TabBar.Item
-
className={UserProfileClasses.tabBarItem}
-
id={ExtensionPage.Settings}
-
>
-
Settings
-
</TabBar.Item>
-
)}
-
</TabBar>
-
</div>
-
)}
-
-
<Flex
-
justify={Flex.Justify.START}
-
wrap={Flex.Wrap.WRAP}
-
style={{
-
padding: "16px 16px"
-
}}
-
>
-
{tab === ExtensionPage.Info && <ExtensionInfo ext={ext} />}
-
{tab === ExtensionPage.Description && (
-
<Text variant="text-md/normal">
-
{MarkdownParser.parse(description ?? "*No description*")}
-
</Text>
-
)}
-
{tab === ExtensionPage.Settings && <Settings ext={ext} />}
-
</Flex>
-
</div>
-
</Card>
-
);
-
};
-
};
-378
packages/core-extensions/src/moonbase/ui/filterBar.tsx
···
-
import { WebpackRequireType } from "@moonlight-mod/types";
-
import { tagNames } from "./info";
-
import {
-
ArrowsUpDownIconSVG,
-
ChevronSmallDownIconSVG,
-
ChevronSmallUpIconSVG
-
} from "../types";
-
-
export const defaultFilter = {
-
core: true,
-
normal: true,
-
developer: true,
-
enabled: true,
-
disabled: true,
-
installed: true,
-
repository: true
-
};
-
export type Filter = typeof defaultFilter;
-
-
export default async (require: WebpackRequireType) => {
-
const spacepack = require("spacepack_spacepack").spacepack;
-
const React = require("common_react");
-
const Flux = require("common_flux");
-
const { WindowStore } = require("common_stores");
-
-
const {
-
Button,
-
Text,
-
Heading,
-
Popout,
-
Dialog
-
} = require("common_components");
-
-
const channelModule =
-
require.m[
-
spacepack.findByCode(
-
'"Missing channel in Channel.openChannelContextMenu"'
-
)[0].id
-
].toString();
-
const moduleId = channelModule.match(/webpackId:"(.+?)"/)![1];
-
await require.el(moduleId);
-
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
-
const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0]
-
.exports;
-
const FilterDialogClasses = spacepack.findByCode(
-
"countContainer:",
-
"tagContainer:"
-
)[0].exports;
-
const FilterBarClasses = spacepack.findByCode("tagsButtonWithCount:")[0]
-
.exports;
-
-
const TagItem = spacepack.findByCode("IncreasedActivityForumTagPill:")[0]
-
.exports.default;
-
-
const ChevronSmallDownIcon = spacepack.findByCode(ChevronSmallDownIconSVG)[0]
-
.exports.default;
-
const ChevronSmallUpIcon = spacepack.findByCode(ChevronSmallUpIconSVG)[0]
-
.exports.default;
-
const ArrowsUpDownIcon =
-
spacepack.findByCode(ArrowsUpDownIconSVG)[0].exports.default;
-
-
function toggleTag(
-
selectedTags: Set<string>,
-
setSelectedTags: (tags: Set<string>) => void,
-
tag: string
-
) {
-
const newState = new Set(selectedTags);
-
if (newState.has(tag)) newState.delete(tag);
-
else newState.add(tag);
-
setSelectedTags(newState);
-
}
-
-
function FilterButtonPopout({
-
filter,
-
setFilter,
-
closePopout
-
}: {
-
filter: Filter;
-
setFilter: (filter: Filter) => void;
-
closePopout: () => void;
-
}) {
-
const {
-
Menu,
-
MenuItem,
-
MenuGroup,
-
MenuCheckboxItem
-
} = require("common_components");
-
-
return (
-
<div className={SortMenuClasses.container}>
-
<Menu navId="sort-filter" hideScrollbar={true} onClose={closePopout}>
-
<MenuGroup label="Type">
-
<MenuCheckboxItem
-
id="t-core"
-
label="Core"
-
checked={filter.core}
-
action={() => setFilter({ ...filter, core: !filter.core })}
-
/>
-
<MenuCheckboxItem
-
id="t-normal"
-
label="Normal"
-
checked={filter.normal}
-
action={() => setFilter({ ...filter, normal: !filter.normal })}
-
/>
-
<MenuCheckboxItem
-
id="t-developer"
-
label="Developer"
-
checked={filter.developer}
-
action={() =>
-
setFilter({ ...filter, developer: !filter.developer })
-
}
-
/>
-
</MenuGroup>
-
<MenuGroup label="State">
-
<MenuCheckboxItem
-
id="s-enabled"
-
label="Enabled"
-
checked={filter.enabled}
-
action={() => setFilter({ ...filter, enabled: !filter.enabled })}
-
/>
-
<MenuCheckboxItem
-
id="s-disabled"
-
label="Disabled"
-
checked={filter.disabled}
-
action={() =>
-
setFilter({ ...filter, disabled: !filter.disabled })
-
}
-
/>
-
</MenuGroup>
-
<MenuGroup label="Location">
-
<MenuCheckboxItem
-
id="l-installed"
-
label="Installed"
-
checked={filter.installed}
-
action={() =>
-
setFilter({ ...filter, installed: !filter.installed })
-
}
-
/>
-
<MenuCheckboxItem
-
id="l-repository"
-
label="Repository"
-
checked={filter.repository}
-
action={() =>
-
setFilter({ ...filter, repository: !filter.repository })
-
}
-
/>
-
</MenuGroup>
-
<MenuGroup>
-
<MenuItem
-
id="reset-all"
-
className={SortMenuClasses.clearText}
-
label={
-
<Text variant="text-sm/medium" color="none">
-
Reset to default
-
</Text>
-
}
-
action={() => {
-
setFilter({ ...defaultFilter });
-
closePopout();
-
}}
-
/>
-
</MenuGroup>
-
</Menu>
-
</div>
-
);
-
}
-
-
function TagButtonPopout({
-
selectedTags,
-
setSelectedTags,
-
setPopoutRef,
-
closePopout
-
}: any) {
-
return (
-
<Dialog ref={setPopoutRef} className={FilterDialogClasses.container}>
-
<div className={FilterDialogClasses.header}>
-
<div className={FilterDialogClasses.headerLeft}>
-
<Heading
-
color="interactive-normal"
-
variant="text-xs/bold"
-
className={FilterDialogClasses.headerText}
-
>
-
Select tags
-
</Heading>
-
<div className={FilterDialogClasses.countContainer}>
-
<Text
-
className={FilterDialogClasses.countText}
-
color="none"
-
variant="text-xs/medium"
-
>
-
{selectedTags.size}
-
</Text>
-
</div>
-
</div>
-
</div>
-
<div className={FilterDialogClasses.tagContainer}>
-
{Object.keys(tagNames).map((tag) => (
-
<TagItem
-
key={tag}
-
className={FilterDialogClasses.tag}
-
tag={{ name: tagNames[tag as keyof typeof tagNames] }}
-
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
-
selected={selectedTags.has(tag)}
-
/>
-
))}
-
</div>
-
<div className={FilterDialogClasses.separator} />
-
<Button
-
look={Button.Looks.LINK}
-
size={Button.Sizes.MIN}
-
color={Button.Colors.CUSTOM}
-
className={FilterDialogClasses.clear}
-
onClick={() => {
-
setSelectedTags(new Set());
-
closePopout();
-
}}
-
>
-
<Text variant="text-sm/medium" color="text-link">
-
Clear all
-
</Text>
-
</Button>
-
</Dialog>
-
);
-
}
-
-
return function FilterBar({
-
filter,
-
setFilter,
-
selectedTags,
-
setSelectedTags
-
}: {
-
filter: Filter;
-
setFilter: (filter: Filter) => void;
-
selectedTags: Set<string>;
-
setSelectedTags: (tags: Set<string>) => void;
-
}) {
-
const windowSize = Flux.useStateFromStores([WindowStore], () =>
-
WindowStore.windowSize()
-
);
-
-
const tagsContainer = React.useRef<HTMLDivElement>(null);
-
const tagListInner = React.useRef<HTMLDivElement>(null);
-
const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0);
-
React.useLayoutEffect(() => {
-
if (tagsContainer.current === null || tagListInner.current === null)
-
return;
-
const { left: containerX, top: containerY } =
-
tagsContainer.current.getBoundingClientRect();
-
let offset = 0;
-
for (const child of tagListInner.current.children) {
-
const {
-
right: childX,
-
top: childY,
-
height
-
} = child.getBoundingClientRect();
-
if (childY - containerY > height) break;
-
const newOffset = childX - containerX;
-
if (newOffset > offset) {
-
offset = newOffset;
-
}
-
}
-
setTagsButtonOffset(offset);
-
}, [windowSize]);
-
-
return (
-
<div
-
ref={tagsContainer}
-
style={{
-
paddingTop: "12px"
-
}}
-
className={`${FilterBarClasses.tagsContainer} ${Margins.marginBottom8}`}
-
>
-
<Popout
-
renderPopout={({ closePopout }: any) => (
-
<FilterButtonPopout
-
filter={filter}
-
setFilter={setFilter}
-
closePopout={closePopout}
-
/>
-
)}
-
position="bottom"
-
align="left"
-
>
-
{(props: any, { isShown }: { isShown: boolean }) => (
-
<Button
-
{...props}
-
size={Button.Sizes.MIN}
-
color={Button.Colors.CUSTOM}
-
className={FilterBarClasses.sortDropdown}
-
innerClassName={FilterBarClasses.sortDropdownInner}
-
>
-
<ArrowsUpDownIcon />
-
<Text
-
className={FilterBarClasses.sortDropdownText}
-
variant="text-sm/medium"
-
color="interactive-normal"
-
>
-
Sort & filter
-
</Text>
-
{isShown ? (
-
<ChevronSmallUpIcon size={20} />
-
) : (
-
<ChevronSmallDownIcon size={20} />
-
)}
-
</Button>
-
)}
-
</Popout>
-
<div className={FilterBarClasses.divider} />
-
<div className={FilterBarClasses.tagList}>
-
<div ref={tagListInner} className={FilterBarClasses.tagListInner}>
-
{Object.keys(tagNames).map((tag) => (
-
<TagItem
-
key={tag}
-
className={FilterBarClasses.tag}
-
tag={{ name: tagNames[tag as keyof typeof tagNames] }}
-
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
-
selected={selectedTags.has(tag)}
-
/>
-
))}
-
</div>
-
</div>
-
<Popout
-
renderPopout={({ setPopoutRef, closePopout }: any) => (
-
<TagButtonPopout
-
selectedTags={selectedTags}
-
setSelectedTags={setSelectedTags}
-
setPopoutRef={setPopoutRef}
-
closePopout={closePopout}
-
/>
-
)}
-
position="bottom"
-
align="right"
-
>
-
{(props: any, { isShown }: { isShown: boolean }) => (
-
<Button
-
{...props}
-
size={Button.Sizes.MIN}
-
color={Button.Colors.CUSTOM}
-
style={{
-
left: tagsButtonOffset
-
}}
-
// TODO: Use Discord's class name utility
-
className={`${FilterBarClasses.tagsButton} ${
-
selectedTags.size > 0
-
? FilterBarClasses.tagsButtonWithCount
-
: ""
-
}`}
-
innerClassName={FilterBarClasses.tagsButtonInner}
-
>
-
{selectedTags.size > 0 ? (
-
<div
-
style={{ boxSizing: "content-box" }}
-
className={FilterBarClasses.countContainer}
-
>
-
<Text
-
className={FilterBarClasses.countText}
-
color="none"
-
variant="text-xs/medium"
-
>
-
{selectedTags.size}
-
</Text>
-
</div>
-
) : (
-
<>All</>
-
)}
-
{isShown ? (
-
<ChevronSmallUpIcon size={20} />
-
) : (
-
<ChevronSmallDownIcon size={20} />
-
)}
-
</Button>
-
)}
-
</Popout>
-
</div>
-
);
-
};
-
};
-114
packages/core-extensions/src/moonbase/ui/index.tsx
···
-
import {
-
ExtensionLoadSource,
-
ExtensionTag,
-
WebpackRequireType
-
} from "@moonlight-mod/types";
-
import card from "./card";
-
import filterBar, { defaultFilter } from "./filterBar";
-
import { ExtensionState } from "../types";
-
-
export enum ExtensionPage {
-
Info,
-
Description,
-
Settings
-
}
-
-
export default (require: WebpackRequireType) => {
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
const Flux = require("common_flux");
-
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("../webpackModules/stores");
-
-
const ExtensionCard = card(require);
-
const FilterBar = React.lazy(() =>
-
filterBar(require).then((c) => ({ default: c }))
-
);
-
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
-
const SearchBar = spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0]
-
.exports.default;
-
-
return function Moonbase() {
-
const { Text } = require("common_components");
-
-
const { extensions } = Flux.useStateFromStoresObject(
-
[MoonbaseSettingsStore],
-
() => {
-
return { extensions: MoonbaseSettingsStore.extensions };
-
}
-
);
-
-
const [query, setQuery] = React.useState("");
-
const [filter, setFilter] = React.useState({ ...defaultFilter });
-
const [selectedTags, setSelectedTags] = React.useState(new Set<string>());
-
-
const sorted = Object.values(extensions).sort((a, b) => {
-
const aName = a.manifest.meta?.name ?? a.id;
-
const bName = b.manifest.meta?.name ?? b.id;
-
return aName.localeCompare(bName);
-
});
-
-
const filtered = sorted.filter(
-
(ext) =>
-
(ext.manifest.meta?.name?.toLowerCase().includes(query) ||
-
ext.manifest.meta?.tagline?.toLowerCase().includes(query) ||
-
ext.manifest.meta?.description?.toLowerCase().includes(query)) &&
-
[...selectedTags.values()].every(
-
(tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)
-
) &&
-
// This seems very bad, sorry
-
!(
-
(!filter.core && ext.source.type === ExtensionLoadSource.Core) ||
-
(!filter.normal && ext.source.type === ExtensionLoadSource.Normal) ||
-
(!filter.developer &&
-
ext.source.type === ExtensionLoadSource.Developer) ||
-
(!filter.enabled &&
-
MoonbaseSettingsStore.getExtensionEnabled(ext.id)) ||
-
(!filter.disabled &&
-
!MoonbaseSettingsStore.getExtensionEnabled(ext.id)) ||
-
(!filter.installed && ext.state !== ExtensionState.NotDownloaded) ||
-
(!filter.repository && ext.state === ExtensionState.NotDownloaded)
-
)
-
);
-
-
return (
-
<>
-
<Text
-
className={Margins.marginBottom20}
-
variant="heading-lg/semibold"
-
tag="h2"
-
>
-
Moonbase
-
</Text>
-
<SearchBar
-
size={SearchBar.Sizes.MEDIUM}
-
query={query}
-
onChange={(v: string) => setQuery(v.toLowerCase())}
-
onClear={() => setQuery("")}
-
autoFocus={true}
-
autoComplete="off"
-
inputProps={{
-
autoCapitalize: "none",
-
autoCorrect: "off",
-
spellCheck: "false"
-
}}
-
/>
-
<React.Suspense
-
fallback={<div className={Margins.marginBottom20}></div>}
-
>
-
<FilterBar
-
filter={filter}
-
setFilter={setFilter}
-
selectedTags={selectedTags}
-
setSelectedTags={setSelectedTags}
-
/>
-
</React.Suspense>
-
{filtered.map((ext) => (
-
<ExtensionCard id={ext.id} key={ext.id} />
-
))}
-
</>
-
);
-
};
-
};
-202
packages/core-extensions/src/moonbase/ui/info.tsx
···
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
-
import { ExtensionTag } from "@moonlight-mod/types";
-
import { MoonbaseExtension } from "../types";
-
-
type Dependency = {
-
id: string;
-
type: DependencyType;
-
};
-
-
enum DependencyType {
-
Dependency = "dependency",
-
Optional = "optional",
-
Incompatible = "incompatible"
-
}
-
-
export const tagNames: Record<ExtensionTag, string> = {
-
[ExtensionTag.Accessibility]: "Accessibility",
-
[ExtensionTag.Appearance]: "Appearance",
-
[ExtensionTag.Chat]: "Chat",
-
[ExtensionTag.Commands]: "Commands",
-
[ExtensionTag.ContextMenu]: "Context Menu",
-
[ExtensionTag.DangerZone]: "Danger Zone",
-
[ExtensionTag.Development]: "Development",
-
[ExtensionTag.Fixes]: "Fixes",
-
[ExtensionTag.Fun]: "Fun",
-
[ExtensionTag.Markdown]: "Markdown",
-
[ExtensionTag.Voice]: "Voice",
-
[ExtensionTag.Privacy]: "Privacy",
-
[ExtensionTag.Profiles]: "Profiles",
-
[ExtensionTag.QualityOfLife]: "Quality of Life",
-
[ExtensionTag.Library]: "Library"
-
};
-
-
export default (require: typeof WebpackRequire) => {
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
-
const CommonComponents = require("common_components");
-
const UserInfoClasses = spacepack.findByCode(
-
"infoScroller",
-
"userInfoSection",
-
"userInfoSectionHeader"
-
)[0].exports;
-
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("../webpackModules/stores");
-
-
function InfoSection({
-
title,
-
children
-
}: {
-
title: string;
-
children: React.ReactNode;
-
}) {
-
return (
-
<div
-
style={{
-
marginRight: "1em"
-
}}
-
>
-
<CommonComponents.Text
-
variant="eyebrow"
-
className={UserInfoClasses.userInfoSectionHeader}
-
>
-
{title}
-
</CommonComponents.Text>
-
-
<CommonComponents.Text variant="text-sm/normal">
-
{children}
-
</CommonComponents.Text>
-
</div>
-
);
-
}
-
-
function Badge({
-
color,
-
children
-
}: {
-
color: string;
-
children: React.ReactNode;
-
}) {
-
return (
-
<span
-
style={{
-
borderRadius: ".1875rem",
-
padding: "0 0.275rem",
-
marginRight: "0.4em",
-
backgroundColor: color,
-
color: "#fff"
-
}}
-
>
-
{children}
-
</span>
-
);
-
}
-
-
function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) {
-
const authors = ext.manifest?.meta?.authors;
-
const tags = ext.manifest?.meta?.tags;
-
-
const dependencies: Dependency[] = [];
-
if (ext.manifest.dependencies != null) {
-
dependencies.push(
-
...ext.manifest.dependencies.map((dep) => ({
-
id: dep,
-
type: DependencyType.Dependency
-
}))
-
);
-
}
-
-
if (ext.manifest.suggested != null) {
-
dependencies.push(
-
...ext.manifest.suggested.map((dep) => ({
-
id: dep,
-
type: DependencyType.Optional
-
}))
-
);
-
}
-
-
if (ext.manifest.incompatible != null) {
-
dependencies.push(
-
...ext.manifest.incompatible.map((dep) => ({
-
id: dep,
-
type: DependencyType.Incompatible
-
}))
-
);
-
}
-
-
return (
-
<>
-
{authors != null && (
-
<InfoSection title="Authors">
-
{authors.map((author, i) => {
-
const comma = i !== authors.length - 1 ? ", " : "";
-
if (typeof author === "string") {
-
return (
-
<span key={i}>
-
{author}
-
{comma}
-
</span>
-
);
-
} else {
-
// TODO: resolve IDs
-
return (
-
<span key={i}>
-
{author.name}
-
{comma}
-
</span>
-
);
-
}
-
})}
-
</InfoSection>
-
)}
-
-
{tags != null && (
-
<InfoSection title="Tags">
-
{tags.map((tag, i) => {
-
const name = tagNames[tag];
-
-
return (
-
<Badge
-
key={i}
-
color={
-
tag === ExtensionTag.DangerZone
-
? "var(--red-400)"
-
: "var(--brand-500)"
-
}
-
>
-
{name}
-
</Badge>
-
);
-
})}
-
</InfoSection>
-
)}
-
-
{dependencies.length > 0 && (
-
<InfoSection title="Dependencies">
-
{dependencies.map((dep) => {
-
const colors = {
-
[DependencyType.Dependency]: "var(--brand-500)",
-
[DependencyType.Optional]: "var(--orange-400)",
-
[DependencyType.Incompatible]: "var(--red-400)"
-
};
-
const color = colors[dep.type];
-
const name = MoonbaseSettingsStore.getExtensionName(dep.id);
-
return (
-
<Badge color={color} key={dep.id}>
-
{name}
-
</Badge>
-
);
-
})}
-
</InfoSection>
-
)}
-
</>
-
);
-
}
-
-
return {
-
InfoSection,
-
ExtensionInfo
-
};
-
};
-369
packages/core-extensions/src/moonbase/ui/settings.tsx
···
-
import {
-
ExtensionSettingType,
-
ExtensionSettingsManifest,
-
MultiSelectSettingType,
-
NumberSettingType,
-
SelectSettingType
-
} from "@moonlight-mod/types/config";
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
-
import { CircleXIconSVG, MoonbaseExtension } from "../types";
-
-
type SettingsProps = {
-
ext: MoonbaseExtension;
-
name: string;
-
setting: ExtensionSettingsManifest;
-
};
-
-
type SettingsComponent = React.ComponentType<SettingsProps>;
-
-
export default (require: typeof WebpackRequire) => {
-
const React = require("common_react");
-
const CommonComponents = require("common_components");
-
const Flux = require("common_flux");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("../webpackModules/stores");
-
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
-
-
function useConfigEntry<T>(id: string, name: string) {
-
return Flux.useStateFromStores(
-
[MoonbaseSettingsStore],
-
() => {
-
return {
-
value: MoonbaseSettingsStore.getExtensionConfig<T>(id, name),
-
displayName: MoonbaseSettingsStore.getExtensionConfigName(id, name),
-
description: MoonbaseSettingsStore.getExtensionConfigDescription(
-
id,
-
name
-
)
-
};
-
},
-
[id, name]
-
);
-
}
-
-
function Boolean({ ext, name, setting }: SettingsProps) {
-
const { FormSwitch } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<boolean>(
-
ext.id,
-
name
-
);
-
-
return (
-
<FormSwitch
-
value={value ?? false}
-
hideBorder={true}
-
onChange={(value: boolean) => {
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
-
}}
-
note={description}
-
className={`${Margins.marginReset} ${Margins.marginTop20}`}
-
>
-
{displayName}
-
</FormSwitch>
-
);
-
}
-
-
function Number({ ext, name, setting }: SettingsProps) {
-
const { FormItem, FormText, Slider } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<number>(
-
ext.id,
-
name
-
);
-
-
const castedSetting = setting as NumberSettingType;
-
const min = castedSetting.min ?? 0;
-
const max = castedSetting.max ?? 100;
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && <FormText>{description}</FormText>}
-
<Slider
-
initialValue={value ?? 0}
-
minValue={castedSetting.min ?? 0}
-
maxValue={castedSetting.max ?? 100}
-
onValueChange={(value: number) => {
-
const rounded = Math.max(min, Math.min(max, Math.round(value)));
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
-
}}
-
/>
-
</FormItem>
-
);
-
}
-
-
function String({ ext, name, setting }: SettingsProps) {
-
const { FormItem, FormText, TextInput } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<string>(
-
ext.id,
-
name
-
);
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
-
)}
-
<TextInput
-
value={value ?? ""}
-
onChange={(value: string) => {
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
-
}}
-
/>
-
</FormItem>
-
);
-
}
-
-
function Select({ ext, name, setting }: SettingsProps) {
-
const { FormItem, FormText, SingleSelect } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<string>(
-
ext.id,
-
name
-
);
-
-
const castedSetting = setting as SelectSettingType;
-
const options = castedSetting.options;
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
-
)}
-
<SingleSelect
-
autofocus={false}
-
clearable={false}
-
value={value ?? ""}
-
options={options.map((o) => ({ value: o, label: o }))}
-
onChange={(value: string) => {
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
-
}}
-
/>
-
</FormItem>
-
);
-
}
-
-
function MultiSelect({ ext, name, setting }: SettingsProps) {
-
const { FormItem, FormText, Select, useVariableSelect, multiSelect } =
-
CommonComponents;
-
const { value, displayName, description } = useConfigEntry<
-
string | string[]
-
>(ext.id, name);
-
-
const castedSetting = setting as MultiSelectSettingType;
-
const options = castedSetting.options;
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
-
)}
-
<Select
-
autofocus={false}
-
clearable={false}
-
closeOnSelect={false}
-
options={options.map((o) => ({ value: o, label: o }))}
-
{...useVariableSelect({
-
onSelectInteraction: multiSelect,
-
value: new Set(Array.isArray(value) ? value : [value]),
-
onChange: (value: string) => {
-
MoonbaseSettingsStore.setExtensionConfig(
-
ext.id,
-
name,
-
Array.from(value)
-
);
-
}
-
})}
-
/>
-
</FormItem>
-
);
-
}
-
-
const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0]
-
.exports;
-
const CircleXIcon = spacepack.findByCode(CircleXIconSVG)[0].exports.default;
-
function RemoveEntryButton({ onClick }: { onClick: () => void }) {
-
const { Tooltip, Clickable } = CommonComponents;
-
return (
-
<div className={RemoveButtonClasses.removeButtonContainer}>
-
<Tooltip text="Remove entry" position="top">
-
{(props: any) => (
-
<Clickable
-
{...props}
-
className={RemoveButtonClasses.removeButton}
-
onClick={onClick}
-
>
-
<CircleXIcon width={16} height={16} />
-
</Clickable>
-
)}
-
</Tooltip>
-
</div>
-
);
-
}
-
-
function List({ ext, name, setting }: SettingsProps) {
-
const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<string[]>(
-
ext.id,
-
name
-
);
-
-
const entries = value ?? [];
-
const updateConfig = () =>
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries);
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom4}>{description}</FormText>
-
)}
-
<Flex direction={Flex.Direction.VERTICAL}>
-
{entries.map((val, i) => (
-
// FIXME: stylesheets
-
<div
-
key={i}
-
style={{
-
display: "grid",
-
height: "32px",
-
gap: "8px",
-
gridTemplateColumns: "1fr 32px",
-
alignItems: "center"
-
}}
-
>
-
<TextInput
-
size={TextInput.Sizes.MINI}
-
value={val}
-
onChange={(newVal: string) => {
-
entries[i] = newVal;
-
updateConfig();
-
}}
-
/>
-
<RemoveEntryButton
-
onClick={() => {
-
entries.splice(i, 1);
-
updateConfig();
-
}}
-
/>
-
</div>
-
))}
-
-
<Button
-
look={Button.Looks.FILLED}
-
color={Button.Colors.GREEN}
-
size={Button.Sizes.SMALL}
-
className={Margins.marginTop8}
-
onClick={() => {
-
entries.push("");
-
updateConfig();
-
}}
-
>
-
Add new entry
-
</Button>
-
</Flex>
-
</FormItem>
-
);
-
}
-
-
function Dictionary({ ext, name, setting }: SettingsProps) {
-
const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<
-
Record<string, string>
-
>(ext.id, name);
-
-
const entries = Object.entries(value ?? {});
-
const updateConfig = () =>
-
MoonbaseSettingsStore.setExtensionConfig(
-
ext.id,
-
name,
-
Object.fromEntries(entries)
-
);
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom4}>{description}</FormText>
-
)}
-
<Flex direction={Flex.Direction.VERTICAL}>
-
{entries.map(([key, val], i) => (
-
// FIXME: stylesheets
-
<div
-
key={i}
-
style={{
-
display: "grid",
-
height: "32px",
-
gap: "8px",
-
gridTemplateColumns: "1fr 1fr 32px",
-
alignItems: "center"
-
}}
-
>
-
<TextInput
-
size={TextInput.Sizes.MINI}
-
value={key}
-
onChange={(newKey: string) => {
-
entries[i][0] = newKey;
-
updateConfig();
-
}}
-
/>
-
<TextInput
-
size={TextInput.Sizes.MINI}
-
value={val}
-
onChange={(newValue: string) => {
-
entries[i][1] = newValue;
-
updateConfig();
-
}}
-
/>
-
<RemoveEntryButton
-
onClick={() => {
-
entries.splice(i, 1);
-
updateConfig();
-
}}
-
/>
-
</div>
-
))}
-
-
<Button
-
look={Button.Looks.FILLED}
-
color={Button.Colors.GREEN}
-
size={Button.Sizes.SMALL}
-
className={Margins.marginTop8}
-
onClick={() => {
-
entries.push([`entry-${entries.length}`, ""]);
-
updateConfig();
-
}}
-
>
-
Add new entry
-
</Button>
-
</Flex>
-
</FormItem>
-
);
-
}
-
-
function Setting({ ext, name, setting }: SettingsProps) {
-
const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = {
-
[ExtensionSettingType.Boolean]: Boolean,
-
[ExtensionSettingType.Number]: Number,
-
[ExtensionSettingType.String]: String,
-
[ExtensionSettingType.Select]: Select,
-
[ExtensionSettingType.MultiSelect]: MultiSelect,
-
[ExtensionSettingType.List]: List,
-
[ExtensionSettingType.Dictionary]: Dictionary
-
};
-
const element = elements[setting.type];
-
if (element == null) return <></>;
-
return React.createElement(element, { ext, name, setting });
-
}
-
-
return function Settings({ ext }: { ext: MoonbaseExtension }) {
-
const { Flex } = CommonComponents;
-
return (
-
<Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}>
-
<style>
-
{".moonbase-settings > :nth-child(2) { margin-top: 0px; }"}
-
</style>
-
{Object.entries(ext.manifest.settings!).map(([name, setting]) => (
-
<Setting ext={ext} key={name} name={name} setting={setting} />
-
))}
-
</Flex>
-
);
-
};
-
};
+36
packages/core-extensions/src/moonbase/webpackModules/ThemeDarkIcon.tsx
···
+
// RIP to ThemeDarkIcon ????-2025
+
// <Cynthia> Failed to remap "ThemeDarkIcon" in "discord/components/common/index"
+
// <NotNite> bro are you fucking kidding me
+
// <NotNite> that's literally the icon we use for the update banner
+
+
import React from "@moonlight-mod/wp/react";
+
import icons from "@moonlight-mod/wp/common_icons";
+
import type { IconProps } from "@moonlight-mod/types/coreExtensions/common";
+
+
export default function ThemeDarkIcon(props?: IconProps) {
+
const parsed = icons.parseProps(props);
+
+
return (
+
<svg
+
aria-hidden="true"
+
role="img"
+
xmlns="http://www.w3.org/2000/svg"
+
width={parsed.width}
+
height={parsed.height}
+
fill="none"
+
viewBox="0 0 24 24"
+
>
+
<path
+
fill={parsed.fill}
+
className={parsed.className}
+
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"
+
/>
+
+
<path
+
fill={parsed.fill}
+
className={parsed.className}
+
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"
+
/>
+
</svg>
+
);
+
}
+263
packages/core-extensions/src/moonbase/webpackModules/crashScreen.tsx
···
+
import React from "@moonlight-mod/wp/react";
+
import { Button, TabBar } from "@moonlight-mod/wp/discord/components/common/index";
+
import { useStateFromStores, useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import { RepositoryManifest, UpdateState } from "../types";
+
import { ConfigExtension, DetectedExtension } from "@moonlight-mod/types";
+
import DiscoveryClasses from "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css";
+
+
const MODULE_REGEX = /Webpack-Module\/(\d+)\/(\d+)/g;
+
+
const logger = moonlight.getLogger("moonbase/crashScreen");
+
+
type ErrorState = {
+
error: Error;
+
info: {
+
componentStack: string;
+
};
+
__moonlight_update?: UpdateState;
+
};
+
+
type WrapperProps = {
+
action: React.ReactNode;
+
state: ErrorState;
+
};
+
+
type UpdateCardProps = {
+
id: number;
+
ext: {
+
version: string;
+
download: string;
+
updateManifest: RepositoryManifest;
+
};
+
};
+
+
const updateStrings: Record<UpdateState, string> = {
+
[UpdateState.Ready]: "A new version of moonlight is available.",
+
[UpdateState.Working]: "Updating moonlight...",
+
[UpdateState.Installed]: "Updated moonlight. Click Reload to apply changes.",
+
[UpdateState.Failed]: "Failed to update moonlight. Please use the installer."
+
};
+
const buttonStrings: Record<UpdateState, string> = {
+
[UpdateState.Ready]: "Update moonlight",
+
[UpdateState.Working]: "Updating moonlight...",
+
[UpdateState.Installed]: "",
+
[UpdateState.Failed]: "Update failed"
+
};
+
const extensionButtonStrings: Record<UpdateState, string> = {
+
[UpdateState.Ready]: "Update",
+
[UpdateState.Working]: "Updating...",
+
[UpdateState.Installed]: "Updated",
+
[UpdateState.Failed]: "Update failed"
+
};
+
+
function ExtensionUpdateCard({ id, ext }: UpdateCardProps) {
+
const [state, setState] = React.useState(UpdateState.Ready);
+
const installed = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.getExtension(id), [id]);
+
+
return (
+
<div className="moonbase-crash-extensionCard">
+
<div className="moonbase-crash-extensionCard-meta">
+
<div className="moonbase-crash-extensionCard-title">
+
{ext.updateManifest.meta?.name ?? ext.updateManifest.id}
+
</div>
+
<div className="moonbase-crash-extensionCard-version">{`v${installed?.manifest?.version ?? "???"} -> v${
+
ext.version
+
}`}</div>
+
</div>
+
<div className="moonbase-crash-extensionCard-button">
+
<Button
+
color={Button.Colors.GREEN}
+
disabled={state !== UpdateState.Ready}
+
onClick={() => {
+
setState(UpdateState.Working);
+
MoonbaseSettingsStore.installExtension(id)
+
.then(() => setState(UpdateState.Installed))
+
.catch(() => setState(UpdateState.Failed));
+
}}
+
>
+
{extensionButtonStrings[state]}
+
</Button>
+
</div>
+
</div>
+
);
+
}
+
+
function ExtensionDisableCard({ ext }: { ext: DetectedExtension }) {
+
async function disableWithDependents() {
+
const disable = new Set<string>();
+
disable.add(ext.id);
+
for (const [id, dependencies] of moonlightNode.processedExtensions.dependencyGraph) {
+
if (dependencies?.has(ext.id)) disable.add(id);
+
}
+
+
const config = structuredClone(moonlightNode.config);
+
for (const id in config.extensions) {
+
if (!disable.has(id)) continue;
+
if (typeof config.extensions[id] === "boolean") config.extensions[id] = false;
+
else (config.extensions[id] as ConfigExtension).enabled = false;
+
}
+
+
let msg = `Are you sure you want to disable "${ext.manifest.meta?.name ?? ext.id}"`;
+
if (disable.size > 1) {
+
msg += ` and its ${disable.size - 1} dependent${disable.size - 1 === 1 ? "" : "s"}`;
+
}
+
msg += "?";
+
+
if (confirm(msg)) {
+
await moonlightNode.writeConfig(config);
+
window.location.reload();
+
}
+
}
+
+
return (
+
<div className="moonbase-crash-extensionCard">
+
<div className="moonbase-crash-extensionCard-meta">
+
<div className="moonbase-crash-extensionCard-title">{ext.manifest.meta?.name ?? ext.id}</div>
+
<div className="moonbase-crash-extensionCard-version">{`v${ext.manifest.version ?? "???"}`}</div>
+
</div>
+
<div className="moonbase-crash-extensionCard-button">
+
<Button color={Button.Colors.RED} onClick={disableWithDependents}>
+
Disable
+
</Button>
+
</div>
+
</div>
+
);
+
}
+
+
export function wrapAction({ action, state }: WrapperProps) {
+
const [tab, setTab] = React.useState("crash");
+
+
const { updates, updateCount } = useStateFromStoresObject([MoonbaseSettingsStore], () => {
+
const { updates } = MoonbaseSettingsStore;
+
return {
+
updates: Object.entries(updates),
+
updateCount: Object.keys(updates).length
+
};
+
});
+
+
const causes = React.useMemo(() => {
+
const causes = new Set<string>();
+
if (state.error.stack) {
+
for (const [, , id] of state.error.stack.matchAll(MODULE_REGEX))
+
for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
+
}
+
for (const [, , id] of state.info.componentStack.matchAll(MODULE_REGEX))
+
for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
+
+
for (const [path, id] of Object.entries(moonlight.moonmap.modules)) {
+
const MAPPING_REGEX = new RegExp(
+
// @ts-expect-error Only Firefox has RegExp.escape
+
`(${RegExp.escape ? RegExp.escape(path) : path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`,
+
"g"
+
);
+
+
if (state.error.stack) {
+
for (const match of state.error.stack.matchAll(MAPPING_REGEX))
+
if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
+
}
+
for (const match of state.info.componentStack.matchAll(MAPPING_REGEX))
+
if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
+
}
+
+
return [...causes];
+
}, []);
+
+
return (
+
<div className="moonbase-crash-wrapper">
+
{action}
+
<TabBar
+
className={`${DiscoveryClasses.tabBar} moonbase-crash-tabs`}
+
type="top"
+
selectedItem={tab}
+
onItemSelect={(v) => setTab(v)}
+
>
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id="crash">
+
Crash details
+
</TabBar.Item>
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id="extensions" disabled={updateCount === 0}>
+
{`Extension updates (${updateCount})`}
+
</TabBar.Item>
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id="causes" disabled={causes.length === 0}>
+
{`Possible causes (${causes.length})`}
+
</TabBar.Item>
+
</TabBar>
+
{tab === "crash" ? (
+
<div className="moonbase-crash-details-wrapper">
+
<pre className="moonbase-crash-details">
+
<code>
+
{state.error.stack}
+
{"\n\nComponent stack:"}
+
{state.info.componentStack}
+
</code>
+
</pre>
+
</div>
+
) : null}
+
{tab === "extensions" ? (
+
<div className="moonbase-crash-extensions">
+
{updates.map(([id, ext]) => (
+
<ExtensionUpdateCard id={Number(id)} ext={ext} />
+
))}
+
</div>
+
) : null}
+
{tab === "causes" ? (
+
<div className="moonbase-crash-extensions">
+
{causes
+
.map((ext) => moonlightNode.extensions.find((e) => e.id === ext)!)
+
.map((ext) => (
+
<ExtensionDisableCard ext={ext} />
+
))}
+
</div>
+
) : null}
+
</div>
+
);
+
}
+
+
export function UpdateText({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) {
+
if (!state.__moonlight_update) {
+
setState({
+
...state,
+
__moonlight_update: UpdateState.Ready
+
});
+
}
+
const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion);
+
+
return newVersion == null ? null : (
+
<p>{state.__moonlight_update !== undefined ? updateStrings[state.__moonlight_update] : ""}</p>
+
);
+
}
+
+
export function UpdateButton({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) {
+
const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion);
+
return newVersion == null ||
+
state.__moonlight_update === UpdateState.Installed ||
+
state.__moonlight_update === undefined ? null : (
+
<Button
+
size={Button.Sizes.LARGE}
+
disabled={state.__moonlight_update !== UpdateState.Ready}
+
onClick={() => {
+
setState({
+
...state,
+
__moonlight_update: UpdateState.Working
+
});
+
+
MoonbaseSettingsStore.updateMoonlight()
+
.then(() => {
+
setState({
+
...state,
+
__moonlight_update: UpdateState.Installed
+
});
+
})
+
.catch((e) => {
+
logger.error(e);
+
setState({
+
...state,
+
__moonlight_update: UpdateState.Failed
+
});
+
});
+
}}
+
>
+
{state.__moonlight_update !== undefined ? buttonStrings[state.__moonlight_update] : ""}
+
</Button>
+
);
+
}
+10
packages/core-extensions/src/moonbase/webpackModules/moonbase.ts
···
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import type { Moonbase } from "@moonlight-mod/types/coreExtensions/moonbase";
+
+
export const moonbase: Moonbase = {
+
registerConfigComponent(ext, option, component) {
+
MoonbaseSettingsStore.registerConfigComponent(ext, option, component);
+
}
+
};
+
+
export default moonbase;
+100
packages/core-extensions/src/moonbase/webpackModules/settings.tsx
···
+
import settings from "@moonlight-mod/wp/settings_settings";
+
import React from "@moonlight-mod/wp/react";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import { Moonbase, pages, RestartAdviceMessage, Update } from "@moonlight-mod/wp/moonbase_ui";
+
import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators";
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import { Text, Breadcrumbs } from "@moonlight-mod/wp/discord/components/common/index";
+
import { MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu";
+
+
const notice = {
+
stores: [MoonbaseSettingsStore],
+
element: () => {
+
// Require it here because lazy loading SUX
+
const SettingsNotice = spacepack.require("discord/components/common/SettingsNotice").default;
+
return (
+
<SettingsNotice
+
submitting={MoonbaseSettingsStore.submitting}
+
onReset={() => {
+
MoonbaseSettingsStore.reset();
+
}}
+
onSave={async () => {
+
await MoonbaseSettingsStore.writeConfig();
+
}}
+
/>
+
);
+
}
+
};
+
+
const oldLocation = MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "oldLocation", false);
+
const position = oldLocation ? -2 : -9999;
+
+
function addSection(id: string, name: string, element: React.FunctionComponent) {
+
settings.addSection(`moonbase-${id}`, name, element, null, position, notice);
+
}
+
+
// FIXME: move to component types
+
type Breadcrumb = {
+
id: string;
+
label: string;
+
};
+
+
function renderBreadcrumb(crumb: Breadcrumb, last: boolean) {
+
return (
+
<Text variant="heading-lg/semibold" tag="h2" color={last ? "header-primary" : "header-secondary"}>
+
{crumb.label}
+
</Text>
+
);
+
}
+
+
if (!oldLocation) {
+
settings.addDivider(position);
+
}
+
+
if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "sections", false)) {
+
if (oldLocation) settings.addHeader("Moonbase", position);
+
+
const _pages = oldLocation ? pages : pages.reverse();
+
for (const page of _pages) {
+
addSection(page.id, page.name, () => {
+
const breadcrumbs = [
+
{ id: "moonbase", label: "Moonbase" },
+
{ id: page.id, label: page.name }
+
];
+
return (
+
<>
+
<Breadcrumbs
+
className={Margins.marginBottom20}
+
renderCustomBreadcrumb={renderBreadcrumb}
+
breadcrumbs={breadcrumbs}
+
activeId={page.id}
+
>
+
{page.name}
+
</Breadcrumbs>
+
+
<RestartAdviceMessage />
+
<Update />
+
+
<page.element />
+
</>
+
);
+
});
+
}
+
+
if (!oldLocation) settings.addHeader("Moonbase", position);
+
} else {
+
settings.addSection("moonbase", "Moonbase", Moonbase, null, position, notice);
+
+
settings.addSectionMenuItems(
+
"moonbase",
+
...pages.map((page, i) => (
+
<MenuItem
+
key={page.id}
+
id={`moonbase-${page.id}`}
+
label={page.name}
+
action={() => UserSettingsModalActionCreators.open("moonbase", i.toString())}
+
/>
+
))
+
);
+
}
+422 -116
packages/core-extensions/src/moonbase/webpackModules/stores.ts
···
-
import { Config, ExtensionLoadSource } from "@moonlight-mod/types";
+
import { Config, ExtensionEnvironment, ExtensionLoadSource, ExtensionSettingsAdvice } from "@moonlight-mod/types";
import {
ExtensionState,
MoonbaseExtension,
MoonbaseNatives,
-
RepositoryManifest
+
RepositoryManifest,
+
RestartAdvice,
+
UpdateState
} from "../types";
-
import Flux from "@moonlight-mod/wp/common_flux";
-
import Dispatcher from "@moonlight-mod/wp/common_fluxDispatcher";
+
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
+
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
+
import getNatives from "../native";
+
import { mainRepo } from "@moonlight-mod/types/constants";
+
import { checkExtensionCompat, ExtensionCompat } from "@moonlight-mod/core/extension/loader";
+
import { CustomComponent } from "@moonlight-mod/types/coreExtensions/moonbase";
+
import { NodeEventType } from "@moonlight-mod/types/core/event";
+
import { getConfigOption, setConfigOption } from "@moonlight-mod/core/util/config";
+
import diff from "microdiff";
-
const natives: MoonbaseNatives = moonlight.getNatives("moonbase");
const logger = moonlight.getLogger("moonbase");
-
class MoonbaseSettingsStore extends Flux.Store<any> {
-
private origConfig: Config;
+
let natives: MoonbaseNatives = moonlight.getNatives("moonbase");
+
if (moonlightNode.isBrowser) natives = getNatives();
+
+
class MoonbaseSettingsStore extends Store<any> {
+
private initialConfig: Config;
+
private savedConfig: Config;
private config: Config;
+
private extensionIndex: number;
+
private configComponents: Record<string, Record<string, CustomComponent>> = {};
modified: boolean;
submitting: boolean;
installing: boolean;
-
extensions: { [id: string]: MoonbaseExtension };
-
updates: { [id: string]: { version: string; download: string } };
+
#updateState = UpdateState.Ready;
+
get updateState() {
+
return this.#updateState;
+
}
+
newVersion: string | null;
+
shouldShowNotice: boolean;
+
+
restartAdvice = RestartAdvice.NotNeeded;
+
+
extensions: { [id: number]: MoonbaseExtension };
+
updates: {
+
[id: number]: {
+
version: string;
+
download: string;
+
updateManifest: RepositoryManifest;
+
};
+
};
constructor() {
super(Dispatcher);
-
// Fucking Electron making it immutable
-
this.origConfig = moonlightNode.config;
-
this.config = JSON.parse(JSON.stringify(this.origConfig));
+
this.initialConfig = moonlightNode.config;
+
this.savedConfig = moonlightNode.config;
+
this.config = this.clone(this.savedConfig);
+
this.extensionIndex = 0;
this.modified = false;
this.submitting = false;
this.installing = false;
+
this.newVersion = null;
+
this.shouldShowNotice = false;
+
this.extensions = {};
this.updates = {};
for (const ext of moonlightNode.extensions) {
-
const existingExtension = this.extensions[ext.id];
-
if (existingExtension != null) continue;
-
-
this.extensions[ext.id] = {
+
const uniqueId = this.extensionIndex++;
+
this.extensions[uniqueId] = {
...ext,
-
state: moonlight.enabledExtensions.has(ext.id)
-
? ExtensionState.Enabled
-
: ExtensionState.Disabled
+
uniqueId,
+
state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled : ExtensionState.Disabled,
+
compat: checkExtensionCompat(ext.manifest),
+
hasUpdate: false
};
}
-
natives.fetchRepositories(this.config.repositories).then((ret) => {
-
for (const [repo, exts] of Object.entries(ret)) {
-
try {
-
for (const ext of exts) {
-
try {
-
const existingExtension = this.extensions[ext.id];
-
if (existingExtension !== undefined) {
-
if (this.hasUpdate(repo, ext, existingExtension)) {
-
this.updates[ext.id] = {
-
version: ext.version!,
-
download: ext.download
-
};
-
}
-
continue;
-
}
+
// This is async but we're calling it without
+
this.checkUpdates();
+
+
// Update our state if another extension edited the config programatically
+
moonlightNode.events.addEventListener(NodeEventType.ConfigSaved, (config) => {
+
if (!this.submitting) {
+
this.config = this.clone(config);
+
// NOTE: This is also async but we're calling it without
+
this.processConfigChanged();
+
}
+
});
+
}
+
+
async checkUpdates() {
+
await Promise.all([this.checkExtensionUpdates(), this.checkMoonlightUpdates()]);
+
this.shouldShowNotice = this.newVersion != null || Object.keys(this.updates).length > 0;
+
this.emitChange();
+
}
+
+
private async checkExtensionUpdates() {
+
const repositories = await natives!.fetchRepositories(this.savedConfig.repositories);
+
+
// Reset update state
+
for (const id in this.extensions) {
+
const ext = this.extensions[id];
+
ext.hasUpdate = false;
+
ext.changelog = undefined;
+
}
+
this.updates = {};
+
+
for (const [repo, exts] of Object.entries(repositories)) {
+
for (const ext of exts) {
+
const uniqueId = this.extensionIndex++;
+
const extensionData = {
+
id: ext.id,
+
uniqueId,
+
manifest: ext,
+
source: { type: ExtensionLoadSource.Normal, url: repo },
+
state: ExtensionState.NotDownloaded,
+
compat: ExtensionCompat.Compatible,
+
hasUpdate: false
+
};
+
+
// Don't present incompatible updates
+
if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue;
+
+
const existing = this.getExisting(extensionData);
+
if (existing != null) {
+
// Make sure the download URL is properly updated
+
existing.manifest = {
+
...existing.manifest,
+
download: ext.download
+
};
-
this.extensions[ext.id] = {
-
id: ext.id,
-
manifest: ext,
-
source: { type: ExtensionLoadSource.Normal, url: repo },
-
state: ExtensionState.NotDownloaded
-
};
-
} catch (e) {
-
logger.error(`Error processing extension ${ext.id}`, e);
-
}
+
if (this.hasUpdate(extensionData)) {
+
this.updates[existing.uniqueId] = {
+
version: ext.version!,
+
download: ext.download,
+
updateManifest: ext
+
};
+
existing.hasUpdate = true;
+
existing.changelog = ext.meta?.changelog;
}
-
} catch (e) {
-
logger.error(`Error processing repository ${repo}`, e);
+
} else {
+
this.extensions[uniqueId] = extensionData;
}
}
+
}
+
}
-
this.emitChange();
-
});
+
private async checkMoonlightUpdates() {
+
this.newVersion = this.getExtensionConfigRaw("moonbase", "updateChecking", true)
+
? await natives!.checkForMoonlightUpdate()
+
: null;
+
}
+
+
private getExisting(ext: MoonbaseExtension) {
+
return Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url);
}
-
// this logic sucks so bad lol
-
private hasUpdate(
-
repo: string,
-
repoExt: RepositoryManifest,
-
existing: MoonbaseExtension
-
) {
-
return (
-
existing.source.type === ExtensionLoadSource.Normal &&
-
existing.source.url != null &&
-
existing.source.url === repo &&
-
repoExt.version != null &&
-
existing.manifest.version !== repoExt.version
-
);
+
private hasUpdate(ext: MoonbaseExtension) {
+
const existing = Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url);
+
if (existing == null) return false;
+
+
return existing.manifest.version !== ext.manifest.version && existing.state !== ExtensionState.NotDownloaded;
}
// Jank
private isModified() {
-
const orig = JSON.stringify(this.origConfig);
+
const orig = JSON.stringify(this.savedConfig);
const curr = JSON.stringify(this.config);
return orig !== curr;
}
···
return this.submitting || this.installing;
}
+
// Required for the settings store contract
showNotice() {
return this.modified;
}
-
getExtension(id: string) {
-
return this.extensions[id];
+
getExtension(uniqueId: number) {
+
return this.extensions[uniqueId];
}
-
getExtensionName(id: string) {
-
return Object.prototype.hasOwnProperty.call(this.extensions, id)
-
? this.extensions[id].manifest.meta?.name ?? id
-
: id;
+
getExtensionUniqueId(id: string) {
+
return Object.values(this.extensions).find((ext) => ext.id === id)?.uniqueId;
}
-
getExtensionUpdate(id: string) {
-
return Object.prototype.hasOwnProperty.call(this.updates, id)
-
? this.updates[id]
-
: null;
+
getExtensionConflicting(uniqueId: number) {
+
const ext = this.getExtension(uniqueId);
+
if (ext.state !== ExtensionState.NotDownloaded) return false;
+
return Object.values(this.extensions).some(
+
(e) => e.id === ext.id && e.uniqueId !== uniqueId && e.state !== ExtensionState.NotDownloaded
+
);
+
}
+
+
getExtensionName(uniqueId: number) {
+
const ext = this.getExtension(uniqueId);
+
return ext.manifest.meta?.name ?? ext.id;
}
-
getExtensionEnabled(id: string) {
-
const val = this.config.extensions[id];
+
getExtensionUpdate(uniqueId: number) {
+
return this.updates[uniqueId]?.version;
+
}
+
+
getExtensionEnabled(uniqueId: number) {
+
const ext = this.getExtension(uniqueId);
+
if (ext.state === ExtensionState.NotDownloaded) return false;
+
const val = this.config.extensions[ext.id];
if (val == null) return false;
return typeof val === "boolean" ? val : val.enabled;
}
-
getExtensionConfig<T>(id: string, key: string): T | undefined {
-
const defaultValue = this.extensions[id].manifest.settings?.[key]?.default;
+
getExtensionConfig<T>(uniqueId: number, key: string): T | undefined {
+
const ext = this.getExtension(uniqueId);
+
const settings = ext.settingsOverride ?? ext.manifest.settings;
+
return getConfigOption(ext.id, key, this.config, settings);
+
}
+
+
getExtensionConfigRaw<T>(id: string, key: string, defaultValue: T | undefined): T | undefined {
const cfg = this.config.extensions[id];
-
if (cfg == null || typeof cfg === "boolean") return defaultValue;
return cfg.config?.[key] ?? defaultValue;
}
-
getExtensionConfigName(id: string, key: string) {
-
return this.extensions[id].manifest.settings?.[key]?.displayName ?? key;
+
getExtensionConfigName(uniqueId: number, key: string) {
+
const ext = this.getExtension(uniqueId);
+
const settings = ext.settingsOverride ?? ext.manifest.settings;
+
return settings?.[key]?.displayName ?? key;
}
-
getExtensionConfigDescription(id: string, key: string) {
-
return this.extensions[id].manifest.settings?.[key]?.description;
+
getExtensionConfigDescription(uniqueId: number, key: string) {
+
const ext = this.getExtension(uniqueId);
+
const settings = ext.settingsOverride ?? ext.manifest.settings;
+
return settings?.[key]?.description;
}
setExtensionConfig(id: string, key: string, value: any) {
-
const oldConfig = this.config.extensions[id];
-
const newConfig =
-
typeof oldConfig === "boolean"
-
? {
-
enabled: oldConfig,
-
config: { [key]: value }
-
}
-
: {
-
...oldConfig,
-
config: { ...(oldConfig?.config ?? {}), [key]: value }
-
};
-
-
this.config.extensions[id] = newConfig;
+
setConfigOption(this.config, id, key, value);
this.modified = this.isModified();
this.emitChange();
}
-
setExtensionEnabled(id: string, enabled: boolean) {
-
let val = this.config.extensions[id];
+
setExtensionEnabled(uniqueId: number, enabled: boolean) {
+
const ext = this.getExtension(uniqueId);
+
let val = this.config.extensions[ext.id];
if (val == null) {
-
this.config.extensions[id] = { enabled };
+
this.config.extensions[ext.id] = enabled;
+
this.modified = this.isModified();
this.emitChange();
return;
}
···
val.enabled = enabled;
}
-
this.config.extensions[id] = val;
+
this.config.extensions[ext.id] = val;
this.modified = this.isModified();
this.emitChange();
}
-
async installExtension(id: string) {
-
const ext = this.getExtension(id);
+
dismissAllExtensionUpdates() {
+
for (const id in this.extensions) {
+
this.extensions[id].hasUpdate = false;
+
}
+
this.emitChange();
+
}
+
+
async updateAllExtensions() {
+
for (const id of Object.keys(this.updates)) {
+
try {
+
await this.installExtension(parseInt(id));
+
} catch (e) {
+
logger.error("Error bulk updating extension", id, e);
+
}
+
}
+
}
+
+
async installExtension(uniqueId: number) {
+
const ext = this.getExtension(uniqueId);
if (!("download" in ext.manifest)) {
throw new Error("Extension has no download URL");
}
this.installing = true;
try {
-
const url = this.updates[id]?.download ?? ext.manifest.download;
-
await natives.installExtension(ext.manifest, url, ext.source.url!);
+
const update = this.updates[uniqueId];
+
const url = update?.download ?? ext.manifest.download;
+
await natives!.installExtension(ext.manifest, url, ext.source.url!);
if (ext.state === ExtensionState.NotDownloaded) {
-
this.extensions[id].state = ExtensionState.Disabled;
+
this.extensions[uniqueId].state = ExtensionState.Disabled;
}
-
delete this.updates[id];
+
if (update != null) {
+
const existing = this.extensions[uniqueId];
+
existing.settingsOverride = update.updateManifest.settings;
+
existing.compat = checkExtensionCompat(update.updateManifest);
+
existing.manifest = update.updateManifest;
+
existing.changelog = update.updateManifest.meta?.changelog;
+
}
+
+
delete this.updates[uniqueId];
} catch (e) {
logger.error("Error installing extension:", e);
}
this.installing = false;
+
this.restartAdvice = this.#computeRestartAdvice();
this.emitChange();
}
-
async deleteExtension(id: string) {
-
const ext = this.getExtension(id);
+
private getRank(ext: MoonbaseExtension) {
+
if (ext.source.type === ExtensionLoadSource.Developer) return 3;
+
if (ext.source.type === ExtensionLoadSource.Core) return 2;
+
if (ext.source.url === mainRepo) return 1;
+
return 0;
+
}
+
+
async getDependencies(uniqueId: number) {
+
const ext = this.getExtension(uniqueId);
+
+
const missingDeps = [];
+
for (const dep of ext.manifest.dependencies ?? []) {
+
const anyInstalled = Object.values(this.extensions).some(
+
(e) => e.id === dep && e.state !== ExtensionState.NotDownloaded
+
);
+
if (!anyInstalled) missingDeps.push(dep);
+
}
+
+
if (missingDeps.length === 0) return null;
+
+
const deps: Record<string, MoonbaseExtension[]> = {};
+
for (const dep of missingDeps) {
+
const candidates = Object.values(this.extensions).filter((e) => e.id === dep);
+
+
deps[dep] = candidates.sort((a, b) => {
+
const aRank = this.getRank(a);
+
const bRank = this.getRank(b);
+
if (aRank === bRank) {
+
const repoIndex = this.savedConfig.repositories.indexOf(a.source.url!);
+
const otherRepoIndex = this.savedConfig.repositories.indexOf(b.source.url!);
+
return repoIndex - otherRepoIndex;
+
} else {
+
return bRank - aRank;
+
}
+
});
+
}
+
+
return deps;
+
}
+
+
async deleteExtension(uniqueId: number) {
+
const ext = this.getExtension(uniqueId);
if (ext == null) return;
this.installing = true;
try {
-
await natives.deleteExtension(ext.id);
-
this.extensions[id].state = ExtensionState.NotDownloaded;
+
await natives!.deleteExtension(ext.id);
+
this.extensions[uniqueId].state = ExtensionState.NotDownloaded;
} catch (e) {
logger.error("Error deleting extension:", e);
}
this.installing = false;
+
this.restartAdvice = this.#computeRestartAdvice();
this.emitChange();
}
-
writeConfig() {
-
this.submitting = true;
+
async updateMoonlight() {
+
this.#updateState = UpdateState.Working;
+
this.emitChange();
+
+
await natives
+
.updateMoonlight()
+
.then(() => (this.#updateState = UpdateState.Installed))
+
.catch((e) => {
+
logger.error(e);
+
this.#updateState = UpdateState.Failed;
+
});
+
+
this.emitChange();
+
}
+
+
getConfigOption<K extends keyof Config>(key: K): Config[K] {
+
return this.config[key];
+
}
+
+
setConfigOption<K extends keyof Config>(key: K, value: Config[K]) {
+
this.config[key] = value;
+
this.modified = this.isModified();
+
this.emitChange();
+
}
+
+
tryGetExtensionName(id: string) {
+
const uniqueId = this.getExtensionUniqueId(id);
+
return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id;
+
}
+
+
registerConfigComponent(ext: string, name: string, component: CustomComponent) {
+
if (!(ext in this.configComponents)) this.configComponents[ext] = {};
+
this.configComponents[ext][name] = component;
+
}
+
+
getExtensionConfigComponent(ext: string, name: string) {
+
return this.configComponents[ext]?.[name];
+
}
+
+
#computeRestartAdvice() {
+
// If moonlight update needs a restart, always hide advice.
+
if (this.#updateState === UpdateState.Installed) return RestartAdvice.NotNeeded;
+
+
const i = this.initialConfig; // Initial config, from startup
+
const n = this.config; // New config about to be saved
+
+
let returnedAdvice = RestartAdvice.NotNeeded;
+
const updateAdvice = (r: RestartAdvice) => (returnedAdvice < r ? (returnedAdvice = r) : returnedAdvice);
+
+
// Top-level keys, repositories is not needed here because Moonbase handles it.
+
if (i.patchAll !== n.patchAll) updateAdvice(RestartAdvice.ReloadNeeded);
+
if (i.loggerLevel !== n.loggerLevel) updateAdvice(RestartAdvice.ReloadNeeded);
+
if (diff(i.devSearchPaths ?? [], n.devSearchPaths ?? [], { cyclesFix: false }).length !== 0)
+
return updateAdvice(RestartAdvice.RestartNeeded);
+
+
// Extension specific logic
+
for (const id in n.extensions) {
+
// Installed extension (might not be detected yet)
+
const ext = Object.values(this.extensions).find((e) => e.id === id && e.state !== ExtensionState.NotDownloaded);
+
// Installed and detected extension
+
const detected = moonlightNode.extensions.find((e) => e.id === id);
+
+
// If it's not installed at all, we don't care
+
if (!ext) continue;
+
+
const initState = i.extensions[id];
+
const newState = n.extensions[id];
+
+
const newEnabled = typeof newState === "boolean" ? newState : newState.enabled;
+
// If it's enabled but not detected yet, restart.
+
if (newEnabled && !detected) {
+
return updateAdvice(RestartAdvice.RestartNeeded);
+
}
+
+
// Toggling extensions specifically wants to rely on the initial state,
+
// that's what was considered when loading extensions.
+
const initEnabled = initState && (typeof initState === "boolean" ? initState : initState.enabled);
+
if (initEnabled !== newEnabled || detected?.manifest.version !== ext.manifest.version) {
+
// If we have the extension locally, we confidently know if it has host/preload scripts.
+
// If not, we have to respect the environment specified in the manifest.
+
// If that is the default, we can't know what's needed.
+
+
if (detected?.scripts.hostPath || detected?.scripts.nodePath) {
+
return updateAdvice(RestartAdvice.RestartNeeded);
+
}
+
+
switch (ext.manifest.environment) {
+
case ExtensionEnvironment.Both:
+
case ExtensionEnvironment.Web:
+
updateAdvice(RestartAdvice.ReloadNeeded);
+
continue;
+
case ExtensionEnvironment.Desktop:
+
return updateAdvice(RestartAdvice.RestartNeeded);
+
default:
+
updateAdvice(RestartAdvice.ReloadNeeded);
+
continue;
+
}
+
}
+
const initConfig = typeof initState === "boolean" ? {} : { ...initState?.config };
+
const newConfig = typeof newState === "boolean" ? {} : { ...newState?.config };
+
+
const def = ext.manifest.settings;
+
if (!def) continue;
+
+
for (const key in def) {
+
const defaultValue = def[key].default;
+
+
initConfig[key] ??= defaultValue;
+
newConfig[key] ??= defaultValue;
+
}
+
+
const changedKeys = diff(initConfig, newConfig, { cyclesFix: false }).map((c) => c.path[0]);
+
for (const key in def) {
+
if (!changedKeys.includes(key)) continue;
+
+
const advice = def[key].advice;
+
switch (advice) {
+
case ExtensionSettingsAdvice.None:
+
updateAdvice(RestartAdvice.NotNeeded);
+
continue;
+
case ExtensionSettingsAdvice.Reload:
+
updateAdvice(RestartAdvice.ReloadNeeded);
+
continue;
+
case ExtensionSettingsAdvice.Restart:
+
updateAdvice(RestartAdvice.RestartNeeded);
+
continue;
+
default:
+
updateAdvice(RestartAdvice.ReloadSuggested);
+
}
+
}
+
}
+
+
return returnedAdvice;
+
}
+
+
async writeConfig() {
try {
-
moonlightNode.writeConfig(this.config);
-
// I love jank cloning
-
this.origConfig = JSON.parse(JSON.stringify(this.config));
-
} catch (e) {
-
logger.error("Error writing config", e);
+
this.submitting = true;
+
this.emitChange();
+
+
await moonlightNode.writeConfig(this.config);
+
await this.processConfigChanged();
+
} finally {
+
this.submitting = false;
+
this.emitChange();
}
+
}
-
this.submitting = false;
+
private async processConfigChanged() {
+
this.savedConfig = this.clone(this.config);
+
this.restartAdvice = this.#computeRestartAdvice();
this.modified = false;
+
+
const modifiedRepos = diff(this.savedConfig.repositories, this.config.repositories);
+
if (modifiedRepos.length !== 0) await this.checkUpdates();
+
this.emitChange();
}
reset() {
this.submitting = false;
this.modified = false;
-
this.config = JSON.parse(JSON.stringify(this.origConfig));
+
this.config = this.clone(this.savedConfig);
this.emitChange();
+
}
+
+
restartDiscord() {
+
if (moonlightNode.isBrowser) {
+
window.location.reload();
+
} else {
+
// @ts-expect-error TODO: DiscordNative
+
window.DiscordNative.app.relaunch();
+
}
+
}
+
+
// Required because electron likes to make it immutable sometimes.
+
// This sucks.
+
private clone<T>(obj: T): T {
+
return structuredClone(obj);
}
}
+47
packages/core-extensions/src/moonbase/webpackModules/ui/HelpMessage.tsx
···
+
import React from "@moonlight-mod/wp/react";
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
+
import { Text } from "@moonlight-mod/wp/discord/components/common/index";
+
import HelpMessageClasses from "@moonlight-mod/wp/discord/components/common/HelpMessage.css";
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
+
+
// reimpl of HelpMessage but with a custom icon
+
export default function HelpMessage({
+
className,
+
text,
+
icon,
+
children,
+
type = "info"
+
}: {
+
className?: string;
+
text: string;
+
icon: React.ComponentType<any>;
+
type?: "warning" | "positive" | "error" | "info";
+
children?: React.ReactNode;
+
}) {
+
return (
+
<div
+
className={`${Margins.marginBottom20} ${HelpMessageClasses[type]} ${HelpMessageClasses.container} moonbase-help-message ${className}`}
+
>
+
<Flex direction={Flex.Direction.HORIZONTAL}>
+
<div
+
className={HelpMessageClasses.iconDiv}
+
style={{
+
alignItems: "center"
+
}}
+
>
+
{React.createElement(icon, {
+
size: "sm",
+
color: "currentColor",
+
className: HelpMessageClasses.icon
+
})}
+
</div>
+
+
<Text variant="text-sm/medium" color="currentColor" className={HelpMessageClasses.text}>
+
{text}
+
</Text>
+
+
{children}
+
</Flex>
+
</div>
+
);
+
}
+43
packages/core-extensions/src/moonbase/webpackModules/ui/RestartAdvice.tsx
···
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import { Button, CircleWarningIcon } from "@moonlight-mod/wp/discord/components/common/index";
+
import React from "@moonlight-mod/wp/react";
+
import { RestartAdvice } from "../../types";
+
import HelpMessage from "./HelpMessage";
+
+
const strings: Record<RestartAdvice, string> = {
+
[RestartAdvice.NotNeeded]: "how did you even",
+
[RestartAdvice.ReloadSuggested]: "A reload might be needed to apply some of the changed options.",
+
[RestartAdvice.ReloadNeeded]: "A reload is needed to apply some of the changed options.",
+
[RestartAdvice.RestartNeeded]: "A restart is needed to apply some of the changed options."
+
};
+
+
const buttonStrings: Record<RestartAdvice, string> = {
+
[RestartAdvice.NotNeeded]: "huh?",
+
[RestartAdvice.ReloadSuggested]: "Reload",
+
[RestartAdvice.ReloadNeeded]: "Reload",
+
[RestartAdvice.RestartNeeded]: "Restart"
+
};
+
+
const actions: Record<RestartAdvice, () => void> = {
+
[RestartAdvice.NotNeeded]: () => {},
+
[RestartAdvice.ReloadSuggested]: () => window.location.reload(),
+
[RestartAdvice.ReloadNeeded]: () => window.location.reload(),
+
[RestartAdvice.RestartNeeded]: () => MoonbaseSettingsStore.restartDiscord()
+
};
+
+
export default function RestartAdviceMessage() {
+
const restartAdvice = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.restartAdvice);
+
+
if (restartAdvice === RestartAdvice.NotNeeded) return null;
+
+
return (
+
<div className="moonbase-help-message-sticky">
+
<HelpMessage text={strings[restartAdvice]} icon={CircleWarningIcon} type="warning">
+
<Button color={Button.Colors.YELLOW} size={Button.Sizes.TINY} onClick={actions[restartAdvice]}>
+
{buttonStrings[restartAdvice]}
+
</Button>
+
</HelpMessage>
+
</div>
+
);
+
}
+110
packages/core-extensions/src/moonbase/webpackModules/ui/about.tsx
···
+
import {
+
Text,
+
useThemeContext,
+
Button,
+
AngleBracketsIcon,
+
BookCheckIcon,
+
ClydeIcon
+
} from "@moonlight-mod/wp/discord/components/common/index";
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
+
import React from "@moonlight-mod/wp/react";
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
+
const wordmark = "https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/heads/main/img/wordmark.png";
+
const wordmarkLight =
+
"https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/heads/main/img/wordmark-light.png";
+
+
function parse(str: string) {
+
return MarkupUtils.parse(str, true, {
+
allowHeading: true,
+
allowLinks: true,
+
allowList: true
+
});
+
}
+
+
function Dev({ name, picture, link }: { name: string; picture: string; link: string }) {
+
return (
+
<Button onClick={() => window.open(link)} color={Button.Colors.PRIMARY} className="moonbase-dev">
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap">
+
<img src={picture} alt={name} className="moonbase-dev-avatar" />
+
+
<Text variant="text-md/semibold">{name}</Text>
+
</Flex>
+
</Button>
+
);
+
}
+
+
function IconButton({
+
text,
+
link,
+
icon,
+
openInClient
+
}: {
+
text: string;
+
link: string;
+
icon: React.FC<any>;
+
openInClient?: boolean;
+
}) {
+
return (
+
<Button
+
onClick={() => {
+
if (openInClient) {
+
try {
+
const { handleClick } = spacepack.require("discord/utils/MaskedLinkUtils");
+
handleClick({ href: link });
+
} catch {
+
window.open(link);
+
}
+
} else {
+
// Will open externally in the user's browser
+
window.open(link);
+
}
+
}}
+
>
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap">
+
{React.createElement(icon, {
+
size: "sm",
+
color: "currentColor"
+
})}
+
{text}
+
</Flex>
+
</Button>
+
);
+
}
+
+
export default function AboutPage() {
+
const darkTheme = useThemeContext()?.theme !== "light";
+
+
return (
+
<Flex direction={Flex.Direction.VERTICAL} align={Flex.Align.CENTER} className="moonbase-about-page">
+
<img src={darkTheme ? wordmarkLight : wordmark} alt="moonlight wordmark" className="moonbase-wordmark" />
+
+
<Text variant="heading-lg/medium">created by:</Text>
+
<div className="moonbase-devs">
+
<Dev name="Cynosphere" picture="https://github.com/Cynosphere.png" link="https://github.com/Cynosphere" />
+
<Dev name="NotNite" picture="https://github.com/NotNite.png" link="https://github.com/NotNite" />
+
<Dev name="adryd" picture="https://github.com/adryd325.png" link="https://github.com/adryd325" />
+
<Dev name="redstonekasi" picture="https://github.com/redstonekasi.png" link="https://github.com/redstonekasi" />
+
</div>
+
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap">
+
<IconButton text="View source" icon={AngleBracketsIcon} link="https://github.com/moonlight-mod/moonlight" />
+
<IconButton text="Open the docs" icon={BookCheckIcon} link="https://moonlight-mod.github.io/" />
+
<IconButton text="Join the server" icon={ClydeIcon} link="https://discord.gg/FdZBTFCP6F" openInClient={true} />
+
</Flex>
+
+
<Flex direction={Flex.Direction.VERTICAL} align={Flex.Align.START}>
+
<Text variant="text-sm/normal">
+
{parse(`moonlight \`${window.moonlight.version}\` on \`${window.moonlight.branch}\``)}
+
</Text>
+
+
<Text variant="text-sm/normal">
+
{parse(
+
"moonlight is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.html) (`LGPL-3.0-or-later`)."
+
)}
+
</Text>
+
</Flex>
+
</Flex>
+
);
+
}
+157
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
···
+
import { LogLevel } from "@moonlight-mod/types";
+
+
const logLevels = Object.values(LogLevel).filter((v) => typeof v === "string") as string[];
+
+
import React from "@moonlight-mod/wp/react";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import {
+
FormDivider,
+
FormItem,
+
FormText,
+
FormSwitch,
+
TextInput,
+
Button,
+
SingleSelect,
+
Tooltip,
+
Clickable
+
} from "@moonlight-mod/wp/discord/components/common/index";
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
+
import { CircleXIcon } from "@moonlight-mod/wp/discord/components/common/index";
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
+
import FormSwitchClasses from "@moonlight-mod/wp/discord/components/common/FormSwitch.css";
+
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
+
let GuildSettingsRoleEditClasses: any;
+
spacepack
+
.lazyLoad(
+
"renderArtisanalHack",
+
/\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"GuildSettings"/,
+
/webpackId:(\d+),name:"GuildSettings"/
+
)
+
.then(
+
() =>
+
(GuildSettingsRoleEditClasses = spacepack.require(
+
"discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"
+
))
+
);
+
+
function RemoveEntryButton({ onClick }: { onClick: () => void }) {
+
return (
+
<div className={GuildSettingsRoleEditClasses.removeButtonContainer}>
+
<Tooltip text="Remove entry" position="top">
+
{(props: any) => (
+
<Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}>
+
<CircleXIcon width={24} height={24} />
+
</Clickable>
+
)}
+
</Tooltip>
+
</div>
+
);
+
}
+
+
function ArrayFormItem({ config }: { config: "repositories" | "devSearchPaths" }) {
+
const items = MoonbaseSettingsStore.getConfigOption(config) ?? [];
+
return (
+
<Flex
+
style={{
+
gap: "20px"
+
}}
+
direction={Flex.Direction.VERTICAL}
+
>
+
{items.map((val, i) => (
+
<div
+
key={i}
+
style={{
+
display: "grid",
+
height: "32px",
+
gap: "8px",
+
gridTemplateColumns: "1fr 32px",
+
alignItems: "center"
+
}}
+
>
+
<TextInput
+
size={TextInput.Sizes.DEFAULT}
+
value={val}
+
onChange={(newVal: string) => {
+
items[i] = newVal;
+
MoonbaseSettingsStore.setConfigOption(config, items);
+
}}
+
/>
+
<RemoveEntryButton
+
onClick={() => {
+
items.splice(i, 1);
+
MoonbaseSettingsStore.setConfigOption(config, items);
+
}}
+
/>
+
</div>
+
))}
+
+
<Button
+
look={Button.Looks.FILLED}
+
color={Button.Colors.GREEN}
+
size={Button.Sizes.SMALL}
+
style={{
+
marginTop: "10px"
+
}}
+
onClick={() => {
+
items.push("");
+
MoonbaseSettingsStore.setConfigOption(config, items);
+
}}
+
>
+
Add new entry
+
</Button>
+
</Flex>
+
);
+
}
+
+
export default function ConfigPage() {
+
return (
+
<>
+
<FormSwitch
+
className={Margins.marginTop20}
+
value={MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "updateChecking", true) ?? true}
+
onChange={(value: boolean) => {
+
MoonbaseSettingsStore.setExtensionConfig("moonbase", "updateChecking", value);
+
}}
+
note="Checks for updates to moonlight"
+
>
+
Automatic update checking
+
</FormSwitch>
+
<FormItem title="Repositories">
+
<FormText className={Margins.marginBottom4}>A list of remote repositories to display extensions from</FormText>
+
<ArrayFormItem config="repositories" />
+
</FormItem>
+
<FormDivider className={FormSwitchClasses.dividerDefault} />
+
<FormItem title="Extension search paths" className={Margins.marginTop20}>
+
<FormText className={Margins.marginBottom4}>
+
A list of local directories to search for built extensions
+
</FormText>
+
<ArrayFormItem config="devSearchPaths" />
+
</FormItem>
+
<FormDivider className={FormSwitchClasses.dividerDefault} />
+
<FormSwitch
+
className={Margins.marginTop20}
+
value={MoonbaseSettingsStore.getConfigOption("patchAll") ?? false}
+
onChange={(value: boolean) => {
+
MoonbaseSettingsStore.setConfigOption("patchAll", value);
+
}}
+
note="Wraps every webpack module in a function, separating them in DevTools"
+
>
+
Patch all
+
</FormSwitch>
+
<FormItem title="Log level">
+
<SingleSelect
+
autofocus={false}
+
clearable={false}
+
value={MoonbaseSettingsStore.getConfigOption("loggerLevel")}
+
options={logLevels.map((o) => ({
+
value: o.toLowerCase(),
+
label: o[0] + o.slice(1).toLowerCase()
+
}))}
+
onChange={(v) => MoonbaseSettingsStore.setConfigOption("loggerLevel", v)}
+
/>
+
</FormItem>
+
</>
+
);
+
}
+335
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
···
+
import { ExtensionState } from "../../../types";
+
import { constants, ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types";
+
+
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
+
import {
+
ScienceIcon,
+
DownloadIcon,
+
TrashIcon,
+
AngleBracketsIcon,
+
Tooltip,
+
Card,
+
Text,
+
FormSwitch,
+
TabBar,
+
Button,
+
ChannelListIcon,
+
HeartIcon,
+
WindowTopOutlineIcon,
+
WarningIcon
+
} from "@moonlight-mod/wp/discord/components/common/index";
+
import React from "@moonlight-mod/wp/react";
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
+
import AppCardClasses from "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCard.css";
+
import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton";
+
import DiscoveryClasses from "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css";
+
import MarkupClasses from "@moonlight-mod/wp/discord/modules/messages/web/Markup.css";
+
import BuildOverrideClasses from "@moonlight-mod/wp/discord/modules/build_overrides/web/BuildOverride.css";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary";
+
import ExtensionInfo from "./info";
+
import Settings from "./settings";
+
import { doGenericExtensionPopup, doMissingExtensionPopup } from "./popup";
+
+
export enum ExtensionPage {
+
Info,
+
Description,
+
Changelog,
+
Settings
+
}
+
+
const COMPAT_TEXT_MAP: Record<ExtensionCompat, string> = {
+
[ExtensionCompat.Compatible]: "huh?",
+
[ExtensionCompat.InvalidApiLevel]: "Incompatible API level",
+
[ExtensionCompat.InvalidEnvironment]: "Incompatible platform"
+
};
+
const CONFLICTING_TEXT = "This extension is already installed from another source.";
+
+
function PanelLinkButton({ icon, tooltip, link }: { icon: React.ReactNode; tooltip: string; link: string }) {
+
return (
+
<PanelButton
+
icon={icon}
+
tooltipText={tooltip}
+
onClick={() => {
+
window.open(link);
+
}}
+
/>
+
);
+
}
+
+
export default function ExtensionCard({ uniqueId, selectTag }: { uniqueId: number; selectTag: (tag: string) => void }) {
+
const { ext, enabled, busy, update, conflicting } = useStateFromStores([MoonbaseSettingsStore], () => {
+
return {
+
ext: MoonbaseSettingsStore.getExtension(uniqueId),
+
enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId),
+
busy: MoonbaseSettingsStore.busy,
+
update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId),
+
conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId)
+
};
+
});
+
+
const [tab, setTab] = React.useState(
+
update != null && ext?.changelog != null ? ExtensionPage.Changelog : ExtensionPage.Info
+
);
+
+
const tagline = ext.manifest?.meta?.tagline;
+
const settings = ext.settingsOverride ?? ext.manifest?.settings;
+
const description = ext.manifest?.meta?.description;
+
const changelog = ext.changelog;
+
const linkButtons = [
+
ext?.manifest?.meta?.source && (
+
<PanelLinkButton icon={<AngleBracketsIcon />} tooltip="View source" link={ext.manifest.meta.source} />
+
),
+
ext?.source?.url && <PanelLinkButton icon={<ChannelListIcon />} tooltip="View repository" link={ext.source.url} />,
+
ext?.manifest?.meta?.donate && (
+
<PanelLinkButton icon={<HeartIcon />} tooltip="Donate" link={ext.manifest.meta.donate} />
+
)
+
].filter((x) => x != null);
+
+
const enabledDependants = useStateFromStores([MoonbaseSettingsStore], () =>
+
Object.keys(MoonbaseSettingsStore.extensions)
+
.filter((uniqueId) => {
+
const potentialDependant = MoonbaseSettingsStore.getExtension(parseInt(uniqueId));
+
+
return (
+
potentialDependant.manifest.dependencies?.includes(ext?.id) &&
+
MoonbaseSettingsStore.getExtensionEnabled(parseInt(uniqueId))
+
);
+
})
+
.map((a) => MoonbaseSettingsStore.getExtension(parseInt(a)))
+
);
+
const implicitlyEnabled = enabledDependants.length > 0;
+
+
const hasDuplicateEntry = useStateFromStores([MoonbaseSettingsStore], () =>
+
Object.entries(MoonbaseSettingsStore.extensions).some(
+
([otherUniqueId, otherExt]) =>
+
otherExt != null && otherExt?.id === ext?.id && parseInt(otherUniqueId) !== uniqueId
+
)
+
);
+
+
return ext == null ? (
+
<></>
+
) : (
+
<Card editable={true} className={AppCardClasses.card}>
+
<div className={AppCardClasses.cardHeader}>
+
<Flex direction={Flex.Direction.VERTICAL}>
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER}>
+
<Text variant="text-md/semibold">{ext.manifest?.meta?.name ?? ext.id}</Text>
+
{ext.source.type === ExtensionLoadSource.Developer && (
+
<Tooltip text="This is a local extension" position="top">
+
{(props: any) => <ScienceIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />}
+
</Tooltip>
+
)}
+
+
{hasDuplicateEntry && ext?.source?.url && (
+
<Tooltip text={`This extension is from the following repository: ${ext.source.url}`} position="top">
+
{(props: any) => <WindowTopOutlineIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />}
+
</Tooltip>
+
)}
+
+
{ext.manifest?.meta?.deprecated && (
+
<Tooltip text="This extension is deprecated" position="top">
+
{(props: any) => <WarningIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />}
+
</Tooltip>
+
)}
+
</Flex>
+
+
{tagline != null && <Text variant="text-sm/normal">{MarkupUtils.parse(tagline)}</Text>}
+
</Flex>
+
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.END} justify={Flex.Justify.END}>
+
<div
+
// too lazy to learn how <Flex /> works lmao
+
style={{
+
display: "flex",
+
alignItems: "center",
+
gap: "1rem"
+
}}
+
>
+
{ext.state === ExtensionState.NotDownloaded ? (
+
<Tooltip
+
text={conflicting ? CONFLICTING_TEXT : COMPAT_TEXT_MAP[ext.compat]}
+
shouldShow={conflicting || ext.compat !== ExtensionCompat.Compatible}
+
>
+
{(props: any) => (
+
<Button
+
{...props}
+
color={Button.Colors.BRAND}
+
submitting={busy}
+
disabled={ext.compat !== ExtensionCompat.Compatible || conflicting}
+
onClick={async () => {
+
await MoonbaseSettingsStore.installExtension(uniqueId);
+
const deps = await MoonbaseSettingsStore.getDependencies(uniqueId);
+
if (deps != null) {
+
await doMissingExtensionPopup(deps);
+
}
+
+
// Don't auto enable dangerous extensions
+
if (!ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) {
+
MoonbaseSettingsStore.setExtensionEnabled(uniqueId, true);
+
}
+
}}
+
>
+
Install
+
</Button>
+
)}
+
</Tooltip>
+
) : (
+
<>
+
{ext.source.type === ExtensionLoadSource.Normal && (
+
<PanelButton
+
icon={TrashIcon}
+
tooltipText="Delete"
+
onClick={() => {
+
MoonbaseSettingsStore.deleteExtension(uniqueId);
+
}}
+
/>
+
)}
+
+
{update != null && (
+
<PanelButton
+
icon={DownloadIcon}
+
tooltipText="Update"
+
onClick={() => {
+
MoonbaseSettingsStore.installExtension(uniqueId);
+
}}
+
/>
+
)}
+
+
<FormSwitch
+
value={ext.compat === ExtensionCompat.Compatible && (enabled || implicitlyEnabled)}
+
disabled={implicitlyEnabled || ext.compat !== ExtensionCompat.Compatible}
+
hideBorder={true}
+
style={{ marginBottom: "0px" }}
+
// @ts-expect-error fix type later
+
tooltipNote={
+
ext.compat !== ExtensionCompat.Compatible ? (
+
COMPAT_TEXT_MAP[ext.compat]
+
) : implicitlyEnabled ? (
+
<div style={{ display: "flex", flexDirection: "column" }}>
+
<div>{`This extension is a dependency of the following enabled extension${
+
enabledDependants.length > 1 ? "s" : ""
+
}:`}</div>
+
{enabledDependants.map((dep) => (
+
<div>{"โ€ข " + (dep.manifest.meta?.name ?? dep.id)}</div>
+
))}
+
</div>
+
) : undefined
+
}
+
onChange={() => {
+
const toggle = () => {
+
MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled);
+
};
+
+
if (enabled && constants.builtinExtensions.includes(ext.id)) {
+
doGenericExtensionPopup(
+
"Built in extension",
+
"This extension is enabled by default. Disabling it might have consequences. Are you sure you want to disable it?",
+
uniqueId,
+
toggle
+
);
+
} else if (!enabled && ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) {
+
doGenericExtensionPopup(
+
"Dangerous extension",
+
"This extension is marked as dangerous. Enabling it might have consequences. Are you sure you want to enable it?",
+
uniqueId,
+
toggle
+
);
+
} else {
+
toggle();
+
}
+
}}
+
/>
+
</>
+
)}
+
</div>
+
</Flex>
+
</div>
+
+
<div>
+
{(description != null || changelog != null || settings != null || linkButtons.length > 0) && (
+
<Flex>
+
<TabBar
+
selectedItem={tab}
+
type="top"
+
onItemSelect={setTab}
+
className={DiscoveryClasses.tabBar}
+
style={{
+
padding: "0 20px"
+
}}
+
>
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Info}>
+
Info
+
</TabBar.Item>
+
+
{description != null && (
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Description}>
+
Description
+
</TabBar.Item>
+
)}
+
+
{changelog != null && (
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Changelog}>
+
Changelog
+
</TabBar.Item>
+
)}
+
+
{settings != null && (
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Settings}>
+
Settings
+
</TabBar.Item>
+
)}
+
</TabBar>
+
+
<Flex
+
align={Flex.Align.CENTER}
+
justify={Flex.Justify.END}
+
direction={Flex.Direction.HORIZONTAL}
+
grow={1}
+
className="moonbase-link-buttons"
+
>
+
{linkButtons.length > 0 && linkButtons}
+
</Flex>
+
</Flex>
+
)}
+
+
<Flex
+
justify={Flex.Justify.START}
+
wrap={Flex.Wrap.WRAP}
+
style={{
+
padding: "16px 16px",
+
// This looks wonky in the settings tab
+
rowGap: tab === ExtensionPage.Info ? "16px" : undefined
+
}}
+
>
+
{tab === ExtensionPage.Info && <ExtensionInfo ext={ext} selectTag={selectTag} />}
+
{tab === ExtensionPage.Description && (
+
<Text variant="text-md/normal" className={MarkupClasses.markup} style={{ width: "100%" }}>
+
{MarkupUtils.parse(description ?? "*No description*", true, {
+
allowHeading: true,
+
allowLinks: true,
+
allowList: true
+
})}
+
</Text>
+
)}
+
{tab === ExtensionPage.Changelog && (
+
<Text variant="text-md/normal" className={MarkupClasses.markup} style={{ width: "100%" }}>
+
{MarkupUtils.parse(changelog ?? "*No changelog*", true, {
+
allowHeading: true,
+
allowLinks: true,
+
allowList: true
+
})}
+
</Text>
+
)}
+
{tab === ExtensionPage.Settings && (
+
<ErrorBoundary>
+
<Settings ext={ext} />
+
</ErrorBoundary>
+
)}
+
</Flex>
+
</div>
+
</Card>
+
);
+
}
+356
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
···
+
import { tagNames } from "./info";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import * as React from "@moonlight-mod/wp/react";
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
+
import { WindowStore } from "@moonlight-mod/wp/common_stores";
+
import {
+
Button,
+
Text,
+
Heading,
+
Popout,
+
Dialog,
+
Menu,
+
ChevronSmallDownIcon,
+
ChevronSmallUpIcon,
+
ArrowsUpDownIcon,
+
RetryIcon,
+
Tooltip
+
} from "@moonlight-mod/wp/discord/components/common/index";
+
import { MenuGroup, MenuCheckboxItem, MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
+
import TagItem from "@moonlight-mod/wp/discord/modules/forums/web/Tag";
+
+
export enum Filter {
+
Core = 1 << 0,
+
Normal = 1 << 1,
+
Developer = 1 << 2,
+
Enabled = 1 << 3,
+
Disabled = 1 << 4,
+
Installed = 1 << 5,
+
Repository = 1 << 6,
+
Incompatible = 1 << 7,
+
Deprecated = 1 << 8
+
}
+
export const defaultFilter = 127 as Filter;
+
+
let HeaderClasses: any;
+
let ForumsClasses: any;
+
let SortMenuClasses: any;
+
spacepack
+
.lazyLoad('"Missing channel in Channel.openChannelContextMenu"', /e\("(\d+)"\)/g, /webpackId:(\d+?),/)
+
.then(() => {
+
ForumsClasses = spacepack.require("discord/modules/forums/web/Forums.css");
+
HeaderClasses = spacepack.require("discord/modules/forums/web/Header.css");
+
SortMenuClasses = spacepack.require("discord/modules/forums/web/SortMenu.css");
+
});
+
+
function toggleTag(selectedTags: Set<string>, setSelectedTags: (tags: Set<string>) => void, tag: string) {
+
const newState = new Set(selectedTags);
+
if (newState.has(tag)) newState.delete(tag);
+
else newState.add(tag);
+
setSelectedTags(newState);
+
}
+
+
function FilterButtonPopout({
+
filter,
+
setFilter,
+
closePopout
+
}: {
+
filter: Filter;
+
setFilter: (filter: Filter) => void;
+
closePopout: () => void;
+
}) {
+
const toggleFilter = (set: Filter) => setFilter(filter & set ? filter & ~set : filter | set);
+
+
return (
+
<div className={SortMenuClasses.container}>
+
<Menu navId="sort-filter" hideScroller={true} onClose={closePopout}>
+
<MenuGroup label="Type">
+
<MenuCheckboxItem
+
id="t-core"
+
label="Core"
+
checked={(filter & Filter.Core) === Filter.Core}
+
action={() => toggleFilter(Filter.Core)}
+
/>
+
<MenuCheckboxItem
+
id="t-normal"
+
label="Normal"
+
checked={(filter & Filter.Normal) === Filter.Normal}
+
action={() => toggleFilter(Filter.Normal)}
+
/>
+
<MenuCheckboxItem
+
id="t-developer"
+
label="Developer"
+
checked={(filter & Filter.Developer) === Filter.Developer}
+
action={() => toggleFilter(Filter.Developer)}
+
/>
+
</MenuGroup>
+
<MenuGroup label="State">
+
<MenuCheckboxItem
+
id="s-enabled"
+
label="Enabled"
+
checked={(filter & Filter.Enabled) === Filter.Enabled}
+
action={() => toggleFilter(Filter.Enabled)}
+
/>
+
<MenuCheckboxItem
+
id="s-disabled"
+
label="Disabled"
+
checked={(filter & Filter.Disabled) === Filter.Disabled}
+
action={() => toggleFilter(Filter.Disabled)}
+
/>
+
</MenuGroup>
+
<MenuGroup label="Location">
+
<MenuCheckboxItem
+
id="l-installed"
+
label="Installed"
+
checked={(filter & Filter.Installed) === Filter.Installed}
+
action={() => toggleFilter(Filter.Installed)}
+
/>
+
<MenuCheckboxItem
+
id="l-repository"
+
label="Repository"
+
checked={(filter & Filter.Repository) === Filter.Repository}
+
action={() => toggleFilter(Filter.Repository)}
+
/>
+
</MenuGroup>
+
<MenuGroup>
+
<MenuCheckboxItem
+
id="l-incompatible"
+
label="Show incompatible"
+
checked={(filter & Filter.Incompatible) === Filter.Incompatible}
+
action={() => toggleFilter(Filter.Incompatible)}
+
/>
+
<MenuCheckboxItem
+
id="l-deprecated"
+
label="Show deprecated"
+
checked={(filter & Filter.Deprecated) === Filter.Deprecated}
+
action={() => toggleFilter(Filter.Deprecated)}
+
/>
+
<MenuItem
+
id="reset-all"
+
className={SortMenuClasses.clearText}
+
label="Reset to default"
+
action={() => {
+
setFilter(defaultFilter);
+
closePopout();
+
}}
+
/>
+
</MenuGroup>
+
</Menu>
+
</div>
+
);
+
}
+
+
function TagButtonPopout({ selectedTags, setSelectedTags, setPopoutRef, closePopout }: any) {
+
return (
+
<Dialog ref={setPopoutRef} className={HeaderClasses.container}>
+
<div className={HeaderClasses.header}>
+
<div className={HeaderClasses.headerLeft}>
+
<Heading color="interactive-normal" variant="text-xs/bold" className={HeaderClasses.headerText}>
+
Select tags
+
</Heading>
+
<div className={HeaderClasses.countContainer}>
+
<Text className={HeaderClasses.countText} color="none" variant="text-xs/medium">
+
{selectedTags.size}
+
</Text>
+
</div>
+
</div>
+
</div>
+
<div className={HeaderClasses.tagContainer}>
+
{Object.keys(tagNames).map((tag) => (
+
<TagItem
+
key={tag}
+
className={HeaderClasses.tag}
+
tag={{ name: tagNames[tag as keyof typeof tagNames], id: tagNames[tag as keyof typeof tagNames] }}
+
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
+
selected={selectedTags.has(tag)}
+
/>
+
))}
+
</div>
+
<div className={HeaderClasses.separator} />
+
<Button
+
look={Button.Looks.LINK}
+
size={Button.Sizes.MIN}
+
color={Button.Colors.CUSTOM}
+
className={HeaderClasses.clear}
+
onClick={() => {
+
setSelectedTags(new Set());
+
closePopout();
+
}}
+
>
+
<Text variant="text-sm/medium" color="text-link">
+
Clear all
+
</Text>
+
</Button>
+
</Dialog>
+
);
+
}
+
+
export default function FilterBar({
+
filter,
+
setFilter,
+
selectedTags,
+
setSelectedTags
+
}: {
+
filter: Filter;
+
setFilter: (filter: Filter) => void;
+
selectedTags: Set<string>;
+
setSelectedTags: (tags: Set<string>) => void;
+
}) {
+
const windowSize = useStateFromStores([WindowStore], () => WindowStore.windowSize());
+
+
const tagsContainer = React.useRef<HTMLDivElement>(null);
+
const tagListInner = React.useRef<HTMLDivElement>(null);
+
const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0);
+
const [checkingUpdates, setCheckingUpdates] = React.useState(false);
+
+
React.useLayoutEffect(() => {
+
if (tagsContainer.current === null || tagListInner.current === null) return;
+
const { left: containerX, top: containerY } = tagsContainer.current.getBoundingClientRect();
+
let offset = 0;
+
for (const child of tagListInner.current.children) {
+
const { right: childX, top: childY, height } = child.getBoundingClientRect();
+
if (childY - containerY > height) break;
+
const newOffset = childX - containerX;
+
if (newOffset > offset) {
+
offset = newOffset;
+
}
+
}
+
setTagsButtonOffset(offset);
+
}, [windowSize, tagsContainer.current, tagListInner.current, tagListInner.current?.getBoundingClientRect()?.width]);
+
+
return (
+
<div
+
ref={tagsContainer}
+
style={{
+
paddingTop: "12px"
+
}}
+
className={`${ForumsClasses.tagsContainer} ${Margins.marginBottom8}`}
+
>
+
<Tooltip text="Refresh updates" position="top">
+
{(props: any) => (
+
<Button
+
{...props}
+
size={Button.Sizes.MIN}
+
color={Button.Colors.CUSTOM}
+
className={`${ForumsClasses.sortDropdown} moonbase-retry-button`}
+
innerClassName={ForumsClasses.sortDropdownInner}
+
onClick={() => {
+
(async () => {
+
try {
+
setCheckingUpdates(true);
+
await MoonbaseSettingsStore.checkUpdates();
+
} finally {
+
// artificial delay because the spin is fun
+
await new Promise((r) => setTimeout(r, 500));
+
setCheckingUpdates(false);
+
}
+
})();
+
}}
+
>
+
<RetryIcon size={"custom"} width={16} className={checkingUpdates ? "moonbase-speen" : ""} />
+
</Button>
+
)}
+
</Tooltip>
+
<Popout
+
renderPopout={({ closePopout }: any) => (
+
<FilterButtonPopout filter={filter} setFilter={setFilter} closePopout={closePopout} />
+
)}
+
position="bottom"
+
align="left"
+
>
+
{(props: any, { isShown }: { isShown: boolean }) => (
+
<Button
+
{...props}
+
size={Button.Sizes.MIN}
+
color={Button.Colors.CUSTOM}
+
className={ForumsClasses.sortDropdown}
+
innerClassName={ForumsClasses.sortDropdownInner}
+
>
+
<ArrowsUpDownIcon size="xs" />
+
<Text className={ForumsClasses.sortDropdownText} variant="text-sm/medium" color="interactive-normal">
+
Sort & filter
+
</Text>
+
{isShown ? (
+
<ChevronSmallUpIcon size={"custom"} width={20} />
+
) : (
+
<ChevronSmallDownIcon size={"custom"} width={20} />
+
)}
+
</Button>
+
)}
+
</Popout>
+
<div className={ForumsClasses.divider} />
+
<div className={ForumsClasses.tagList}>
+
<div ref={tagListInner} className={ForumsClasses.tagListInner}>
+
{Object.keys(tagNames).map((tag) => (
+
<TagItem
+
key={tag}
+
className={ForumsClasses.tag}
+
tag={{ name: tagNames[tag as keyof typeof tagNames], id: tag }}
+
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
+
selected={selectedTags.has(tag)}
+
/>
+
))}
+
</div>
+
</div>
+
<Popout
+
renderPopout={({ setPopoutRef, closePopout }: any) => (
+
<TagButtonPopout
+
selectedTags={selectedTags}
+
setSelectedTags={setSelectedTags}
+
setPopoutRef={setPopoutRef}
+
closePopout={closePopout}
+
/>
+
)}
+
position="bottom"
+
align="right"
+
>
+
{(props: any, { isShown }: { isShown: boolean }) => (
+
<Button
+
{...props}
+
size={Button.Sizes.MIN}
+
color={Button.Colors.CUSTOM}
+
style={{
+
left: tagsButtonOffset
+
}}
+
// TODO: Use Discord's class name utility
+
className={`${ForumsClasses.tagsButton} ${selectedTags.size > 0 ? ForumsClasses.tagsButtonWithCount : ""}`}
+
innerClassName={ForumsClasses.tagsButtonInner}
+
>
+
{selectedTags.size > 0 ? (
+
<div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}>
+
<Text className={ForumsClasses.countText} color="none" variant="text-xs/medium">
+
{selectedTags.size}
+
</Text>
+
</div>
+
) : (
+
<>All</>
+
)}
+
{isShown ? (
+
<ChevronSmallUpIcon size={"custom"} width={20} />
+
) : (
+
<ChevronSmallDownIcon size={"custom"} width={20} />
+
)}
+
</Button>
+
)}
+
</Popout>
+
<Button
+
size={Button.Sizes.MIN}
+
color={Button.Colors.CUSTOM}
+
className={`${ForumsClasses.tagsButton} ${ForumsClasses.tagsButtonPlaceholder}`}
+
innerClassName={ForumsClasses.tagsButtonInner}
+
>
+
{selectedTags.size > 0 ? (
+
<div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}>
+
<Text className={ForumsClasses.countText} color="none" variant="text-xs/medium">
+
{selectedTags.size}
+
</Text>
+
</div>
+
) : null}
+
+
<ChevronSmallUpIcon size={"custom"} width={20} />
+
</Button>
+
</div>
+
);
+
}
+162
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
···
+
import { ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types";
+
import { ExtensionState } from "../../../types";
+
import FilterBar, { Filter, defaultFilter } from "./filterBar";
+
import ExtensionCard from "./card";
+
+
import React from "@moonlight-mod/wp/react";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
+
import {
+
FormDivider,
+
CircleInformationIcon,
+
XSmallIcon,
+
Button
+
} from "@moonlight-mod/wp/discord/components/common/index";
+
import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton";
+
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary";
+
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
+
import HelpMessage from "../HelpMessage";
+
+
const SearchBar = spacepack.require("discord/uikit/search/SearchBar").default;
+
+
const validTags: string[] = Object.values(ExtensionTag);
+
+
export default function ExtensionsPage() {
+
const { extensions, savedFilter } = useStateFromStoresObject([MoonbaseSettingsStore], () => {
+
return {
+
extensions: MoonbaseSettingsStore.extensions,
+
savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw<number>("moonbase", "filter", defaultFilter)
+
};
+
});
+
+
const [query, setQuery] = React.useState("");
+
const [hitUpdateAll, setHitUpdateAll] = React.useState(false);
+
+
const filterState = React.useState(defaultFilter);
+
+
let filter: Filter, setFilter: (filter: Filter) => void;
+
if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "saveFilter", false)) {
+
filter = savedFilter ?? defaultFilter;
+
setFilter = (filter) => MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter);
+
} else {
+
filter = filterState[0];
+
setFilter = filterState[1];
+
}
+
+
const [selectedTags, setSelectedTags] = React.useState(new Set<string>());
+
const selectTag = React.useCallback(
+
(tag: string) => {
+
const newState = new Set(selectedTags);
+
if (validTags.includes(tag)) newState.add(tag);
+
setSelectedTags(newState);
+
},
+
[selectedTags]
+
);
+
+
const sorted = Object.values(extensions).sort((a, b) => {
+
const aName = a.manifest.meta?.name ?? a.id;
+
const bName = b.manifest.meta?.name ?? b.id;
+
return aName.localeCompare(bName);
+
});
+
+
const filtered = sorted.filter(
+
(ext) =>
+
(query === "" ||
+
ext.manifest.id?.toLowerCase().includes(query) ||
+
ext.manifest.meta?.name?.toLowerCase().includes(query) ||
+
ext.manifest.meta?.tagline?.toLowerCase().includes(query) ||
+
(ext.manifest?.settings != null &&
+
Object.entries(ext.manifest.settings).some(([key, setting]) =>
+
(setting.displayName ?? key).toLowerCase().includes(query)
+
)) ||
+
(ext.manifest?.meta?.authors != null &&
+
ext.manifest.meta.authors.some((author) =>
+
(typeof author === "string" ? author : author.name).toLowerCase().includes(query)
+
)) ||
+
ext.manifest.meta?.description?.toLowerCase().includes(query)) &&
+
[...selectedTags.values()].every((tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)) &&
+
// This seems very bad, sorry
+
!(
+
(!(filter & Filter.Core) && ext.source.type === ExtensionLoadSource.Core) ||
+
(!(filter & Filter.Normal) && ext.source.type === ExtensionLoadSource.Normal) ||
+
(!(filter & Filter.Developer) && ext.source.type === ExtensionLoadSource.Developer) ||
+
(!(filter & Filter.Enabled) && MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) ||
+
(!(filter & Filter.Disabled) && !MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) ||
+
(!(filter & Filter.Installed) && ext.state !== ExtensionState.NotDownloaded) ||
+
(!(filter & Filter.Repository) && ext.state === ExtensionState.NotDownloaded)
+
) &&
+
(filter & Filter.Incompatible ||
+
ext.compat === ExtensionCompat.Compatible ||
+
(ext.compat === ExtensionCompat.InvalidApiLevel && ext.hasUpdate)) &&
+
(filter & Filter.Deprecated ||
+
ext.manifest?.meta?.deprecated !== true ||
+
ext.state !== ExtensionState.NotDownloaded)
+
);
+
+
// Prioritize extensions with updates
+
const filteredWithUpdates = filtered.filter((ext) => ext!.hasUpdate);
+
const filteredWithoutUpdates = filtered.filter((ext) => !ext!.hasUpdate);
+
+
return (
+
<>
+
<SearchBar
+
size={SearchBar.Sizes.MEDIUM}
+
query={query}
+
onChange={(v: string) => setQuery(v.toLowerCase())}
+
onClear={() => setQuery("")}
+
autoFocus={true}
+
autoComplete="off"
+
inputProps={{
+
autoCapitalize: "none",
+
autoCorrect: "off",
+
spellCheck: "false"
+
}}
+
/>
+
<FilterBar filter={filter} setFilter={setFilter} selectedTags={selectedTags} setSelectedTags={setSelectedTags} />
+
+
{filteredWithUpdates.length > 0 && (
+
<HelpMessage
+
icon={CircleInformationIcon}
+
text="Extension updates are available"
+
className="moonbase-extension-update-section"
+
>
+
<div className="moonbase-help-message-buttons">
+
<Button
+
color={Button.Colors.BRAND}
+
size={Button.Sizes.TINY}
+
disabled={hitUpdateAll}
+
onClick={() => {
+
setHitUpdateAll(true);
+
MoonbaseSettingsStore.updateAllExtensions();
+
}}
+
>
+
Update all
+
</Button>
+
<PanelButton
+
icon={XSmallIcon}
+
onClick={() => {
+
MoonbaseSettingsStore.dismissAllExtensionUpdates();
+
}}
+
/>
+
</div>
+
</HelpMessage>
+
)}
+
+
{filteredWithUpdates.map((ext) => (
+
<ErrorBoundary>
+
<ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} />
+
</ErrorBoundary>
+
))}
+
{filteredWithUpdates.length > 0 && filteredWithoutUpdates.length > 0 && (
+
<FormDivider className="moonbase-update-divider" />
+
)}
+
{filteredWithoutUpdates.map((ext) => (
+
<ErrorBoundary>
+
<ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} />
+
</ErrorBoundary>
+
))}
+
</>
+
);
+
}
+205
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx
···
+
import { ExtensionTag } from "@moonlight-mod/types";
+
import { MoonbaseExtension } from "../../../types";
+
+
import React from "@moonlight-mod/wp/react";
+
import { Text } from "@moonlight-mod/wp/discord/components/common/index";
+
+
type Dependency = {
+
id: string;
+
type: DependencyType;
+
};
+
+
enum DependencyType {
+
Dependency = "dependency",
+
Optional = "optional",
+
Incompatible = "incompatible"
+
}
+
+
export const tagNames: Record<ExtensionTag, string> = {
+
[ExtensionTag.Accessibility]: "Accessibility",
+
[ExtensionTag.Appearance]: "Appearance",
+
[ExtensionTag.Chat]: "Chat",
+
[ExtensionTag.Commands]: "Commands",
+
[ExtensionTag.ContextMenu]: "Context Menu",
+
[ExtensionTag.DangerZone]: "Danger Zone",
+
[ExtensionTag.Development]: "Development",
+
[ExtensionTag.Fixes]: "Fixes",
+
[ExtensionTag.Fun]: "Fun",
+
[ExtensionTag.Markdown]: "Markdown",
+
[ExtensionTag.Voice]: "Voice",
+
[ExtensionTag.Privacy]: "Privacy",
+
[ExtensionTag.Profiles]: "Profiles",
+
[ExtensionTag.QualityOfLife]: "Quality of Life",
+
[ExtensionTag.Library]: "Library"
+
};
+
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
+
function InfoSection({ title, children }: { title: string; children: React.ReactNode }) {
+
return (
+
<div
+
style={{
+
marginRight: "1em"
+
}}
+
>
+
<Text variant="eyebrow" className="moonlight-card-info-header">
+
{title}
+
</Text>
+
+
<Text variant="text-sm/normal">{children}</Text>
+
</div>
+
);
+
}
+
+
function Badge({
+
color,
+
children,
+
style = {},
+
onClick
+
}: {
+
color: string;
+
children: React.ReactNode;
+
style?: React.CSSProperties;
+
onClick?: () => void;
+
}) {
+
if (onClick) style.cursor ??= "pointer";
+
return (
+
<span
+
className="moonlight-card-badge"
+
style={
+
{
+
"--badge-color": color,
+
...style
+
} as React.CSSProperties
+
}
+
onClick={onClick}
+
>
+
{children}
+
</span>
+
);
+
}
+
+
export default function ExtensionInfo({
+
ext,
+
selectTag
+
}: {
+
ext: MoonbaseExtension;
+
selectTag: (tag: string) => void;
+
}) {
+
const authors = ext.manifest?.meta?.authors;
+
const tags = ext.manifest?.meta?.tags;
+
const version = ext.manifest?.version;
+
+
const dependencies: Dependency[] = [];
+
const incompatible: Dependency[] = [];
+
+
if (ext.manifest.dependencies != null) {
+
dependencies.push(
+
...ext.manifest.dependencies.map((dep) => ({
+
id: dep,
+
type: DependencyType.Dependency
+
}))
+
);
+
}
+
+
if (ext.manifest.suggested != null) {
+
dependencies.push(
+
...ext.manifest.suggested.map((dep) => ({
+
id: dep,
+
type: DependencyType.Optional
+
}))
+
);
+
}
+
+
if (ext.manifest.incompatible != null) {
+
incompatible.push(
+
...ext.manifest.incompatible.map((dep) => ({
+
id: dep,
+
type: DependencyType.Incompatible
+
}))
+
);
+
}
+
+
return (
+
<>
+
{authors != null && (
+
<InfoSection title="Authors">
+
{authors.map((author, i) => {
+
const comma = i !== authors.length - 1 ? ", " : "";
+
if (typeof author === "string") {
+
return (
+
<span key={i}>
+
{author}
+
{comma}
+
</span>
+
);
+
} else {
+
// TODO: resolve IDs
+
return (
+
<span key={i}>
+
{author.name}
+
{comma}
+
</span>
+
);
+
}
+
})}
+
</InfoSection>
+
)}
+
+
{tags != null && (
+
<InfoSection title="Tags">
+
{tags.map((tag, i) => {
+
const name = tagNames[tag];
+
let color = "var(--bg-mod-strong)";
+
let style;
+
if (tag === ExtensionTag.DangerZone) {
+
color = "var(--red-460)";
+
style = { color: "var(--primary-230)" };
+
}
+
+
return (
+
<Badge key={i} color={color} style={style} onClick={() => selectTag(tag)}>
+
{name}
+
</Badge>
+
);
+
})}
+
</InfoSection>
+
)}
+
+
{dependencies.length > 0 && (
+
<InfoSection title="Dependencies">
+
{dependencies.map((dep) => {
+
const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id);
+
+
// TODO: figure out a decent way to distinguish suggested
+
return (
+
<Badge color="var(--bg-mod-strong)" key={dep.id}>
+
{name}
+
</Badge>
+
);
+
})}
+
</InfoSection>
+
)}
+
+
{incompatible.length > 0 && (
+
<InfoSection title="Incompatible">
+
{incompatible.map((dep) => {
+
const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id);
+
+
return (
+
<Badge color="var(--bg-mod-strong)" key={dep.id}>
+
{name}
+
</Badge>
+
);
+
})}
+
</InfoSection>
+
)}
+
+
{version != null && (
+
<InfoSection title="Version">
+
<span>{version}</span>
+
</InfoSection>
+
)}
+
</>
+
);
+
}
+211
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx
···
+
// TODO: clean up the styling here
+
import React from "@moonlight-mod/wp/react";
+
import { MoonbaseExtension } from "core-extensions/src/moonbase/types";
+
import { openModalLazy, useModalsStore, closeModal } from "@moonlight-mod/wp/discord/modules/modals/Modals";
+
import { SingleSelect, Text } from "@moonlight-mod/wp/discord/components/common/index";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import { ExtensionLoadSource } from "@moonlight-mod/types";
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
+
let ConfirmModal: typeof import("@moonlight-mod/wp/discord/components/modals/ConfirmModal").default;
+
+
function close() {
+
const ModalStore = useModalsStore.getState();
+
closeModal(ModalStore.default[0].key);
+
}
+
+
// do this to avoid a hard dependency
+
function lazyLoad() {
+
if (!ConfirmModal) {
+
ConfirmModal = spacepack.require("discord/components/modals/ConfirmModal").default;
+
}
+
}
+
+
const presentableLoadSources: Record<ExtensionLoadSource, string> = {
+
[ExtensionLoadSource.Developer]: "Local extension", // should never show up
+
[ExtensionLoadSource.Core]: "Core extension",
+
[ExtensionLoadSource.Normal]: "Extension repository"
+
};
+
+
function ExtensionSelect({
+
id,
+
candidates,
+
option,
+
setOption
+
}: {
+
id: string;
+
candidates: MoonbaseExtension[];
+
option: string | undefined;
+
setOption: (pick: string | undefined) => void;
+
}) {
+
return (
+
<SingleSelect
+
key={id}
+
autofocus={false}
+
value={option}
+
options={candidates.map((candidate) => {
+
return {
+
value: candidate.uniqueId.toString(),
+
label:
+
candidate.source.url ?? presentableLoadSources[candidate.source.type] ?? candidate.manifest.version ?? ""
+
};
+
})}
+
onChange={(value: string) => {
+
setOption(value);
+
}}
+
placeholder="Missing extension"
+
/>
+
);
+
}
+
+
function MissingExtensionPopup({
+
deps,
+
transitionState
+
}: {
+
deps: Record<string, MoonbaseExtension[]>;
+
transitionState: number | null;
+
}) {
+
lazyLoad();
+
const amountNotAvailable = Object.values(deps).filter((candidates) => candidates.length === 0).length;
+
+
const [options, setOptions] = React.useState<Record<string, string | undefined>>(
+
Object.fromEntries(
+
Object.entries(deps).map(([id, candidates]) => [
+
id,
+
candidates.length > 0 ? candidates[0].uniqueId.toString() : undefined
+
])
+
)
+
);
+
+
return (
+
<ConfirmModal
+
body={
+
<Flex
+
style={{
+
gap: "20px"
+
}}
+
direction={Flex.Direction.VERTICAL}
+
>
+
<Text variant="text-md/normal">
+
This extension depends on other extensions which are not downloaded. Choose which extensions to download.
+
</Text>
+
+
{amountNotAvailable > 0 && (
+
<Text variant="text-md/normal">
+
{amountNotAvailable} extension
+
{amountNotAvailable > 1 ? "s" : ""} could not be found, and must be installed manually.
+
</Text>
+
)}
+
+
<div
+
style={{
+
display: "grid",
+
gridTemplateColumns: "1fr 2fr",
+
gap: "10px"
+
}}
+
>
+
{Object.entries(deps).map(([id, candidates], i) => (
+
<>
+
<Text
+
variant="text-md/normal"
+
style={{
+
alignSelf: "center",
+
wordBreak: "break-word"
+
}}
+
>
+
{MoonbaseSettingsStore.tryGetExtensionName(id)}
+
</Text>
+
+
<ExtensionSelect
+
id={id}
+
candidates={candidates}
+
option={options[id]}
+
setOption={(pick) =>
+
setOptions((prev) => ({
+
...prev,
+
[id]: pick
+
}))
+
}
+
/>
+
</>
+
))}
+
</div>
+
</Flex>
+
}
+
cancelText="Cancel"
+
confirmText="Install"
+
onCancel={close}
+
onConfirm={() => {
+
close();
+
+
for (const pick of Object.values(options)) {
+
if (pick != null) {
+
MoonbaseSettingsStore.installExtension(parseInt(pick));
+
}
+
}
+
}}
+
title="Extension dependencies"
+
transitionState={transitionState}
+
/>
+
);
+
}
+
+
export async function doMissingExtensionPopup(deps: Record<string, MoonbaseExtension[]>) {
+
await openModalLazy(async () => {
+
return ({ transitionState }: { transitionState: number | null }) => {
+
return <MissingExtensionPopup transitionState={transitionState} deps={deps} />;
+
};
+
});
+
}
+
+
function GenericExtensionPopup({
+
title,
+
content,
+
transitionState,
+
uniqueId,
+
cb
+
}: {
+
title: string;
+
content: string;
+
transitionState: number | null;
+
uniqueId: number;
+
cb: () => void;
+
}) {
+
lazyLoad();
+
+
return (
+
<ConfirmModal
+
title={title}
+
body={
+
<Flex>
+
<Text variant="text-md/normal">{content}</Text>
+
</Flex>
+
}
+
confirmText="Yes"
+
cancelText="No"
+
onCancel={close}
+
onConfirm={() => {
+
close();
+
cb();
+
}}
+
transitionState={transitionState}
+
/>
+
);
+
}
+
+
export async function doGenericExtensionPopup(title: string, content: string, uniqueId: number, cb: () => void) {
+
await openModalLazy(async () => {
+
return ({ transitionState }: { transitionState: number | null }) => {
+
return (
+
<GenericExtensionPopup
+
title={title}
+
content={content}
+
transitionState={transitionState}
+
uniqueId={uniqueId}
+
cb={cb}
+
/>
+
);
+
};
+
});
+
}
+418
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
···
+
import {
+
ExtensionSettingType,
+
ExtensionSettingsManifest,
+
MultiSelectSettingType,
+
NumberSettingType,
+
SelectOption,
+
SelectSettingType
+
} from "@moonlight-mod/types/config";
+
+
import { ExtensionState, MoonbaseExtension } from "../../../types";
+
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import React from "@moonlight-mod/wp/react";
+
import {
+
FormSwitch,
+
FormItem,
+
FormText,
+
TextInput,
+
Slider,
+
TextArea,
+
Tooltip,
+
Clickable,
+
CircleXIcon,
+
Text,
+
SingleSelect,
+
Button,
+
useVariableSelect,
+
multiSelect,
+
Select as DiscordSelect,
+
NumberInputStepper
+
} from "@moonlight-mod/wp/discord/components/common/index";
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary";
+
+
let GuildSettingsRoleEditClasses: any;
+
spacepack
+
.lazyLoad(
+
"renderArtisanalHack",
+
/\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"GuildSettings"/,
+
/webpackId:(\d+),name:"GuildSettings"/
+
)
+
.then(
+
() =>
+
(GuildSettingsRoleEditClasses = spacepack.require(
+
"discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"
+
))
+
);
+
+
type SettingsProps = {
+
ext: MoonbaseExtension;
+
name: string;
+
setting: ExtensionSettingsManifest;
+
disabled: boolean;
+
};
+
type SettingsComponent = React.ComponentType<SettingsProps>;
+
+
const Margins = spacepack.require("discord/styles/shared/Margins.css");
+
+
function markdownify(str: string) {
+
return MarkupUtils.parse(str, true, {
+
hideSimpleEmbedContent: true,
+
allowLinks: true
+
});
+
}
+
+
function useConfigEntry<T>(uniqueId: number, name: string) {
+
return useStateFromStores(
+
[MoonbaseSettingsStore],
+
() => {
+
return {
+
value: MoonbaseSettingsStore.getExtensionConfig<T>(uniqueId, name),
+
displayName: MoonbaseSettingsStore.getExtensionConfigName(uniqueId, name),
+
description: MoonbaseSettingsStore.getExtensionConfigDescription(uniqueId, name)
+
};
+
},
+
[uniqueId, name]
+
);
+
}
+
+
function Boolean({ ext, name, setting, disabled }: SettingsProps) {
+
const { value, displayName, description } = useConfigEntry<boolean>(ext.uniqueId, name);
+
+
return (
+
<FormSwitch
+
value={value ?? false}
+
hideBorder={true}
+
disabled={disabled}
+
onChange={(value: boolean) => {
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
+
}}
+
note={description != null ? markdownify(description) : undefined}
+
className={`${Margins.marginReset} ${Margins.marginTop20}`}
+
>
+
{displayName}
+
</FormSwitch>
+
);
+
}
+
+
function Number({ ext, name, setting, disabled }: SettingsProps) {
+
const { value, displayName, description } = useConfigEntry<number>(ext.uniqueId, name);
+
+
const castedSetting = setting as NumberSettingType;
+
const min = castedSetting.min;
+
const max = castedSetting.max;
+
+
const onChange = (value: number) => {
+
const rounded = min == null || max == null ? Math.round(value) : Math.max(min, Math.min(max, Math.round(value)));
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
+
};
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{min == null || max == null ? (
+
<Flex justify={Flex.Justify.BETWEEN} direction={Flex.Direction.HORIZONTAL}>
+
{description && <FormText>{markdownify(description)}</FormText>}
+
<NumberInputStepper value={value ?? 0} onChange={onChange} />
+
</Flex>
+
) : (
+
<>
+
{description && <FormText>{markdownify(description)}</FormText>}
+
<Slider
+
initialValue={value ?? 0}
+
disabled={disabled}
+
minValue={min}
+
maxValue={max}
+
onValueChange={onChange}
+
onValueRender={(value: number) => `${Math.round(value)}`}
+
/>
+
</>
+
)}
+
</FormItem>
+
);
+
}
+
+
function String({ ext, name, setting, disabled }: SettingsProps) {
+
const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name);
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
+
<TextInput
+
value={value ?? ""}
+
disabled={disabled}
+
onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)}
+
/>
+
</FormItem>
+
);
+
}
+
+
function MultilineString({ ext, name, setting, disabled }: SettingsProps) {
+
const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name);
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
+
<TextArea
+
rows={5}
+
value={value ?? ""}
+
disabled={disabled}
+
className={"moonbase-resizeable"}
+
onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)}
+
/>
+
</FormItem>
+
);
+
}
+
+
function Select({ ext, name, setting, disabled }: SettingsProps) {
+
const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name);
+
+
const castedSetting = setting as SelectSettingType;
+
const options = castedSetting.options;
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
+
<SingleSelect
+
autofocus={false}
+
clearable={false}
+
value={value ?? ""}
+
options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))}
+
onChange={(value: string) => {
+
if (disabled) return;
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
+
}}
+
/>
+
</FormItem>
+
);
+
}
+
+
function MultiSelect({ ext, name, setting, disabled }: SettingsProps) {
+
const { value, displayName, description } = useConfigEntry<string | string[]>(ext.uniqueId, name);
+
+
const castedSetting = setting as MultiSelectSettingType;
+
const options = castedSetting.options;
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
+
<DiscordSelect
+
autofocus={false}
+
clearable={false}
+
closeOnSelect={false}
+
options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))}
+
{...useVariableSelect({
+
onSelectInteraction: multiSelect,
+
value: value == null ? new Set() : new Set(Array.isArray(value) ? value : [value]),
+
onChange: (value: string) => {
+
if (disabled) return;
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Array.from(value));
+
}
+
})}
+
/>
+
</FormItem>
+
);
+
}
+
+
function RemoveEntryButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) {
+
return (
+
<div className={GuildSettingsRoleEditClasses.removeButtonContainer}>
+
<Tooltip text="Remove entry" position="top">
+
{(props: any) => (
+
<Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}>
+
<CircleXIcon width={16} height={16} />
+
</Clickable>
+
)}
+
</Tooltip>
+
</div>
+
);
+
}
+
+
function List({ ext, name, setting, disabled }: SettingsProps) {
+
const { value, displayName, description } = useConfigEntry<string[]>(ext.uniqueId, name);
+
+
const entries = value ?? [];
+
const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries);
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>}
+
<Flex direction={Flex.Direction.VERTICAL}>
+
{entries.map((val, i) => (
+
// FIXME: stylesheets
+
<div
+
key={i}
+
style={{
+
display: "grid",
+
height: "32px",
+
gap: "8px",
+
gridTemplateColumns: "1fr 32px",
+
alignItems: "center"
+
}}
+
>
+
<TextInput
+
size={TextInput.Sizes.MINI}
+
value={val}
+
disabled={disabled}
+
onChange={(newVal: string) => {
+
entries[i] = newVal;
+
updateConfig();
+
}}
+
/>
+
<RemoveEntryButton
+
disabled={disabled}
+
onClick={() => {
+
entries.splice(i, 1);
+
updateConfig();
+
}}
+
/>
+
</div>
+
))}
+
+
<Button
+
look={Button.Looks.FILLED}
+
color={Button.Colors.GREEN}
+
size={Button.Sizes.SMALL}
+
disabled={disabled}
+
className={Margins.marginTop8}
+
onClick={() => {
+
entries.push("");
+
updateConfig();
+
}}
+
>
+
Add new entry
+
</Button>
+
</Flex>
+
</FormItem>
+
);
+
}
+
+
function Dictionary({ ext, name, setting, disabled }: SettingsProps) {
+
const { value, displayName, description } = useConfigEntry<Record<string, string>>(ext.uniqueId, name);
+
+
const entries = Object.entries(value ?? {});
+
const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Object.fromEntries(entries));
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>}
+
<Flex direction={Flex.Direction.VERTICAL}>
+
{entries.map(([key, val], i) => (
+
// FIXME: stylesheets
+
<div
+
key={i}
+
style={{
+
display: "grid",
+
height: "32px",
+
gap: "8px",
+
gridTemplateColumns: "1fr 1fr 32px",
+
alignItems: "center"
+
}}
+
>
+
<TextInput
+
size={TextInput.Sizes.MINI}
+
value={key}
+
disabled={disabled}
+
onChange={(newKey: string) => {
+
entries[i][0] = newKey;
+
updateConfig();
+
}}
+
/>
+
<TextInput
+
size={TextInput.Sizes.MINI}
+
value={val}
+
disabled={disabled}
+
onChange={(newValue: string) => {
+
entries[i][1] = newValue;
+
updateConfig();
+
}}
+
/>
+
<RemoveEntryButton
+
disabled={disabled}
+
onClick={() => {
+
entries.splice(i, 1);
+
updateConfig();
+
}}
+
/>
+
</div>
+
))}
+
+
<Button
+
look={Button.Looks.FILLED}
+
color={Button.Colors.GREEN}
+
size={Button.Sizes.SMALL}
+
className={Margins.marginTop8}
+
disabled={disabled}
+
onClick={() => {
+
entries.push([`entry-${entries.length}`, ""]);
+
updateConfig();
+
}}
+
>
+
Add new entry
+
</Button>
+
</Flex>
+
</FormItem>
+
);
+
}
+
+
function Custom({ ext, name, setting, disabled }: SettingsProps) {
+
const { value, displayName } = useConfigEntry<any>(ext.uniqueId, name);
+
+
const { component: Component } = useStateFromStores(
+
[MoonbaseSettingsStore],
+
() => {
+
return {
+
component: MoonbaseSettingsStore.getExtensionConfigComponent(ext.id, name)
+
};
+
},
+
[ext.uniqueId, name]
+
);
+
+
if (Component == null) {
+
return (
+
<Text variant="text-md/normal">{`Custom setting "${displayName}" is missing a component. Perhaps the extension is not installed?`}</Text>
+
);
+
}
+
+
return (
+
<ErrorBoundary>
+
<Component value={value} setValue={(value) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} />
+
</ErrorBoundary>
+
);
+
}
+
+
function Setting({ ext, name, setting, disabled }: SettingsProps) {
+
const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = {
+
[ExtensionSettingType.Boolean]: Boolean,
+
[ExtensionSettingType.Number]: Number,
+
[ExtensionSettingType.String]: String,
+
[ExtensionSettingType.MultilineString]: MultilineString,
+
[ExtensionSettingType.Select]: Select,
+
[ExtensionSettingType.MultiSelect]: MultiSelect,
+
[ExtensionSettingType.List]: List,
+
[ExtensionSettingType.Dictionary]: Dictionary,
+
[ExtensionSettingType.Custom]: Custom
+
};
+
const element = elements[setting.type];
+
if (element == null) return <></>;
+
return React.createElement(element, { ext, name, setting, disabled });
+
}
+
+
export default function Settings({ ext }: { ext: MoonbaseExtension }) {
+
return (
+
<Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}>
+
{Object.entries(ext.settingsOverride ?? ext.manifest.settings!).map(([name, setting]) => (
+
<Setting
+
ext={ext}
+
key={name}
+
name={name}
+
setting={setting}
+
disabled={ext.state === ExtensionState.NotDownloaded}
+
/>
+
))}
+
</Flex>
+
);
+
}
+85
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
···
+
import React from "@moonlight-mod/wp/react";
+
import { Text, TabBar } from "@moonlight-mod/wp/discord/components/common/index";
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
+
import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores";
+
+
import ExtensionsPage from "./extensions";
+
import ConfigPage from "./config";
+
import AboutPage from "./about";
+
import Update from "./update";
+
import RestartAdviceMessage from "./RestartAdvice";
+
import { Divider } from "@moonlight-mod/wp/discord/components/common/BaseHeaderBar";
+
import HeaderBarClasses from "@moonlight-mod/wp/discord/components/common/HeaderBar.css";
+
import PeoplePageClasses from "@moonlight-mod/wp/discord/modules/people/web/PeoplePage.css";
+
import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators";
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
+
+
export const pages: {
+
id: string;
+
name: string;
+
element: React.FunctionComponent;
+
}[] = [
+
{
+
id: "extensions",
+
name: "Extensions",
+
element: ExtensionsPage
+
},
+
{
+
id: "config",
+
name: "Config",
+
element: ConfigPage
+
},
+
{
+
id: "about",
+
name: "About",
+
element: AboutPage
+
}
+
];
+
+
export function Moonbase(props: { initialTab?: number } = {}) {
+
const subsection = useStateFromStores([UserSettingsModalStore], () => UserSettingsModalStore.getSubsection() ?? 0);
+
const setSubsection = React.useCallback(
+
(to: string) => {
+
if (subsection !== to) UserSettingsModalActionCreators.setSection("moonbase", to);
+
},
+
[subsection]
+
);
+
+
React.useEffect(
+
() => () => {
+
// 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
+
UserSettingsModalActionCreators.clearSubsection("moonbase");
+
},
+
[]
+
);
+
+
return (
+
<>
+
<div className={`${HeaderBarClasses.children} ${Margins.marginBottom20}`}>
+
<Text className={HeaderBarClasses.titleWrapper} variant="heading-lg/semibold" tag="h2">
+
Moonbase
+
</Text>
+
<Divider />
+
<TabBar
+
selectedItem={subsection}
+
onItemSelect={setSubsection}
+
type="top-pill"
+
className={PeoplePageClasses.tabBar}
+
>
+
{pages.map((page, i) => (
+
<TabBar.Item key={page.id} id={i} className={PeoplePageClasses.item}>
+
{page.name}
+
</TabBar.Item>
+
))}
+
</TabBar>
+
</div>
+
+
<RestartAdviceMessage />
+
<Update />
+
+
{React.createElement(pages[subsection].element)}
+
</>
+
);
+
}
+
+
export { RestartAdviceMessage, Update };
+124
packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx
···
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import React from "@moonlight-mod/wp/react";
+
import { UpdateState } from "../../types";
+
import HelpMessage from "./HelpMessage";
+
import { MoonlightBranch } from "@moonlight-mod/types";
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
+
import {
+
Button,
+
Text,
+
ModalRoot,
+
ModalSize,
+
ModalContent,
+
ModalHeader,
+
Heading,
+
ModalCloseButton,
+
openModal
+
} from "@moonlight-mod/wp/discord/components/common/index";
+
import MarkupClasses from "@moonlight-mod/wp/discord/modules/messages/web/Markup.css";
+
import ThemeDarkIcon from "@moonlight-mod/wp/moonbase_ThemeDarkIcon";
+
+
const strings: Record<UpdateState, string> = {
+
[UpdateState.Ready]: "A new version of moonlight is available.",
+
[UpdateState.Working]: "Updating moonlight...",
+
[UpdateState.Installed]: "Updated. Restart Discord to apply changes.",
+
[UpdateState.Failed]: "Failed to update moonlight. Please use the installer instead."
+
};
+
+
function MoonlightChangelog({
+
changelog,
+
version,
+
transitionState,
+
onClose
+
}: {
+
changelog: string;
+
version: string;
+
transitionState: number | null;
+
onClose: () => void;
+
}) {
+
return (
+
<ModalRoot transitionState={transitionState} size={ModalSize.DYNAMIC}>
+
<ModalHeader>
+
<Flex.Child grow={1} shrink={1}>
+
<Heading variant="heading-lg/semibold">moonlight</Heading>
+
<Text variant="text-xs/normal">{version}</Text>
+
</Flex.Child>
+
+
<Flex.Child grow={0}>
+
<ModalCloseButton onClick={onClose} />
+
</Flex.Child>
+
</ModalHeader>
+
+
<ModalContent>
+
<Text variant="text-md/normal" className={MarkupClasses.markup} style={{ padding: "1rem" }}>
+
{MarkupUtils.parse(changelog, true, {
+
allowHeading: true,
+
allowList: true,
+
allowLinks: true
+
})}
+
</Text>
+
</ModalContent>
+
</ModalRoot>
+
);
+
}
+
+
export default function Update() {
+
const [newVersion, state] = useStateFromStores([MoonbaseSettingsStore], () => [
+
MoonbaseSettingsStore.newVersion,
+
MoonbaseSettingsStore.updateState
+
]);
+
+
if (newVersion == null) return null;
+
+
return (
+
<HelpMessage text={strings[state]} className="moonbase-update-section" icon={ThemeDarkIcon}>
+
<div className="moonbase-help-message-buttons">
+
{moonlight.branch === MoonlightBranch.STABLE && (
+
<Button
+
look={Button.Looks.OUTLINED}
+
color={Button.Colors.CUSTOM}
+
size={Button.Sizes.TINY}
+
onClick={() => {
+
fetch(`https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/tags/${newVersion}/CHANGELOG.md`)
+
.then((r) => r.text())
+
.then((changelog) =>
+
openModal((modalProps) => {
+
return <MoonlightChangelog {...modalProps} changelog={changelog} version={newVersion} />;
+
})
+
);
+
}}
+
>
+
View changelog
+
</Button>
+
)}
+
+
{state === UpdateState.Installed && (
+
<Button
+
look={Button.Looks.OUTLINED}
+
color={Button.Colors.CUSTOM}
+
size={Button.Sizes.TINY}
+
onClick={() => {
+
MoonbaseSettingsStore.restartDiscord();
+
}}
+
>
+
Restart Discord
+
</Button>
+
)}
+
+
<Button
+
look={Button.Looks.OUTLINED}
+
color={Button.Colors.CUSTOM}
+
size={Button.Sizes.TINY}
+
disabled={state !== UpdateState.Ready}
+
onClick={() => {
+
MoonbaseSettingsStore.updateMoonlight();
+
}}
+
>
+
Update
+
</Button>
+
</div>
+
</HelpMessage>
+
);
+
}
+74
packages/core-extensions/src/moonbase/webpackModules/updates.tsx
···
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import Notices from "@moonlight-mod/wp/notices_notices";
+
import { MoonlightBranch } from "@moonlight-mod/types";
+
import React from "@moonlight-mod/wp/react";
+
import ThemeDarkIcon from "@moonlight-mod/wp/moonbase_ThemeDarkIcon";
+
+
function plural(str: string, num: number) {
+
return `${str}${num > 1 ? "s" : ""}`;
+
}
+
+
function listener() {
+
if (
+
MoonbaseSettingsStore.shouldShowNotice &&
+
MoonbaseSettingsStore.getExtensionConfigRaw("moonbase", "updateBanner", true)
+
) {
+
MoonbaseSettingsStore.removeChangeListener(listener);
+
+
const version = MoonbaseSettingsStore.newVersion;
+
const extensionUpdateCount = Object.keys(MoonbaseSettingsStore.updates).length;
+
const hasExtensionUpdates = extensionUpdateCount > 0;
+
+
let message;
+
+
if (version != null) {
+
message =
+
moonlightNode.branch === MoonlightBranch.NIGHTLY
+
? `A new version of moonlight is available`
+
: `moonlight ${version} is available`;
+
}
+
+
if (hasExtensionUpdates) {
+
let concat = false;
+
if (message == null) {
+
message = "";
+
} else {
+
concat = true;
+
message += ", and ";
+
}
+
message += `${extensionUpdateCount} ${concat ? "" : "moonlight "}${plural(
+
"extension",
+
extensionUpdateCount
+
)} can be updated`;
+
}
+
+
if (message != null) message += ".";
+
+
Notices.addNotice({
+
element: (
+
<div className="moonbase-updates-notice_text-wrapper">
+
<ThemeDarkIcon size="sm" color="currentColor" />
+
{message}
+
</div>
+
),
+
color: "moonbase-updates-notice",
+
buttons: [
+
{
+
name: "Open Moonbase",
+
onClick: () => {
+
const { open } = spacepack.require("discord/actions/UserSettingsModalActionCreators").default;
+
if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "sections", false)) {
+
open("moonbase-extensions");
+
} else {
+
open("moonbase", "0");
+
}
+
return true;
+
}
+
}
+
]
+
});
+
}
+
}
+
+
MoonbaseSettingsStore.addChangeListener(listener);
+12
packages/core-extensions/src/moonbase/wp.d.ts
···
+
declare module "@moonlight-mod/wp/moonbase_ui" {
+
export * from "core-extensions/src/moonbase/webpackModules/ui";
+
}
+
+
declare module "@moonlight-mod/wp/moonbase_stores" {
+
export * from "core-extensions/src/moonbase/webpackModules/stores";
+
}
+
+
declare module "@moonlight-mod/wp/moonbase_ThemeDarkIcon" {
+
import ThemeDarkIcon from "core-extensions/src/moonbase/webpackModules/ThemeDarkIcon";
+
export = ThemeDarkIcon;
+
}
+186
packages/core-extensions/src/nativeFixes/host.ts
···
+
import { app, nativeTheme } from "electron";
+
import * as path from "node:path";
+
import * as fs from "node:fs/promises";
+
import * as fsSync from "node:fs";
+
import { parseTarGzip } from "nanotar";
+
+
const logger = moonlightHost.getLogger("nativeFixes/host");
+
const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");
+
+
moonlightHost.events.on("window-created", function (browserWindow) {
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "devtoolsThemeFix") ?? true) {
+
browserWindow.webContents.on("devtools-opened", () => {
+
if (!nativeTheme.shouldUseDarkColors) return;
+
nativeTheme.themeSource = "light";
+
setTimeout(() => {
+
nativeTheme.themeSource = "dark";
+
}, 100);
+
});
+
}
+
});
+
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "disableRendererBackgrounding") ?? true) {
+
// Discord already disables UseEcoQoSForBackgroundProcess and some other
+
// related features
+
app.commandLine.appendSwitch("disable-renderer-backgrounding");
+
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
+
+
// already added on Windows, but not on other operating systems
+
app.commandLine.appendSwitch("disable-background-timer-throttling");
+
}
+
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vulkan") ?? false) {
+
enabledFeatures.push("Vulkan", "DefaultANGLEVulkan", "VulkanFromANGLE");
+
}
+
+
if (process.platform === "linux") {
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxAutoscroll") ?? false) {
+
app.commandLine.appendSwitch("enable-blink-features", "MiddleClickAutoscroll");
+
}
+
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxSpeechDispatcher") ?? true) {
+
app.commandLine.appendSwitch("enable-speech-dispatcher");
+
}
+
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxHevcSupport") ?? true) {
+
enabledFeatures.push("PlatformHEVCDecoderSupport");
+
}
+
}
+
+
// NOTE: Only tested if this appears on Windows, it should appear on all when
+
// hardware acceleration is disabled
+
const noAccel = app.commandLine.hasSwitch("disable-gpu-compositing");
+
if ((moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapi") ?? true) && !noAccel) {
+
if (process.platform === "linux") {
+
// These will eventually be renamed https://source.chromium.org/chromium/chromium/src/+/5482210941a94d70406b8da962426e4faca7fce4
+
enabledFeatures.push("VaapiVideoEncoder", "VaapiVideoDecoder", "VaapiVideoDecodeLinuxGL");
+
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapiIgnoreDriverChecks") ?? false)
+
enabledFeatures.push("VaapiIgnoreDriverChecks");
+
}
+
}
+
+
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].join(","));
+
+
if (process.platform === "linux" && moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxUpdater")) {
+
const exePath = app.getPath("exe");
+
const appName = path.basename(exePath);
+
const targetDir = path.dirname(exePath);
+
const { releaseChannel }: { releaseChannel: string } = JSON.parse(
+
fsSync.readFileSync(path.join(targetDir, "resources", "build_info.json"), "utf8")
+
);
+
+
const updaterModule = require(path.join(moonlightHost.asarPath, "app_bootstrap", "hostUpdater.js"));
+
const updater = updaterModule.constructor;
+
+
async function doUpdate(cb: (percent: number) => void) {
+
logger.debug("Extracting to", targetDir);
+
+
const exists = (path: string) =>
+
fs
+
.stat(path)
+
.then(() => true)
+
.catch(() => false);
+
+
const url = `https://discord.com/api/download/${releaseChannel}?platform=linux&format=tar.gz`;
+
const resp = await fetch(url, {
+
cache: "no-store"
+
});
+
+
const reader = resp.body!.getReader();
+
const contentLength = parseInt(resp.headers.get("Content-Length") ?? "0");
+
logger.info(`Expecting ${contentLength} bytes for the update`);
+
const bytes = new Uint8Array(contentLength);
+
let pos = 0;
+
let lastPercent = 0;
+
+
while (true) {
+
const { done, value } = await reader.read();
+
if (done) {
+
break;
+
} else {
+
bytes.set(value, pos);
+
pos += value.length;
+
+
const newPercent = Math.floor((pos / contentLength) * 100);
+
if (lastPercent !== newPercent) {
+
lastPercent = newPercent;
+
cb(newPercent);
+
}
+
}
+
}
+
+
const files = await parseTarGzip(bytes);
+
+
for (const file of files) {
+
if (!file.data) continue;
+
// @ts-expect-error What do you mean their own types are wrong
+
if (file.type !== "file") continue;
+
+
// Discord update files are inside of a main "Discord(PTB|Canary)" folder
+
const filePath = file.name.replace(`${appName}/`, "");
+
logger.info("Extracting", filePath);
+
+
let targetFilePath = path.join(targetDir, filePath);
+
if (filePath === "resources/app.asar") {
+
// You tried
+
targetFilePath = path.join(targetDir, "resources", "_app.asar");
+
} else if (filePath === appName || filePath === "chrome_crashpad_handler") {
+
// Can't write over the executable? Just move it! 4head
+
if (await exists(targetFilePath)) {
+
await fs.rename(targetFilePath, targetFilePath + ".bak");
+
await fs.unlink(targetFilePath + ".bak");
+
}
+
}
+
const targetFileDir = path.dirname(targetFilePath);
+
+
if (!(await exists(targetFileDir))) await fs.mkdir(targetFileDir, { recursive: true });
+
await fs.writeFile(targetFilePath, file.data);
+
+
const mode = file.attrs?.mode;
+
if (mode != null) {
+
// Not sure why this slice is needed
+
await fs.chmod(targetFilePath, mode.slice(-3));
+
}
+
}
+
+
logger.debug("Done updating");
+
}
+
+
const realEmit = updater.prototype.emit;
+
updater.prototype.emit = function (event: string, ...args: any[]) {
+
// Arrow functions don't bind `this` :D
+
const call = (event: string, ...args: any[]) => realEmit.call(this, event, ...args);
+
+
if (event === "update-manually") {
+
const latestVerStr: string = args[0];
+
logger.debug("update-manually called, intercepting", latestVerStr);
+
call("update-available");
+
+
(async () => {
+
try {
+
await doUpdate((progress) => {
+
call("update-progress", progress);
+
});
+
// Copied from the win32 updater
+
this.updateVersion = latestVerStr;
+
call(
+
"update-downloaded",
+
{},
+
releaseChannel,
+
latestVerStr,
+
new Date(),
+
this.updateUrl,
+
this.quitAndInstall.bind(this)
+
);
+
} catch (e) {
+
logger.error("Error updating", e);
+
}
+
})();
+
+
return this;
+
} else {
+
return realEmit.call(this, event, ...args);
+
}
+
};
+
}
+77
packages/core-extensions/src/nativeFixes/manifest.json
···
+
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
+
"id": "nativeFixes",
+
"meta": {
+
"name": "Native Fixes",
+
"tagline": "Various configurable fixes for Discord and Electron",
+
"authors": ["Cynosphere", "adryd", "NotNite"],
+
"tags": ["fixes"]
+
},
+
"environment": "desktop",
+
"settings": {
+
"devtoolsThemeFix": {
+
"advice": "restart",
+
"displayName": "Devtools Theme Fix",
+
"description": "Temporary workaround for devtools defaulting to light theme on Electron 32",
+
"type": "boolean",
+
"default": true
+
},
+
"disableRendererBackgrounding": {
+
"advice": "restart",
+
"displayName": "Disable Renderer Backgrounding",
+
"description": "This is enabled by default as a power saving measure, but it breaks screensharing and websocket connections fairly often",
+
"type": "boolean",
+
"default": true
+
},
+
"vulkan": {
+
"advice": "restart",
+
"displayName": "Enable Vulkan renderer",
+
"description": "Uses the Vulkan backend for rendering",
+
"type": "boolean",
+
"default": false
+
},
+
"linuxAutoscroll": {
+
"advice": "restart",
+
"displayName": "Enable middle click autoscroll on Linux",
+
"description": "Requires manual configuration of your system to disable middle click paste, has no effect on other operating systems",
+
"type": "boolean",
+
"default": false
+
},
+
"linuxSpeechDispatcher": {
+
"advice": "restart",
+
"displayName": "Enable speech-dispatcher for TTS on Linux",
+
"description": "Fixes text-to-speech. Has no effect on other operating systems",
+
"type": "boolean",
+
"default": true
+
},
+
"vaapi": {
+
"advice": "restart",
+
"displayName": "Enable VAAPI features on Linux",
+
"description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems",
+
"type": "boolean",
+
"default": true
+
},
+
"vaapiIgnoreDriverChecks": {
+
"advice": "restart",
+
"displayName": "Ignore VAAPI driver checks on Linux",
+
"description": "Forces hardware video acceleration on some graphics drivers at the cost of stability. Has no effect on other operating systems",
+
"type": "boolean",
+
"default": false
+
},
+
"linuxUpdater": {
+
"advice": "restart",
+
"displayName": "Linux Updater",
+
"description": "Actually implements updating Discord on Linux. Has no effect on other operating systems",
+
"type": "boolean",
+
"default": false
+
},
+
"linuxHevcSupport": {
+
"advice": "restart",
+
"displayName": "HEVC support on Linux",
+
"description": "You might also need to enable Vulkan renderer. Has no effect on other operating systems",
+
"type": "boolean",
+
"default": true
+
}
+
},
+
"apiLevel": 2
+
}
+4 -4
packages/core-extensions/src/noHideToken/index.ts
···
-
import { Patch } from "types/src";
+
import { Patch } from "@moonlight-mod/types";
export const patches: Patch[] = [
{
-
find: "hideToken(){",
+
find: "hideToken:()=>",
replace: {
-
match: /hideToken\(\)\{.+?},/,
-
replacement: `hideToken(){},`
+
match: /hideToken:\(\)=>.+?,/,
+
replacement: `hideToken:()=>{},`
}
}
];
+4 -1
packages/core-extensions/src/noHideToken/manifest.json
···
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "noHideToken",
+
"apiLevel": 2,
"meta": {
"name": "No Hide Token",
-
"tagline": "Disables removal of token from localStorage when opening dev tools",
+
"tagline": "Prevents you from being logged-out on hard-crash",
+
"description": "Prevents you from being logged-out on hard-crash by disabling removal of token from localStorage when opening dev tools",
"authors": ["adryd"],
"tags": ["dangerZone", "development"]
}
-15
packages/core-extensions/src/noTrack/host.ts
···
-
import { BrowserWindow } from "electron";
-
-
moonlightHost.events.on("window-created", (window: BrowserWindow) => {
-
window.webContents.session.webRequest.onBeforeRequest(
-
{
-
urls: [
-
"https://*.discord.com/api/v*/science",
-
"https://*.discord.com/api/v*/metrics"
-
]
-
},
-
function (details, callback) {
-
callback({ cancel: true });
-
}
-
);
-
});
+3 -3
packages/core-extensions/src/noTrack/index.ts
···
export const patches: Patch[] = [
{
-
find: "analyticsTrackingStoreMaker:function",
+
find: "analyticsTrackingStoreMaker:()=>",
replace: {
-
match: /analyticsTrackingStoreMaker:function\(\){return .}/,
-
replacement: "analyticsTrackingStoreMaker:function(){return ()=>{}}"
+
match: /analyticsTrackingStoreMaker:\(\)=>.+?,/,
+
replacement: "analyticsTrackingStoreMaker:()=>()=>{},"
}
},
{
+9 -1
packages/core-extensions/src/noTrack/manifest.json
···
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "noTrack",
+
"apiLevel": 2,
"meta": {
"name": "No Track",
"tagline": "Disables /api/science and analytics",
"authors": ["Cynosphere", "NotNite"],
"tags": ["privacy"]
-
}
+
},
+
"blocked": [
+
"https://*.discord.com/api/v*/science",
+
"https://*.discord.com/api/v*/metrics",
+
"https://*.discordapp.com/api/v*/science",
+
"https://*.discordapp.com/api/v*/metrics"
+
]
}
+42
packages/core-extensions/src/notices/index.ts
···
+
import type { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
+
+
export const patches: Patch[] = [
+
{
+
find: ".GUILD_RAID_NOTIFICATION:",
+
replace: {
+
match: /(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/,
+
replacement: (orig, createElement) =>
+
`case "__moonlight_notice":return${createElement}(require("notices_component").default,{});${orig}`
+
}
+
},
+
{
+
find: '"NoticeStore"',
+
replace: [
+
{
+
match: /\[.{1,2}\..{1,3}\.CONNECT_SPOTIFY\]:{/,
+
replacement: (orig: string) =>
+
`__moonlight_notice:{predicate:()=>require("notices_notices").default.shouldShowNotice()},${orig}`
+
},
+
{
+
match: /=\[(.{1,2}\..{1,3}\.QUARANTINED,)/g,
+
replacement: (_, orig) => `=["__moonlight_notice",${orig}`
+
}
+
]
+
}
+
];
+
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
+
notices: {
+
dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }]
+
},
+
+
component: {
+
dependencies: [
+
{ id: "react" },
+
{ id: "discord/Dispatcher" },
+
{ id: "discord/components/common/index" },
+
{ id: "discord/packages/flux" },
+
{ ext: "notices", id: "notices" }
+
]
+
}
+
};
+11
packages/core-extensions/src/notices/manifest.json
···
+
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
+
"id": "notices",
+
"apiLevel": 2,
+
"meta": {
+
"name": "Notices",
+
"tagline": "An API for adding notices at the top of the page",
+
"authors": ["Cynosphere", "NotNite"],
+
"tags": ["library"]
+
}
+
}
+50
packages/core-extensions/src/notices/webpackModules/component.tsx
···
+
import React from "@moonlight-mod/wp/react";
+
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
+
import { Notice, NoticeCloseButton, PrimaryCTANoticeButton } from "@moonlight-mod/wp/discord/components/common/index";
+
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
+
import NoticesStore from "@moonlight-mod/wp/notices_notices";
+
import type { Notice as NoticeType } from "@moonlight-mod/types/coreExtensions/notices";
+
+
function popAndDismiss(notice: NoticeType) {
+
NoticesStore.popNotice();
+
if (notice?.onDismiss) {
+
notice.onDismiss();
+
}
+
if (!NoticesStore.shouldShowNotice()) {
+
Dispatcher.dispatch({
+
type: "NOTICE_DISMISS"
+
});
+
}
+
}
+
+
export default function UpdateNotice() {
+
const { notice } = useStateFromStoresObject([NoticesStore], () => ({
+
notice: NoticesStore.getCurrentNotice()
+
}));
+
+
if (notice == null) return <></>;
+
+
return (
+
<Notice color={notice.color}>
+
{notice.element}
+
+
{(notice.showClose ?? true) && (
+
<NoticeCloseButton onClick={() => popAndDismiss(notice)} noticeType="__moonlight_notice" />
+
)}
+
+
{(notice.buttons ?? []).map((button) => (
+
<PrimaryCTANoticeButton
+
key={button.name}
+
onClick={() => {
+
if (button.onClick()) {
+
popAndDismiss(notice);
+
}
+
}}
+
noticeType="__moonlight_notice"
+
>
+
{button.name}
+
</PrimaryCTANoticeButton>
+
))}
+
</Notice>
+
);
+
}
+55
packages/core-extensions/src/notices/webpackModules/notices.ts
···
+
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
+
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
+
import type { Notice, Notices } from "@moonlight-mod/types/coreExtensions/notices";
+
+
// very lazy way of doing this, FIXME
+
let open = false;
+
+
class NoticesStore extends Store<any> {
+
private notices: Notice[] = [];
+
+
constructor() {
+
super(Dispatcher);
+
}
+
+
addNotice(notice: Notice) {
+
this.notices.push(notice);
+
if (open && this.notices.length !== 0) {
+
Dispatcher.dispatch({
+
type: "NOTICE_SHOW",
+
notice: { type: "__moonlight_notice" }
+
});
+
}
+
this.emitChange();
+
}
+
+
popNotice() {
+
this.notices.shift();
+
this.emitChange();
+
}
+
+
getCurrentNotice() {
+
return this.notices.length > 0 ? this.notices[0] : null;
+
}
+
+
shouldShowNotice() {
+
return this.notices.length > 0;
+
}
+
}
+
+
const store: Notices = new NoticesStore();
+
+
function showNotice() {
+
open = true;
+
if (store.shouldShowNotice()) {
+
Dispatcher.dispatch({
+
type: "NOTICE_SHOW",
+
notice: { type: "__moonlight_notice" }
+
});
+
}
+
}
+
+
Dispatcher.subscribe("CONNECTION_OPEN", showNotice);
+
Dispatcher.subscribe("CONNECTION_OPEN_SUPPLEMENTAL", showNotice);
+
+
export default store;
+98 -43
packages/core-extensions/src/quietLoggers/index.ts
···
import { Patch } from "@moonlight-mod/types";
const notXssDefensesOnly = () =>
-
(moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ??
-
false) === false;
+
(moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ?? false) === false;
+
+
const silenceDiscordLogger = moonlight.getConfigOption<boolean>("quietLoggers", "silenceDiscordLogger") ?? false;
// These patches MUST run before the simple patches, these are to remove loggers
// that end up causing syntax errors by the normal patch
const loggerFixes: Patch[] = [
{
-
find: '"./ggsans-800-extrabolditalic.woff2":',
+
find: '"./gg-sans/ggsans-800-extrabolditalic.woff2":',
replace: {
-
match: /\.then\(function\(\){var.+?"MODULE_NOT_FOUND",.\}\)/,
-
replacement: ".then(()=>(()=>{}))"
+
match: /var .=Error.+?;throw .+?,./,
+
replacement: ""
}
},
{
find: '("GatewaySocket")',
replace: {
-
match: /.\.(info|log)(\(.+?\))(;|,)/g,
-
replacement: (_, type, body, trail) => `(()=>{})${body}${trail}`
+
match: /\i\.(log|info)\(/g,
+
replacement: "(()=>{})("
+
}
+
},
+
{
+
find: '"_connect called with already existing websocket"',
+
replace: {
+
match: /\i\.(log|info|verbose)\(/g,
+
replacement: "(()=>{})("
}
}
];
···
// Patches to simply remove a logger call
const stubPatches = [
// "sh" is not a valid locale.
+
["is not a valid locale", /void \i\.error\(""\.concat\(\i," is not a valid locale\."\)\)/g],
+
['"[BUILD INFO] Release Channel: "', /new \i\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?\)\),/],
+
['.APP_NATIVE_CRASH,"Storage"', /console\.log\("AppCrashedFatalReport lastCrash:",\i,\i\);/],
+
['.APP_NATIVE_CRASH,"Storage"', 'void console.log("AppCrashedFatalReport: getLastCrash not supported.")'],
+
['"[NATIVE INFO] ', /new \i\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/],
+
['"Spellchecker"', /\i\.info\("Switching to ".+?"\(unavailable\)"\);?/g],
+
['throw Error("Messages are still loading.");', /console\.warn\("Unsupported Locale",\i\),/],
+
["}_dispatchWithDevtools(", /\i\.totalTime>\i&&\i\.verbose\(.+?\);/],
+
['"NativeDispatchUtils"', /null==\i&&\i\.warn\("Tried getting Dispatch instance before instantiated"\),/],
[
-
"is not a valid locale",
-
/(.)\.error\(""\.concat\((.)," is not a valid locale\."\)\)/g
+
'"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "',
+
/\i\.has\(\i\.type\)&&\i\.log\(.+?\.type\)\),/
],
-
['.displayName="RunningGameStore"', /.\.info\("games",{.+?}\),/],
+
['console.warn("Window state not initialized"', /console\.warn\("Window state not initialized",\i\),/],
+
['.name="MaxListenersExceededWarning",', /(?<=\.length),\i\(\i\)/],
[
-
'"[BUILD INFO] Release Channel: "',
-
/new\(0,.{1,2}\.default\)\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?"\)\),/
+
'"The answer for life the universe and everything is:"',
+
/\i\.info\("The answer for life the universe and everything is:",\i\),/
],
[
-
'.AnalyticEvents.APP_NATIVE_CRASH,"Storage"',
-
/console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/
+
'"isLibdiscoreBlockedDomainsEnabled called but libdiscore is not loaded"',
+
/,\i\.verbose\("isLibdiscoreBlockedDomainsEnabledThisSession: ".concat\(\i\)\)/
],
[
-
'.AnalyticEvents.APP_NATIVE_CRASH,"Storage"',
-
'console.log("AppCrashedFatalReport: getLastCrash not supported.");'
+
'"Unable to determine render window for element"',
+
/console\.warn\("Unable to determine render window for element",\i\),/
],
[
-
'"[NATIVE INFO] ',
-
/new\(0,.{1,2}\.default\)\(\)\.log\("\[NATIVE INFO] .+?\)\),/
+
'"Unable to determine render window for element"',
+
/console\.warn\('Unable to find element constructor "'\.concat\(\i,'" in'\),\i\),/
],
-
['"Spellchecker"', /.\.info\("Switching to ".+?"\(unavailable\)"\);?/g],
[
-
'throw new Error("Messages are still loading.");',
-
/console\.warn\("Unsupported Locale",.\);/
+
'"[PostMessageTransport] Protocol error: event data should be an Array!"',
+
/void console\.warn\("\[PostMessageTransport] Protocol error: event data should be an Array!"\)/
],
-
["_dispatchWithDevtools=", /.\.has\(.\.type\)&&.\.log\(.+?\);/],
-
["_dispatchWithDevtools=", /.\.totalTime>100&&.\.log\(.+?\);0;/],
[
-
'"NativeDispatchUtils"',
-
/null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/
-
],
-
[
-
'Error("Messages are still loading.")',
-
/console\.warn\("Unsupported Locale",.\),/
-
],
-
['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/],
-
[
-
'"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "',
-
/.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/
+
'("ComponentDispatchUtils")',
+
/new \i\.Z\("ComponentDispatchUtils"\)\.warn\("ComponentDispatch\.resubscribe: Resubscribe without existing subscription",\i\),/
]
];
+
const stripLoggers = [
+
'("OverlayRenderStore")',
+
'("FetchBlockedDomain")',
+
'="UserSettingsProtoLastWriteTimes",',
+
'("MessageActionCreators")',
+
'("Routing/Utils")',
+
'("DatabaseManager")',
+
'("KeyboardLayoutMapUtils")',
+
'("ChannelMessages")',
+
'("MessageQueue")',
+
'("RTCLatencyTestManager")',
+
'("OverlayStoreV3")',
+
'("OverlayBridgeStore")',
+
'("AuthenticationStore")',
+
'("ConnectionStore")',
+
'"Dispatched INITIAL_GUILD "',
+
'"handleIdentify called"',
+
'("Spotify")'
+
];
+
const simplePatches = [
// Moment.js deprecation warnings
-
["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"],
-
-
// Zustand related
-
[
-
/console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\)/g,
-
"/*$&*/"
-
]
+
["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"]
] as { [0]: string | RegExp; [1]: string }[];
export const patches: Patch[] = [
{
-
find: ".Messages.XSSDefenses",
+
find: ".Messages.SELF_XSS_HEADER",
replace: {
-
match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/,
+
match: /\(null!=\i&&"0\.0\.0"===\i\.remoteApp\.getVersion\(\)\)/,
replacement: "(true)"
}
},
+
{
+
find: '("ComponentDispatchUtils")',
+
replace: {
+
match:
+
/new \i\.Z\("ComponentDispatchUtils"\)\.warn\("ComponentDispatch\.subscribe: Attempting to add a duplicate listener",\i\)/,
+
replacement: "void 0"
+
},
+
prerequisite: notXssDefensesOnly
+
},
+
// Highlight.js deprecation warnings
+
{
+
find: "Deprecated as of",
+
replace: {
+
match: /console\./g,
+
replacement: "false&&console."
+
},
+
prerequisite: notXssDefensesOnly
+
},
+
// Discord's logger
+
{
+
find: "ฮฃ:",
+
replace: {
+
match: "for",
+
replacement: "return;for"
+
},
+
prerequisite: () => silenceDiscordLogger && notXssDefensesOnly()
+
},
...loggerFixes,
...stubPatches.map((patch) => ({
find: patch[0],
···
replace: {
match: patch[0],
replacement: patch[1]
+
},
+
prerequisite: notXssDefensesOnly
+
})),
+
...stripLoggers.map((find) => ({
+
find,
+
replace: {
+
match: /(\i|this\.logger)\.(log|warn|error|info|verbose)\(/g,
+
replacement: "(()=>{})("
},
prerequisite: notXssDefensesOnly
}))
+10
packages/core-extensions/src/quietLoggers/manifest.json
···
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "quietLoggers",
+
"apiLevel": 2,
"meta": {
"name": "Quiet Loggers",
"tagline": "Quiet errors on startup, and disable unnecesary loggers",
···
},
"settings": {
"xssDefensesOnly": {
+
"advice": "reload",
"displayName": "Only hide self-XSS",
"description": "Only disable self XSS prevention log",
+
"type": "boolean",
+
"default": false
+
},
+
"silenceDiscordLogger": {
+
"advice": "reload",
+
"displayName": "Silence Discord logger",
+
"description": "Hides all messages from Discord's logger (the logs that start with purple text in brackets)",
"type": "boolean",
"default": false
}
+75
packages/core-extensions/src/rocketship/host/permissions.ts
···
+
import type { BrowserWindow } from "electron";
+
+
type PermissionRequestHandler = (
+
webContents: Electron.WebContents,
+
permission: string,
+
callback: (permissionGranted: boolean) => void,
+
details: Electron.PermissionRequestHandlerHandlerDetails
+
) => void;
+
+
type PermissionCheckHandler = (
+
webContents: Electron.WebContents | null,
+
permission: string,
+
requestingOrigin: string,
+
details: Electron.PermissionCheckHandlerHandlerDetails
+
) => boolean;
+
+
moonlightHost.events.on("window-created", (window: BrowserWindow, isMainWindow: boolean) => {
+
if (!isMainWindow) return;
+
const windowSession = window.webContents.session;
+
+
// setPermissionRequestHandler
+
windowSession.setPermissionRequestHandler((webcontents, permission, callback, details) => {
+
let cbResult = false;
+
function fakeCallback(result: boolean) {
+
cbResult = result;
+
}
+
+
if (caughtPermissionRequestHandler) {
+
caughtPermissionRequestHandler(webcontents, permission, fakeCallback, details);
+
}
+
+
if (permission === "media" || permission === "display-capture") {
+
cbResult = true;
+
}
+
+
callback(cbResult);
+
});
+
+
let caughtPermissionRequestHandler: PermissionRequestHandler | undefined;
+
+
windowSession.setPermissionRequestHandler = function catchSetPermissionRequestHandler(
+
handler: (
+
webcontents: Electron.WebContents,
+
permission: string,
+
callback: (permissionGranted: boolean) => void
+
) => void
+
) {
+
caughtPermissionRequestHandler = handler;
+
};
+
+
// setPermissionCheckHandler
+
windowSession.setPermissionCheckHandler((webcontents, permission, requestingOrigin, details) => {
+
return false;
+
});
+
+
let caughtPermissionCheckHandler: PermissionCheckHandler | undefined;
+
+
windowSession.setPermissionCheckHandler((webcontents, permission, requestingOrigin, details) => {
+
let result = false;
+
+
if (caughtPermissionCheckHandler) {
+
result = caughtPermissionCheckHandler(webcontents, permission, requestingOrigin, details);
+
}
+
+
if (permission === "media" || permission === "display-capture") {
+
result = true;
+
}
+
+
return result;
+
});
+
+
windowSession.setPermissionCheckHandler = function catchSetPermissionCheckHandler(handler: PermissionCheckHandler) {
+
caughtPermissionCheckHandler = handler;
+
};
+
});
+27
packages/core-extensions/src/rocketship/host/types.ts
···
+
// https://github.com/Vencord/venmic/blob/d737ef33eaae7a73d03ec02673e008cf0243434d/lib/module.d.ts
+
type DefaultProps = "node.name" | "application.name";
+
+
type LiteralUnion<LiteralType, BaseType extends string> = LiteralType | (BaseType & Record<never, never>);
+
+
type Optional<Type, Key extends keyof Type> = Partial<Pick<Type, Key>> & Omit<Type, Key>;
+
+
export type Node<T extends string = never> = Record<LiteralUnion<T, string>, string>;
+
+
export interface LinkData {
+
include: Node[];
+
exclude: Node[];
+
+
ignore_devices?: boolean;
+
+
only_speakers?: boolean;
+
only_default_speakers?: boolean;
+
+
workaround?: Node[];
+
}
+
+
export interface PatchBay {
+
unlink(): void;
+
+
list<T extends string = DefaultProps>(props?: T[]): Node<T>[];
+
link(data: Optional<LinkData, "exclude"> | Optional<LinkData, "include">): boolean;
+
}
+69
packages/core-extensions/src/rocketship/host/venmic.ts
···
+
import type { BrowserWindow } from "electron";
+
import { app, desktopCapturer } from "electron";
+
import path from "node:path";
+
import { type PatchBay } from "./types";
+
+
const logger = moonlightHost.getLogger("rocketship");
+
+
function getPatchbay() {
+
try {
+
const venmic = require(path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node")) as {
+
PatchBay: new () => PatchBay;
+
};
+
const patchbay = new venmic.PatchBay();
+
return patchbay;
+
} catch (error) {
+
logger.error("Failed to load venmic.node:", error);
+
return null;
+
}
+
}
+
+
const patchbay = getPatchbay();
+
+
// TODO: figure out how to map source to window with venmic
+
function linkVenmic() {
+
if (patchbay == null) return false;
+
+
try {
+
const pid =
+
app
+
.getAppMetrics()
+
.find((proc) => proc.name === "Audio Service")
+
?.pid?.toString() ?? "";
+
+
logger.info("Audio Service PID:", pid);
+
+
patchbay.unlink();
+
return patchbay.link({
+
exclude: [{ "application.process.id": pid }, { "media.class": "Stream/Input/Audio" }],
+
ignore_devices: true,
+
only_speakers: true,
+
only_default_speakers: true
+
});
+
} catch (error) {
+
logger.error("Failed to link venmic:", error);
+
return false;
+
}
+
}
+
+
moonlightHost.events.on("window-created", (window: BrowserWindow, isMainWindow: boolean) => {
+
if (!isMainWindow) return;
+
const windowSession = window.webContents.session;
+
+
// @ts-expect-error these types ancient
+
windowSession.setDisplayMediaRequestHandler(
+
(request: any, callback: any) => {
+
const linked = linkVenmic();
+
desktopCapturer.getSources({ types: ["screen", "window"] }).then((sources) => {
+
//logger.debug("desktopCapturer.getSources", sources);
+
logger.debug("Linked to venmic:", linked);
+
+
callback({
+
video: sources[0],
+
audio: "loopback"
+
});
+
});
+
},
+
{ useSystemPicker: true }
+
);
+
});
+2
packages/core-extensions/src/rocketship/host.ts
···
+
import "./host/permissions";
+
import "./host/venmic";
+124
packages/core-extensions/src/rocketship/index.ts
···
+
import { Patch } from "@moonlight-mod/types";
+
+
const logger = moonlight.getLogger("rocketship");
+
const getDisplayMediaOrig = navigator.mediaDevices.getDisplayMedia;
+
+
async function getVenmicStream() {
+
try {
+
const devices = await navigator.mediaDevices.enumerateDevices();
+
logger.debug("Devices:", devices);
+
+
// This isn't vencord :(
+
const id = devices.find((device) => device.label === "vencord-screen-share")?.deviceId;
+
if (!id) return null;
+
logger.debug("Got venmic device ID:", id);
+
+
const stream = await navigator.mediaDevices.getUserMedia({
+
audio: {
+
deviceId: {
+
exact: id
+
},
+
autoGainControl: false,
+
echoCancellation: false,
+
noiseSuppression: false
+
}
+
});
+
+
return stream.getAudioTracks();
+
} catch (error) {
+
logger.warn("Failed to get venmic stream:", error);
+
return null;
+
}
+
}
+
+
navigator.mediaDevices.getDisplayMedia = async function getDisplayMediaRedirect(options) {
+
const orig = await getDisplayMediaOrig.call(this, options);
+
+
const venmic = await getVenmicStream();
+
logger.debug("venmic", venmic);
+
if (venmic != null) {
+
// venmic will be proxying all audio, so we need to remove the original
+
// tracks to not cause overlap
+
for (const track of orig.getAudioTracks()) {
+
orig.removeTrack(track);
+
}
+
+
for (const track of venmic) {
+
orig.addTrack(track);
+
}
+
}
+
+
return orig;
+
};
+
+
export const patches: Patch[] = [
+
// "Ensure discord_voice is happy"
+
{
+
find: "RustAudioDeviceModule",
+
replace: [
+
{
+
match: /static supported\(\)\{.+?\}/,
+
replacement: "static supported(){return true}"
+
},
+
{
+
match: "supported(){return!0}",
+
replacement: "supported(){return true}"
+
}
+
]
+
},
+
// Remove Native media engine from list of choices
+
{
+
find: '.CAMERA_BACKGROUND_LIVE="cameraBackgroundLive"',
+
replace: {
+
match: /.\..{1,2}\.NATIVE,/,
+
replacement: ""
+
}
+
},
+
// Stub out browser checks to allow us to use WebRTC voice on Embedded
+
{
+
find: "Using Unified Plan (",
+
replace: {
+
match: /return .\..{1,2}\?\((.)\.info/,
+
replacement: (_, logger) => `return true?(${logger}.info`
+
}
+
},
+
{
+
find: '"UnifiedConnection("',
+
replace: {
+
match: /this\.videoSupported=.\..{1,2};/,
+
replacement: "this.videoSupported=true;"
+
}
+
},
+
{
+
find: "OculusBrowser",
+
replace: [
+
{
+
match: /"Firefox"===(.)\(\)\.name/g,
+
replacement: (orig, info) => `true||${orig}`
+
}
+
]
+
},
+
{
+
find: ".getMediaEngine().getDesktopSource",
+
replace: {
+
match: /.\.isPlatformEmbedded/,
+
replacement: "false"
+
}
+
},
+
{
+
// Matching MediaEngineStore
+
find: '"displayName","MediaEngineStore")',
+
replace: [
+
// Prevent loading of krisp native module by stubbing out desktop checks
+
{
+
match: /\(\(0,.\.isWindows\)\(\)\|\|\(0,.\.isLinux\)\(\)\|\|.+?&&!__OVERLAY__/,
+
replacement: (orig, macosPlatformCheck) => `false&&!__OVERLAY__`
+
},
+
// Enable loading of web krisp equivelant by replacing isWeb with true
+
{
+
match: /\(0,.\.isWeb\)\(\)&&(.{1,2}\.supports\(.{1,2}\..{1,2}.NOISE_CANCELLATION)/,
+
replacement: (orig, supportsNoiseCancellation) => `true&&${supportsNoiseCancellation}`
+
}
+
]
+
}
+
];
+13
packages/core-extensions/src/rocketship/manifest.json
···
+
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
+
"id": "rocketship",
+
"apiLevel": 2,
+
"environment": "desktop",
+
"meta": {
+
"name": "Rocketship",
+
"tagline": "Adds new features when using rocketship",
+
"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.",
+
"authors": ["NotNite", "Cynosphere", "adryd"],
+
"deprecated": true
+
}
+
}
+6 -8
packages/core-extensions/src/settings/index.ts
···
export const patches: Patch[] = [
{
-
find: ".UserSettingsSections.HOTSPOT_OPTIONS",
+
find: '"useGenerateUserSettingsSections"',
replace: {
-
match: /\.CUSTOM,element:(.+?)}\];return (.{1,2})/,
-
replacement: (_, lastElement, sections) =>
-
`.CUSTOM,element:${lastElement}}];return require("settings_settings").Settings._mutateSections(${sections})`
+
match: /(?<=\.push\(.+?\)}\)\)}\),)(.+?)}/,
+
replacement: (_, sections: string) => `require("settings_settings").Settings._mutateSections(${sections})}`
}
},
{
find: 'navId:"user-settings-cog",',
replace: {
-
match: /children:\[(.)\.map\(.+?\),children:.\((.)\)/,
+
match: /children:\[(\i)\.map\(.+?\),.*?children:\i\((\i)\)/,
replacement: (orig, sections, section) =>
`${orig.replace(
-
/Object\.values\(.\.UserSettingsSections\)/,
-
(orig) =>
-
`[...require("settings_settings").Settings.sectionNames,...${orig}]`
+
/Object\.values\(.\..+?\)/,
+
(orig) => `[...require("settings_settings").Settings.sectionNames,...${orig}]`
)}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()`
}
}
+2
packages/core-extensions/src/settings/manifest.json
···
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "settings",
+
"apiLevel": 2,
"meta": {
"name": "Settings",
"tagline": "An API for adding to Discord's settings menu",
+18 -12
packages/core-extensions/src/settings/webpackModules/settings.ts
···
-
import {
-
SettingsSection,
-
Settings as SettingsType
-
} from "@moonlight-mod/types/coreExtensions";
+
import { SettingsSection, Settings as SettingsType } from "@moonlight-mod/types/coreExtensions/settings";
+
import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators";
export const Settings: SettingsType = {
ourSections: [],
sectionNames: [],
+
sectionMenuItems: {},
-
addSection: (section, label, element, color = null, pos, notice) => {
+
addSection: (section, label, element, color = null, pos, notice, onClick) => {
const data: SettingsSection = {
section,
label,
color,
element,
pos: pos ?? -4,
-
notice: notice
+
notice: notice,
+
onClick: onClick ?? (() => UserSettingsModalActionCreators.open(section))
};
Settings.ourSections.push(data);
-
Settings.sectionNames.push(label);
+
Settings.sectionNames.push(section);
return data;
},
+
addSectionMenuItems(section, ...newItems) {
+
const data = Settings.ourSections.find((x) => x.section === section);
+
if (!data || !("element" in data)) throw new Error(`Could not find section "${section}"`);
+
(Settings.sectionMenuItems[section] ??= []).push(...newItems);
+
data._moonlight_submenu ??= () => Settings.sectionMenuItems[section];
+
},
addDivider: (pos = null) => {
Settings.ourSections.push({
···
_mutateSections: (sections) => {
for (const section of Settings.ourSections) {
-
sections.splice(
-
section.pos < 0 ? sections.length + section.pos : section.pos,
-
0,
-
section
-
);
+
// Discord's `pos` only supports numbers, so lets call the function to get the position.
+
if (typeof section.pos === "function") {
+
section.pos = section.pos(sections);
+
}
+
sections.splice(section.pos < 0 ? sections.length + section.pos : section.pos, 0, section);
}
return sections;
+1 -1
packages/core-extensions/src/spacepack/index.ts
···
import { ExtensionWebExports } from "@moonlight-mod/types";
-
import { Spacepack } from "@moonlight-mod/types/coreExtensions";
+
import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack";
declare global {
interface Window {
+3
packages/core-extensions/src/spacepack/manifest.json
···
{
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "spacepack",
+
"apiLevel": 2,
"meta": {
"name": "Spacepack",
"tagline": "Search utilities across all Webpack modules",
···
},
"settings": {
"addToGlobalScope": {
+
"advice": "reload",
"displayName": "Add to global scope",
"description": "Populates window.spacepack for easier usage in DevTools",
"type": "boolean",
+110 -52
packages/core-extensions/src/spacepack/webpackModules/spacepack.ts
···
-
import {
-
WebpackModule,
-
WebpackModuleFunc,
-
WebpackRequireType
-
} from "@moonlight-mod/types";
-
import { Spacepack } from "@moonlight-mod/types/coreExtensions";
+
import { WebpackModule, WebpackModuleFunc, WebpackRequireType } from "@moonlight-mod/types";
+
import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack";
+
import { processFind, testFind } from "@moonlight-mod/core/util/patch";
const webpackRequire = require as unknown as WebpackRequireType;
const cache = webpackRequire.c;
···
module = module.toString();
}
+
if (module in moonlight.moonmap.modules) {
+
module = moonlight.moonmap.modules[module];
+
}
+
if (!(module in modules)) {
return null;
}
···
"module",
"exports",
"require",
-
`(${funcStr}).apply(this, arguments)\n` +
-
`//# sourceURL=Webpack-Module-${module}`
+
`(${funcStr}).apply(this, arguments)\n` + `//# sourceURL=Webpack-Module/${module.slice(0, 3)}/${module}`
) as WebpackModuleFunc;
},
findByCode: (...args: (string | RegExp)[]) => {
-
return Object.entries(modules)
-
.filter(
-
([id, mod]) =>
-
!args.some(
-
(item) =>
-
!(item instanceof RegExp
-
? item.test(mod.toString())
-
: mod.toString().indexOf(item) !== -1)
-
)
-
)
+
const ret = Object.entries(modules)
+
.filter(([id, mod]) => !args.some((item) => !testFind(mod.toString(), processFind(item))))
.map(([id]) => {
//if (!(id in cache)) require(id);
//return cache[id];
···
try {
exports = require(id);
} catch (e) {
-
logger.error(`Error requiring module "${id}": `, e);
+
logger.error(`findByCode: Error requiring module "${id}": `, args, e);
}
return {
···
};
})
.filter((item) => item !== null);
+
+
if (ret.length === 0) {
+
logger.warn("findByCode: Got zero results for", args, new Error().stack!.substring(5));
+
}
+
+
return ret;
},
findByExports: (...args: string[]) => {
···
!(
exports !== undefined &&
exports !== window &&
-
(exports?.[item] ||
-
exports?.default?.[item] ||
-
exports?.Z?.[item] ||
-
exports?.ZP?.[item])
+
(exports?.[item] || exports?.default?.[item] || exports?.Z?.[item] || exports?.ZP?.[item])
)
)
)
···
},
findObjectFromKey: (exports: Record<string, any>, key: string) => {
+
let ret = null;
let subKey;
if (key.indexOf(".") > -1) {
const splitKey = key.split(".");
···
const obj = exports[exportKey];
if (obj && obj[key] !== undefined) {
if (subKey) {
-
if (obj[key][subKey]) return obj;
+
if (obj[key][subKey]) {
+
ret = obj;
+
break;
+
}
} else {
-
return obj;
+
ret = obj;
+
break;
}
}
}
-
return null;
+
+
if (ret == null) {
+
logger.warn("Failed to find object by key", key, "in", exports, new Error().stack!.substring(5));
+
}
+
+
return ret;
},
findObjectFromValue: (exports: Record<string, any>, value: any) => {
+
let ret = null;
for (const exportKey in exports) {
const obj = exports[exportKey];
// eslint-disable-next-line eqeqeq
-
if (obj == value) return obj;
+
if (obj == value) {
+
ret = obj;
+
break;
+
}
for (const subKey in obj) {
// eslint-disable-next-line eqeqeq
if (obj && obj[subKey] == value) {
-
return obj;
+
ret = obj;
+
break;
}
}
}
-
return null;
+
+
if (ret == null) {
+
logger.warn("Failed to find object by value", value, "in", exports, new Error().stack!.substring(5));
+
}
+
+
return ret;
},
-
findObjectFromKeyValuePair: (
-
exports: Record<string, any>,
-
key: string,
-
value: any
-
) => {
+
findObjectFromKeyValuePair: (exports: Record<string, any>, key: string, value: any) => {
+
let ret = null;
for (const exportKey in exports) {
const obj = exports[exportKey];
// eslint-disable-next-line eqeqeq
if (obj && obj[key] == value) {
-
return obj;
+
ret = obj;
+
break;
}
}
+
+
if (ret == null) {
+
logger.warn(
+
"Failed to find object by key value pair",
+
key,
+
value,
+
"in",
+
exports,
+
new Error().stack!.substring(5)
+
);
+
}
+
return null;
},
-
findFunctionByStrings: (
-
exports: Record<string, any>,
-
...strings: (string | RegExp)[]
-
) => {
-
return (
+
findFunctionByStrings: (exports: Record<string, any>, ...strings: (string | RegExp)[]) => {
+
const ret =
Object.entries(exports).filter(
([index, func]) =>
-
typeof func === "function" &&
-
!strings.some(
-
(query) =>
-
!(query instanceof RegExp
-
? func.toString().match(query)
-
: func.toString().includes(query))
-
)
-
)?.[0]?.[1] ?? null
-
);
+
typeof func === "function" && !strings.some((query) => !testFind(func.toString(), processFind(query)))
+
)?.[0]?.[1] ?? null;
+
+
if (ret == null) {
+
logger.warn("Failed to find function by strings", strings, "in", exports, new Error().stack!.substring(5));
+
}
+
+
return ret;
+
},
+
+
lazyLoad: (find: string | RegExp | (string | RegExp)[], chunk: RegExp, module: RegExp) => {
+
chunk = processFind(chunk);
+
module = processFind(module);
+
+
const mod = Array.isArray(find) ? spacepack.findByCode(...find) : spacepack.findByCode(find);
+
if (mod.length < 1) {
+
logger.warn("lazyLoad: Module find failed", find, chunk, module, new Error().stack!.substring(5));
+
return Promise.reject("Module find failed");
+
}
+
+
const findId = mod[0].id;
+
const findCode = webpackRequire.m[findId].toString().replace(/\n/g, "");
+
+
let chunkIds;
+
if (chunk.flags.includes("g")) {
+
chunkIds = [...findCode.matchAll(chunk)].map(([, id]) => id);
+
} else {
+
const match = findCode.match(chunk);
+
if (match) chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id);
+
}
+
+
if (!chunkIds || chunkIds.length === 0) {
+
logger.warn("lazyLoad: Chunk ID match failed", find, chunk, module, new Error().stack!.substring(5));
+
return Promise.reject("Chunk ID match failed");
+
}
+
+
const moduleId = findCode.match(module)?.[1];
+
if (!moduleId) {
+
logger.warn("lazyLoad: Module ID match failed", find, chunk, module, new Error().stack!.substring(5));
+
return Promise.reject("Module ID match failed");
+
}
+
+
return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() => webpackRequire(moduleId));
+
},
+
+
filterReal: (modules: WebpackModule[]) => {
+
return modules.filter((module) => module.id.toString().match(/^\d+$/));
}
};
-
if (
-
moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true
-
) {
+
if (moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true) {
window.spacepack = spacepack;
}
+4 -1
packages/core-extensions/tsconfig.json
···
{
-
"extends": "../../tsconfig.json"
+
"extends": "../../tsconfig.json",
+
"compilerOptions": {
+
"lib": ["ESNext", "DOM", "DOM.Iterable"]
+
}
}
+10 -3
packages/injector/package.json
···
{
"name": "@moonlight-mod/injector",
"private": true,
+
"engines": {
+
"node": ">=22",
+
"pnpm": ">=10",
+
"npm": "pnpm",
+
"yarn": "pnpm"
+
},
"dependencies": {
-
"@moonlight-mod/types": "workspace:*",
-
"@moonlight-mod/core": "workspace:*"
-
}
+
"@moonlight-mod/core": "workspace:*",
+
"@moonlight-mod/types": "workspace:*"
+
},
+
"engineStrict": true
}
+194 -56
packages/injector/src/index.ts
···
ipcMain,
app
} from "electron";
-
import Module from "module";
-
import { constants } from "@moonlight-mod/types";
-
import { readConfig } from "@moonlight-mod/core/config";
+
import Module from "node:module";
+
import { constants, MoonlightBranch } from "@moonlight-mod/types";
+
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
import { getExtensions } from "@moonlight-mod/core/extension";
-
import Logger from "@moonlight-mod/core/util/logger";
-
import {
-
loadExtensions,
-
loadProcessedExtensions
-
} from "@moonlight-mod/core/extension/loader";
-
import EventEmitter from "events";
+
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
+
import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
+
import EventEmitter from "node:events";
+
import path from "node:path";
+
import persist from "@moonlight-mod/core/persist";
+
import createFS from "@moonlight-mod/core/fs";
+
import { getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
+
import { getConfigPath, getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data";
const logger = new Logger("injector");
-
let oldPreloadPath = "";
+
let oldPreloadPath: string | undefined;
let corsAllow: string[] = [];
+
let blockedUrls: RegExp[] = [];
+
let injectorConfig: InjectorConfig | undefined;
+
+
const scriptUrls = ["web.", "sentry."];
+
const blockedScripts = new Set<string>();
ipcMain.on(constants.ipcGetOldPreloadPath, (e) => {
e.returnValue = oldPreloadPath;
});
+
ipcMain.on(constants.ipcGetAppData, (e) => {
e.returnValue = app.getPath("appData");
});
+
ipcMain.on(constants.ipcGetInjectorConfig, (e) => {
+
e.returnValue = injectorConfig;
+
});
ipcMain.handle(constants.ipcMessageBox, (_, opts) => {
electron.dialog.showMessageBoxSync(opts);
});
···
corsAllow = list;
});
-
function patchCsp(headers: Record<string, string[]>) {
-
const directives = [
-
"style-src",
-
"connect-src",
-
"img-src",
-
"font-src",
-
"media-src",
-
"worker-src",
-
"prefetch-src"
-
];
-
const values = ["*", "blob:", "data:", "'unsafe-inline'", "disclip:"];
+
const reEscapeRegExp = /[\\^$.*+?()[\]{}|]/g;
+
const reMatchPattern = /^(?<scheme>\*|[a-z][a-z0-9+.-]*):\/\/(?<host>.+?)\/(?<path>.+)?$/;
+
+
const escapeRegExp = (s: string) => s.replace(reEscapeRegExp, "\\$&");
+
ipcMain.handle(constants.ipcSetBlockedList, (_, list: string[]) => {
+
// We compile the patterns into a RegExp based on a janky match pattern-like syntax
+
const compiled = list
+
.map((pattern) => {
+
const match = pattern.match(reMatchPattern);
+
if (!match?.groups) return;
+
+
let regex = "";
+
if (match.groups.scheme === "*") regex += ".+?";
+
else regex += escapeRegExp(match.groups.scheme);
+
regex += ":\\/\\/";
+
+
const parts = match.groups.host.split(".");
+
if (parts[0] === "*") {
+
parts.shift();
+
regex += "(?:.+?\\.)?";
+
}
+
regex += escapeRegExp(parts.join("."));
+
+
regex += "\\/" + escapeRegExp(match.groups.path).replace("\\*", ".*?");
+
+
return new RegExp("^" + regex + "$");
+
})
+
.filter(Boolean) as RegExp[];
+
+
blockedUrls = compiled;
+
});
+
+
function patchCsp(headers: Record<string, string[]>, extensionCspOverrides: Record<string, string[]>) {
+
const directives = ["script-src", "style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"];
+
const values = ["*", "blob:", "data:", "'unsafe-inline'", "'unsafe-eval'", "disclip:"];
const csp = "content-security-policy";
if (headers[csp] == null) return;
···
parts[directive] = values;
}
+
for (const [directive, urls] of Object.entries(extensionCspOverrides)) {
+
parts[directive] ??= [];
+
parts[directive].push(...urls);
+
}
+
const stringified = Object.entries<string[]>(parts)
.map(([key, value]) => {
return `${key} ${value.join(" ")}`;
···
class BrowserWindow extends ElectronBrowserWindow {
constructor(opts: BrowserWindowConstructorOptions) {
-
oldPreloadPath = opts.webPreferences!.preload!;
-
opts.webPreferences!.preload = require.resolve("./node-preload.js");
+
const isMainWindow = opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1;
+
+
if (isMainWindow) {
+
if (!oldPreloadPath) oldPreloadPath = opts.webPreferences!.preload;
+
opts.webPreferences!.preload = require.resolve("./node-preload.js");
+
}
+
+
// Event for modifying window options
+
moonlightHost.events.emit("window-options", opts, isMainWindow);
-
moonlightHost.events.emit("window-options", opts);
super(opts);
-
moonlightHost.events.emit("window-created", this);
+
+
// Event for when a window is created
+
moonlightHost.events.emit("window-created", this, isMainWindow);
+
+
const extensionCspOverrides: Record<string, string[]> = {};
+
+
{
+
const extCsps = moonlightHost.processedExtensions.extensions.map((x) => x.manifest.csp ?? {});
+
for (const csp of extCsps) {
+
for (const [directive, urls] of Object.entries(csp)) {
+
extensionCspOverrides[directive] ??= [];
+
extensionCspOverrides[directive].push(...urls);
+
}
+
}
+
}
this.webContents.session.webRequest.onHeadersReceived((details, cb) => {
if (details.responseHeaders != null) {
+
// Patch CSP so things can use externally hosted assets
if (details.resourceType === "mainFrame") {
-
patchCsp(details.responseHeaders);
+
patchCsp(details.responseHeaders, extensionCspOverrides);
}
+
// Allow plugins to bypass CORS for specific URLs
if (corsAllow.some((x) => details.url.startsWith(x))) {
-
details.responseHeaders["access-control-allow-origin"] = ["*"];
+
if (!details.responseHeaders) details.responseHeaders = {};
+
+
// Work around HTTP header case sensitivity by reusing the header name if it exists
+
// https://github.com/moonlight-mod/moonlight/issues/201
+
const fallback = "access-control-allow-origin";
+
const key = Object.keys(details.responseHeaders).find((h) => h.toLowerCase() === fallback) ?? fallback;
+
details.responseHeaders[key] = ["*"];
}
+
moonlightHost.events.emit("headers-received", details, isMainWindow);
+
cb({ cancel: false, responseHeaders: details.responseHeaders });
}
});
+
+
this.webContents.session.webRequest.onBeforeRequest((details, cb) => {
+
/*
+
In order to get moonlight loading to be truly async, we prevent Discord
+
from loading their scripts immediately. We block the requests, keep note
+
of their URLs, and then send them off to node-preload when we get all of
+
them. node-preload then loads node side, web side, and then recreates
+
the script elements to cause them to re-fetch.
+
+
The browser extension also does this, but in a background script (see
+
packages/browser/src/background.js - we should probably get this working
+
with esbuild someday).
+
*/
+
if (details.resourceType === "script" && isMainWindow) {
+
const url = new URL(details.url);
+
const hasUrl = scriptUrls.some((scriptUrl) => {
+
return (
+
details.url.includes(scriptUrl) &&
+
!url.searchParams.has("inj") &&
+
(url.host.endsWith("discord.com") || url.host.endsWith("discordapp.com"))
+
);
+
});
+
if (hasUrl) blockedScripts.add(details.url);
+
+
if (blockedScripts.size === scriptUrls.length) {
+
setTimeout(() => {
+
logger.debug("Kicking off node-preload");
+
this.webContents.send(constants.ipcNodePreloadKickoff, Array.from(blockedScripts));
+
blockedScripts.clear();
+
}, 0);
+
}
+
+
if (hasUrl) return cb({ cancel: true });
+
}
+
+
// Allow plugins to block some URLs,
+
// this is needed because multiple webRequest handlers cannot be registered at once
+
cb({ cancel: blockedUrls.some((u) => u.test(details.url)) });
+
});
}
}
···
value: "BrowserWindow",
writable: false
});
-
// "aight i'm writing exclusively C# from now on and never touching JavaScript again"
+
+
type InjectorConfig = { disablePersist?: boolean; disableLoad?: boolean };
+
export async function inject(asarPath: string, _injectorConfig?: InjectorConfig) {
+
injectorConfig = _injectorConfig;
+
+
global.moonlightNodeSandboxed = {
+
fs: createFS(),
+
// These aren't supposed to be used from host
+
addCors() {},
+
addBlocked() {}
+
};
-
export async function inject(asarPath: string) {
try {
-
const config = readConfig();
-
const extensions = getExtensions();
+
let config = await readConfig();
+
initLogger(config);
+
const extensions = await getExtensions();
+
const processedExtensions = await loadExtensions(extensions);
+
const moonlightDir = await getMoonlightDir();
+
const extensionsPath = await getExtensionsPath();
// Duplicated in node-preload... oops
-
// eslint-disable-next-line no-inner-declarations
function getConfig(ext: string) {
const val = config.extensions[ext];
if (val == null || typeof val === "boolean") return undefined;
return val.config;
}
-
global.moonlightHost = {
+
get config() {
+
return config;
+
},
+
extensions,
+
processedExtensions,
asarPath,
-
config,
events: new EventEmitter(),
-
extensions,
-
processedExtensions: {
-
extensions: [],
-
dependencyGraph: new Map()
-
},
+
+
version: MOONLIGHT_VERSION,
+
branch: MOONLIGHT_BRANCH as MoonlightBranch,
getConfig,
-
getConfigOption: <T>(ext: string, name: string) => {
-
const config = getConfig(ext);
-
if (config == null) return undefined;
-
const option = config[name];
-
if (option == null) return undefined;
-
return option as T;
+
getConfigPath,
+
getConfigOption(ext, name) {
+
const manifest = getManifest(extensions, ext);
+
return getConfigOption(ext, name, config, manifest?.settings);
+
},
+
setConfigOption(ext, name, value) {
+
setConfigOption(config, ext, name, value);
+
this.writeConfig(config);
+
},
+
async writeConfig(newConfig) {
+
await writeConfig(newConfig);
+
config = newConfig;
},
-
getLogger: (id: string) => {
+
+
getLogger(id) {
return new Logger(id);
+
},
+
getMoonlightDir() {
+
return moonlightDir;
+
},
+
getExtensionDir: (ext: string) => {
+
return path.join(extensionsPath, ext);
}
};
patchElectron();
-
global.moonlightHost.processedExtensions = await loadExtensions(extensions);
await loadProcessedExtensions(global.moonlightHost.processedExtensions);
-
} catch (e) {
-
logger.error("Failed to inject", e);
+
} catch (error) {
+
logger.error("Failed to inject:", error);
+
}
+
+
if (injectorConfig?.disablePersist !== true) {
+
persist(asarPath);
}
-
require(asarPath);
+
if (injectorConfig?.disableLoad !== true) {
+
// Need to do this instead of require() or it breaks require.main
+
// @ts-expect-error Module internals
+
Module._load(asarPath, Module, true);
+
}
}
function patchElectron() {
···
configurable: false
});
} else {
-
Object.defineProperty(
-
electronClone,
-
property,
-
Object.getOwnPropertyDescriptor(electron, property)!
-
);
+
Object.defineProperty(electronClone, property, Object.getOwnPropertyDescriptor(electron, property)!);
}
}
-
// exports is a getter only on Windows, let's do some cursed shit instead
+
// exports is a getter only on Windows, recreate export cache instead
const electronPath = require.resolve("electron");
const cachedElectron = require.cache[electronPath]!;
require.cache[electronPath] = new Module(cachedElectron.id, require.main);
+8 -1
packages/node-preload/package.json
···
{
"name": "@moonlight-mod/node-preload",
"private": true,
+
"engines": {
+
"node": ">=22",
+
"pnpm": ">=10",
+
"npm": "pnpm",
+
"yarn": "pnpm"
+
},
"dependencies": {
"@moonlight-mod/core": "workspace:*",
"@moonlight-mod/types": "workspace:*"
-
}
+
},
+
"engineStrict": true
}
+142 -45
packages/node-preload/src/index.ts
···
import { webFrame, ipcRenderer, contextBridge } from "electron";
-
import fs from "fs";
-
import path from "path";
+
import fs from "node:fs";
+
import path from "node:path";
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
-
import { constants } from "@moonlight-mod/types";
+
import { constants, MoonlightBranch } from "@moonlight-mod/types";
import { getExtensions } from "@moonlight-mod/core/extension";
-
import { getExtensionsPath } from "@moonlight-mod/core/util/data";
-
import Logger from "@moonlight-mod/core/util/logger";
-
import {
-
loadExtensions,
-
loadProcessedExtensions
-
} from "@moonlight-mod/core/extension/loader";
+
import { getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data";
+
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
+
import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
+
import createFS from "@moonlight-mod/core/fs";
+
import { registerCors, registerBlocked, getDynamicCors } from "@moonlight-mod/core/cors";
+
import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
+
import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event";
+
import { createEventEmitter } from "@moonlight-mod/core/util/event";
+
+
let initialized = false;
+
let logger: Logger;
+
+
function setCors() {
+
const data = getDynamicCors();
+
ipcRenderer.invoke(constants.ipcSetCorsList, data.cors);
+
ipcRenderer.invoke(constants.ipcSetBlockedList, data.blocked);
+
}
async function injectGlobals() {
-
const config = readConfig();
-
const extensions = getExtensions();
-
const processed = await loadExtensions(extensions);
+
global.moonlightNodeSandboxed = {
+
fs: createFS(),
+
addCors(url) {
+
registerCors(url);
+
if (initialized) setCors();
+
},
+
addBlocked(url) {
+
registerBlocked(url);
+
if (initialized) setCors();
+
}
+
};
-
function getConfig(ext: string) {
-
const val = config.extensions[ext];
-
if (val == null || typeof val === "boolean") return undefined;
-
return val.config;
-
}
+
let config = await readConfig();
+
initLogger(config);
+
logger = new Logger("node-preload");
+
+
const extensions = await getExtensions();
+
const processedExtensions = await loadExtensions(extensions);
+
const moonlightDir = await getMoonlightDir();
+
const extensionsPath = await getExtensionsPath();
global.moonlightNode = {
-
config,
-
extensions: getExtensions(),
-
processedExtensions: processed,
+
get config() {
+
return config;
+
},
+
extensions,
+
processedExtensions,
nativesCache: {},
+
isBrowser: false,
+
events: createEventEmitter<NodeEventType, NodeEventPayloads>(),
-
getConfig,
-
getConfigOption: <T>(ext: string, name: string) => {
-
const config = getConfig(ext);
-
if (config == null) return undefined;
-
const option = config[name];
-
if (option == null) return undefined;
-
return option as T;
+
version: MOONLIGHT_VERSION,
+
branch: MOONLIGHT_BRANCH as MoonlightBranch,
+
+
getConfig(ext) {
+
return getConfig(ext, config);
+
},
+
getConfigOption(ext, name) {
+
const manifest = getManifest(extensions, ext);
+
return getConfigOption(ext, name, config, manifest?.settings);
+
},
+
async setConfigOption(ext, name, value) {
+
setConfigOption(config, ext, name, value);
+
await this.writeConfig(config);
+
},
+
async writeConfig(newConfig) {
+
await writeConfig(newConfig);
+
config = newConfig;
+
this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig);
},
+
getNatives: (ext: string) => global.moonlightNode.nativesCache[ext],
getLogger: (id: string) => {
return new Logger(id);
},
-
-
getExtensionDir: (ext: string) => {
-
const extPath = getExtensionsPath();
-
return path.join(extPath, ext);
+
getMoonlightDir() {
+
return moonlightDir;
},
-
writeConfig
+
getExtensionDir: (ext: string) => {
+
return path.join(extensionsPath, ext);
+
}
};
-
await loadProcessedExtensions(processed);
+
await loadProcessedExtensions(processedExtensions);
contextBridge.exposeInMainWorld("moonlightNode", moonlightNode);
-
const extCors = moonlightNode.processedExtensions.extensions
-
.map((x) => x.manifest.cors ?? [])
-
.flat();
+
const extCors = moonlightNode.processedExtensions.extensions.flatMap((x) => x.manifest.cors ?? []);
+
for (const cors of extCors) {
+
registerCors(cors);
+
}
for (const repo of moonlightNode.config.repositories) {
const url = new URL(repo);
url.pathname = "/";
-
extCors.push(url.toString());
+
registerCors(url.toString());
+
}
+
+
const extBlocked = moonlightNode.processedExtensions.extensions.flatMap((e) => e.manifest.blocked ?? []);
+
for (const blocked of extBlocked) {
+
registerBlocked(blocked);
}
-
ipcRenderer.invoke(constants.ipcSetCorsList, extCors);
+
setCors();
+
+
initialized = true;
}
async function loadPreload() {
const webPreloadPath = path.join(__dirname, "web-preload.js");
const webPreload = fs.readFileSync(webPreloadPath, "utf8");
await webFrame.executeJavaScript(webPreload);
+
+
const func = await webFrame.executeJavaScript("async () => { await window._moonlightWebLoad(); }");
+
await func();
}
-
async function init(oldPreloadPath: string) {
+
async function init() {
try {
await injectGlobals();
await loadPreload();
···
message: message
});
}
+
}
-
// Let Discord start even if we fail
+
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
+
const isOverlay = window.location.href.indexOf("discord_overlay") > -1;
+
+
if (isOverlay) {
+
// The overlay has an inline script tag to call to DiscordNative, so we'll
+
// just load it immediately. Somehow moonlight still loads in this env, I
+
// have no idea why - so I suspect it's just forwarding render calls or
+
// something from the original process
require(oldPreloadPath);
-
}
+
} else {
+
ipcRenderer.on(constants.ipcNodePreloadKickoff, (_, blockedScripts: string[]) => {
+
(async () => {
+
try {
+
await init();
+
logger.debug("Blocked scripts:", blockedScripts);
+
+
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
+
logger.debug("Old preload path:", oldPreloadPath);
+
if (oldPreloadPath) require(oldPreloadPath);
+
+
// Do this to get global.DiscordNative assigned
+
// @ts-expect-error Lying to discord_desktop_core
+
process.emit("loaded");
+
+
function replayScripts() {
+
const scripts = [...document.querySelectorAll("script")].filter(
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
+
);
-
const oldPreloadPath: string = ipcRenderer.sendSync(
-
constants.ipcGetOldPreloadPath
-
);
-
init(oldPreloadPath);
+
blockedScripts.reverse();
+
for (const url of blockedScripts) {
+
if (url.includes("/sentry.")) continue;
+
+
const script = scripts.find((script) => url.includes(script.src))!;
+
const newScript = document.createElement("script");
+
for (const attr of script.attributes) {
+
if (attr.name === "src") attr.value += "?inj";
+
newScript.setAttribute(attr.name, attr.value);
+
}
+
script.remove();
+
document.documentElement.appendChild(newScript);
+
}
+
}
+
+
if (document.readyState === "complete") {
+
replayScripts();
+
} else {
+
window.addEventListener("load", replayScripts);
+
}
+
} catch (e) {
+
logger.error("Error restoring original scripts:", e);
+
}
+
})();
+
});
+
}
+4 -1
packages/node-preload/tsconfig.json
···
{
-
"extends": "../../tsconfig.json"
+
"extends": "../../tsconfig.json",
+
"compilerOptions": {
+
"lib": ["DOM", "ESNext", "DOM.Iterable"]
+
}
}
+15 -7
packages/types/package.json
···
{
"name": "@moonlight-mod/types",
-
"version": "1.1.2",
-
"main": "./src/index.ts",
-
"types": "./src/index.ts",
+
"version": "1.3.17",
"exports": {
".": "./src/index.ts",
"./import": "./src/import.d.ts",
"./*": "./src/*.ts"
},
+
"main": "./src/index.ts",
+
"types": "./src/index.ts",
+
"engineStrict": false,
+
"engines": {
+
"node": ">=22",
+
"pnpm": ">=10",
+
"npm": "pnpm",
+
"yarn": "pnpm"
+
},
"dependencies": {
-
"@types/flux": "^3.1.12",
-
"@types/node": "^20.6.2",
-
"@types/react": "^18.2.22",
-
"csstype": "^3.1.2",
+
"@moonlight-mod/lunast": "^1.0.1",
+
"@moonlight-mod/mappings": "^1.1.25",
+
"@moonlight-mod/moonmap": "^1.0.5",
+
"@types/react": "^18.3.10",
+
"csstype": "^3.1.3",
"standalone-electron-types": "^1.0.0"
}
}
+64 -4
packages/types/src/config.ts
···
patchAll?: boolean;
};
-
export type ConfigExtensions =
-
| { [key: string]: boolean }
-
| { [key: string]: ConfigExtension };
+
export type ConfigExtensions = { [key: string]: boolean } | { [key: string]: ConfigExtension };
export type ConfigExtension = {
enabled: boolean;
···
Boolean = "boolean",
Number = "number",
String = "string",
+
MultilineString = "multilinestring",
Select = "select",
MultiSelect = "multiselect",
List = "list",
···
Custom = "custom"
}
+
export type SelectOption =
+
| string
+
| {
+
value: string;
+
label: string;
+
};
+
export type BooleanSettingType = {
+
/**
+
* Displays as a simple switch.
+
*/
type: ExtensionSettingType.Boolean;
default?: boolean;
};
export type NumberSettingType = {
+
/**
+
* Displays as a simple slider.
+
*/
type: ExtensionSettingType.Number;
default?: number;
min?: number;
···
};
export type StringSettingType = {
+
/**
+
* Displays as a single line string input.
+
*/
type: ExtensionSettingType.String;
default?: string;
};
+
export type MultilineTextInputSettingType = {
+
/**
+
* Displays as a multiple line string input.
+
*/
+
type: ExtensionSettingType.MultilineString;
+
default?: string;
+
};
+
export type SelectSettingType = {
+
/**
+
* A dropdown to pick between one of many values.
+
*/
type: ExtensionSettingType.Select;
-
options: string[];
+
options: SelectOption[];
default?: string;
};
export type MultiSelectSettingType = {
+
/**
+
* A dropdown to pick multiple values.
+
*/
type: ExtensionSettingType.MultiSelect;
options: string[];
default?: string[];
};
export type ListSettingType = {
+
/**
+
* A list of strings that the user can add or remove from.
+
*/
type: ExtensionSettingType.List;
default?: string[];
};
export type DictionarySettingType = {
+
/**
+
* A dictionary (key-value pair) that the user can add or remove from.
+
*/
type: ExtensionSettingType.Dictionary;
default?: Record<string, string>;
};
export type CustomSettingType = {
+
/**
+
* A custom component.
+
* You can use the registerConfigComponent function in the Moonbase API to register a React component to render here.
+
*/
type: ExtensionSettingType.Custom;
default?: any;
};
+
export enum ExtensionSettingsAdvice {
+
None = "none",
+
Reload = "reload",
+
Restart = "restart"
+
}
+
export type ExtensionSettingsManifest = {
+
/**
+
* A human friendly name for the setting.
+
*/
displayName?: string;
+
+
/**
+
* A longer description for the setting.
+
* Markdown is not supported.
+
*/
description?: string;
+
+
/**
+
* The "advice" to give upon changing this setting.
+
* Can be configured to reload the client, restart the client, or do nothing.
+
*/
+
advice?: ExtensionSettingsAdvice;
} & (
| BooleanSettingType
| NumberSettingType
| StringSettingType
+
| MultilineTextInputSettingType
| SelectSettingType
| MultiSelectSettingType
| ListSettingType
+11
packages/types/src/constants.ts
···
export const distDir = "dist";
export const coreExtensionsDir = "core-extensions";
export const repoUrlFile = ".moonlight-repo-url";
+
export const installedVersionFile = ".moonlight-installed-version";
+
export const ipcNodePreloadKickoff = "_moonlight_nodePreloadKickoff";
export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath";
+
export const ipcGetAppData = "_moonlight_getAppData";
+
export const ipcGetInjectorConfig = "_moonlight_getInjectorConfig";
export const ipcMessageBox = "_moonlight_messageBox";
export const ipcSetCorsList = "_moonlight_setCorsList";
+
export const ipcSetBlockedList = "_moonlight_setBlockedList";
+
+
export const apiLevel = 2;
+
+
export const mainRepo = "https://moonlight-mod.github.io/extensions-dist/repo.json";
+
// If you're updating this, update `defaultConfig` in core as well
+
export const builtinExtensions = ["moonbase", "disableSentry", "noTrack", "noHideToken"];
+30
packages/types/src/core/event.ts
···
+
import { Config } from "../config";
+
import { WebpackModuleFunc, WebpackRequireType } from "../discord";
+
+
export interface MoonlightEventEmitter<EventId extends string = string, EventData = Record<EventId, any>> {
+
dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => void;
+
addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => void;
+
removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => void;
+
}
+
+
export enum WebEventType {
+
ChunkLoad = "chunkLoad",
+
ExtensionLoad = "extensionLoad"
+
}
+
+
export type WebEventPayloads = {
+
[WebEventType.ChunkLoad]: {
+
chunkId?: number[];
+
modules: { [id: string]: WebpackModuleFunc };
+
require?: (require: WebpackRequireType) => any;
+
};
+
[WebEventType.ExtensionLoad]: string;
+
};
+
+
export enum NodeEventType {
+
ConfigSaved = "configSaved"
+
}
+
+
export type NodeEventPayloads = {
+
[NodeEventType.ConfigSaved]: Config;
+
};
+13
packages/types/src/coreExtensions/appPanels.ts
···
+
export type AppPanels = {
+
/**
+
* Registers a new panel to be displayed around the user/voice controls.
+
* @param section A unique name for your section
+
* @param element A React component
+
*/
+
addPanel: (section: string, element: React.FC<any>) => void;
+
+
/**
+
* @private
+
*/
+
getPanels: (el: React.FC<any>) => React.ReactNode;
+
};
+204
packages/types/src/coreExtensions/commands.ts
···
+
export const APPLICATION_ID = "-3";
+
+
export enum CommandType {
+
CHAT = 1,
+
MESSAGE = 3,
+
PRIMARY_ENTRY_POINT = 4,
+
USER = 2
+
}
+
+
export enum InputType {
+
BOT = 3,
+
BUILT_IN = 0,
+
BUILT_IN_INTEGRATION = 2,
+
BUILT_IN_TEXT = 1,
+
PLACEHOLDER = 4
+
}
+
+
export enum OptionType {
+
SUB_COMMAND = 1,
+
SUB_COMMAND_GROUP = 2,
+
STRING = 3,
+
INTEGER = 4,
+
BOOLEAN = 5,
+
USER = 6,
+
CHANNEL = 7,
+
ROLE = 8,
+
MENTIONABLE = 9,
+
NUMBER = 10,
+
ATTACHMENT = 11
+
}
+
+
export enum ChannelType {
+
GUILD_TEXT = 0,
+
DM = 1,
+
GUILD_VOICE = 2,
+
GROUP_DM = 3,
+
GUILD_CATEGORY = 4,
+
GUILD_ANNOUNCEMENT = 5,
+
GUILD_STORE = 6,
+
ANNOUNCEMENT_THREAD = 10,
+
PUBLIC_THREAD = 11,
+
PRIVATE_THREAD = 12,
+
GUILD_STAGE_VOICE = 13,
+
GUILD_DIRECTORY = 14,
+
GUILD_FORUM = 15,
+
GUILD_MEDIA = 16,
+
LOBBY = 17,
+
DM_SDK = 18
+
}
+
+
export type RegisteredCommandOption = MoonlightCommandOption & {
+
displayName: string;
+
displayDescription: string;
+
};
+
+
export type CommandOptionChoice<T> = {
+
name: string;
+
value: T;
+
};
+
+
type CommandOptionBase<T> = {
+
type: T;
+
name: string;
+
description: string;
+
required?: T extends OptionType.SUB_COMMAND
+
? never
+
: T extends OptionType.SUB_COMMAND_GROUP
+
? never
+
: boolean | undefined;
+
choices?: T extends OptionType.STRING
+
? CommandOptionChoice<string>[]
+
: T extends OptionType.INTEGER
+
? CommandOptionChoice<number>[]
+
: T extends OptionType.NUMBER
+
? CommandOptionChoice<number>[]
+
: never;
+
options?: T extends OptionType.SUB_COMMAND
+
? MoonlightCommandOption[]
+
: T extends OptionType.SUB_COMMAND_GROUP
+
? MoonlightCommandOption[]
+
: never;
+
channelTypes?: T extends OptionType.CHANNEL ? ChannelType[] : never;
+
minValue?: T extends OptionType.INTEGER ? number : T extends OptionType.NUMBER ? number : never;
+
maxValue?: T extends OptionType.INTEGER ? number : T extends OptionType.NUMBER ? number : never;
+
minLength?: T extends OptionType.STRING ? number : never;
+
maxLength?: T extends OptionType.STRING ? number : never;
+
};
+
+
// This is bad lol
+
export type MoonlightCommandOption =
+
| CommandOptionBase<OptionType.SUB_COMMAND>
+
| CommandOptionBase<OptionType.SUB_COMMAND_GROUP>
+
| CommandOptionBase<OptionType.STRING>
+
| CommandOptionBase<OptionType.INTEGER>
+
| CommandOptionBase<OptionType.BOOLEAN>
+
| CommandOptionBase<OptionType.USER>
+
| CommandOptionBase<OptionType.CHANNEL>
+
| CommandOptionBase<OptionType.ROLE>
+
| CommandOptionBase<OptionType.MENTIONABLE>
+
| CommandOptionBase<OptionType.NUMBER>
+
| CommandOptionBase<OptionType.ATTACHMENT>;
+
+
// TODO: types
+
export type CommandPredicateState = {
+
channel: any;
+
guild: any;
+
};
+
+
export type RegisteredCommand = {
+
id: string;
+
untranslatedName: string;
+
displayName: string;
+
type: CommandType;
+
inputType: InputType;
+
applicationId: string; // set to -3!
+
untranslatedDescription: string;
+
displayDescription: string;
+
options?: RegisteredCommandOption[];
+
predicate?: (state: CommandPredicateState) => boolean;
+
execute: (options: CommandOption[]) => void;
+
};
+
+
export type MoonlightCommand = {
+
id: string;
+
description: string;
+
+
/**
+
* You likely want CHAT
+
*/
+
type: CommandType;
+
+
/**
+
* You likely want BUILT_IN (or BUILT_IN_TEXT if usable with replies)
+
*/
+
inputType: InputType;
+
options?: MoonlightCommandOption[];
+
predicate?: (state: CommandPredicateState) => boolean;
+
execute: (options: CommandOption[]) => void;
+
};
+
+
export type CommandOption = {
+
name: string;
+
} & ( // TODO: more of these
+
| {
+
type: Exclude<OptionType, OptionType.STRING>;
+
value: any;
+
}
+
| {
+
type: OptionType.STRING;
+
value: string;
+
}
+
| {
+
type: OptionType.NUMBER | OptionType.INTEGER;
+
value: number;
+
}
+
| {
+
type: OptionType.BOOLEAN;
+
value: boolean;
+
}
+
| {
+
type: OptionType.SUB_COMMAND | OptionType.SUB_COMMAND_GROUP;
+
options: CommandOption[];
+
}
+
);
+
+
export type AnyScopeRegex = RegExp["exec"] & {
+
regex: RegExp;
+
};
+
+
export type Commands = {
+
/**
+
* Register a command in the internal slash command system
+
*/
+
registerCommand: (command: MoonlightCommand) => void;
+
+
/**
+
* Register a legacy command that works via regex
+
*/
+
registerLegacyCommand: (id: string, command: LegacyCommand) => void;
+
+
/**
+
* Creates a regular expression that legacy commands can understand
+
*/
+
anyScopeRegex: (regex: RegExp) => AnyScopeRegex;
+
+
/**
+
* @private
+
*/
+
_getCommands: () => RegisteredCommand[];
+
};
+
+
export type LegacyContext = {
+
channel: any;
+
isEdit: boolean;
+
};
+
+
export type LegacyReturn = {
+
content: string;
+
};
+
+
export type LegacyCommand = {
+
match?: RegExp | { regex: RegExp } | AnyScopeRegex;
+
action: (content: string, context: LegacyContext) => LegacyReturn;
+
};
+33
packages/types/src/coreExtensions/common.ts
···
+
import type { IconProps, IconSize } from "@moonlight-mod/mappings/discord/components/common/index";
+
+
export type ErrorBoundaryProps = React.PropsWithChildren<{
+
noop?: boolean;
+
fallback?: React.FC<any>;
+
message?: string;
+
}>;
+
+
export type ErrorBoundaryState = {
+
errored: boolean;
+
error?: Error;
+
componentStack?: string;
+
};
+
+
export type ErrorBoundary = React.ComponentClass<ErrorBoundaryProps, ErrorBoundaryState>;
+
+
export type ParsedIconProps = {
+
width: number;
+
height: number;
+
fill: string;
+
className: string;
+
};
+
+
export interface Icons {
+
/**
+
* Parse icon props into their actual width/height.
+
* @param props The icon props
+
*/
+
parseProps(props?: IconProps): ParsedIconProps;
+
}
+
+
// Re-export so extension developers don't need to depend on mappings
+
export type { IconProps, IconSize };
+162
packages/types/src/coreExtensions/componentEditor.ts
···
+
type Patcher<T> = (elements: React.ReactNode[], props: T) => React.ReactNode[];
+
+
//#region DM List
+
export type DMListAnchors =
+
| "content"
+
| "favorite-server-indicator"
+
| "ignored-indicator"
+
| "blocked-indicator"
+
| "close-button"
+
| undefined;
+
export type DMListDecoratorAnchors = "system-tag" | undefined;
+
+
export enum DMListAnchorIndicies {
+
content = 0,
+
"favorite-server-indicator",
+
"ignored-indicator",
+
"blocked-indicator",
+
"close-button"
+
}
+
export enum DMListDecoratorAnchorIndicies {
+
"system-tag" = 0
+
}
+
+
export type DMListItem = {
+
component: React.FC<any>;
+
anchor: DMListAnchors;
+
before: boolean;
+
};
+
export type DMListDecorator = {
+
component: React.FC<any>;
+
anchor: DMListDecoratorAnchors;
+
before: boolean;
+
};
+
+
export type DMList = {
+
addItem: (id: string, component: React.FC<any>, anchor?: DMListAnchors, before?: boolean) => void;
+
addDecorator: (id: string, component: React.FC<any>, anchor?: DMListDecoratorAnchors, before?: boolean) => void;
+
//TODO: fix props type
+
/**
+
* @private
+
*/
+
_patchItems: Patcher<any>;
+
/**
+
* @private
+
*/
+
_patchDecorators: Patcher<any>;
+
};
+
//#endregion
+
+
//#region Member List
+
export type MemberListDecoratorAnchors = "bot-tag" | "owner-crown" | "boost-icon" | undefined;
+
+
export enum MemberListDecoratorAnchorIndicies {
+
"bot-tag" = 0,
+
"owner-crown",
+
"boost-icon"
+
}
+
+
export type MemberListDecorator = {
+
component: React.FC<any>;
+
anchor: MemberListDecoratorAnchors;
+
before: boolean;
+
};
+
+
export type MemberList = {
+
addItem: (id: string, component: React.FC<any>) => void;
+
addDecorator: (id: string, component: React.FC<any>, anchor?: MemberListDecoratorAnchors, before?: boolean) => void;
+
//TODO: fix props type
+
/**
+
* @private
+
*/
+
_patchItems: Patcher<any>;
+
/**
+
* @private
+
*/
+
_patchDecorators: Patcher<any>;
+
};
+
//#endregion
+
+
//#region Messages
+
export type MessageUsernameAnchors = "communication-disabled" | "username" | undefined;
+
export type MessageUsernameBadgeAnchors =
+
| "nitro-author"
+
| "role-icon"
+
| "new-member"
+
| "leaderboard-champion"
+
| "connections"
+
| undefined;
+
export type MessageBadgeAnchors = "silent" | "potion" | undefined;
+
+
export type MessageUsername = {
+
component: React.FC<any>;
+
anchor: MessageUsernameAnchors;
+
before: boolean;
+
};
+
export type MessageUsernameBadge = {
+
component: React.FC<any>;
+
anchor: MessageUsernameBadgeAnchors;
+
before: boolean;
+
};
+
export type MessageBadge = {
+
component: React.FC<any>;
+
anchor: MessageBadgeAnchors;
+
before: boolean;
+
};
+
+
export enum MessageUsernameIndicies {
+
"communication-disabled" = 0,
+
username
+
}
+
export enum MessageUsernameBadgeIndicies {
+
"nitro-author" = 0,
+
"role-icon",
+
"new-member",
+
"leaderboard-champion",
+
connections
+
}
+
export enum MessageBadgeIndicies {
+
silent = 0,
+
potion
+
}
+
+
export type Messages = {
+
/**
+
* Adds a component to the username of a message
+
*/
+
addToUsername: (id: string, component: React.FC<any>, anchor?: MessageUsernameAnchors, before?: boolean) => void;
+
/**
+
* Adds a component to the username badge area of a message (e.g. where role icons/new member badge is)
+
*/
+
addUsernameBadge: (
+
id: string,
+
component: React.FC<any>,
+
anchor?: MessageUsernameBadgeAnchors,
+
before?: boolean
+
) => void;
+
/**
+
* Adds a component to the end of a message header (e.g. silent indicator)
+
*/
+
addBadge: (id: string, component: React.FC<any>, anchor?: MessageBadgeAnchors, before?: boolean) => void;
+
/**
+
* Adds a component to message accessories (e.g. embeds)
+
*/
+
addAccessory: (id: string, component: React.FC<any>) => void;
+
/**
+
* @private
+
*/
+
_patchUsername: Patcher<any>;
+
/**
+
* @private
+
*/
+
_patchUsernameBadges: Patcher<any>;
+
/**
+
* @private
+
*/
+
_patchBadges: Patcher<any>;
+
/**
+
* @private
+
*/
+
_patchAccessories: Patcher<any>;
+
};
+
//#endregion
-362
packages/types/src/coreExtensions/components.ts
···
-
import type {
-
Component,
-
Ref,
-
PropsWithChildren,
-
PropsWithoutRef,
-
CSSProperties,
-
ReactNode,
-
ReactElement,
-
ComponentClass,
-
ComponentType,
-
MouseEventHandler,
-
KeyboardEventHandler
-
} from "react";
-
import * as CSS from "csstype";
-
-
export enum TextInputSizes {
-
DEFAULT = "inputDefault",
-
MINI = "inputMini"
-
}
-
-
interface TextInput
-
extends ComponentClass<
-
PropsWithoutRef<{
-
value?: string;
-
name?: string;
-
className?: string;
-
inputClassName?: string;
-
inputPrefix?: string;
-
disabled?: boolean;
-
size?: TextInputSizes;
-
editable?: boolean;
-
inputRef?: Ref<any>;
-
prefixElement?: Component;
-
focusProps?: PropsWithoutRef<any>;
-
error?: string;
-
minLength?: number;
-
maxLength?: number;
-
onChange?: (value: string, name: string) => void;
-
onFocus?: (event: any, name: string) => void;
-
onBlur?: (event: any, name: string) => void;
-
}>
-
> {
-
Sizes: typeof TextInputSizes;
-
}
-
-
export enum FormTextTypes {
-
DEFAULT = "default",
-
DESCRIPTION = "description",
-
ERROR = "error",
-
INPUT_PLACEHOLDER = "placeholder",
-
LABEL_BOLD = "labelBold",
-
LABEL_DESCRIPTOR = "labelDescriptor",
-
LABEL_SELECTED = "labelSelected",
-
SUCCESS = "success"
-
}
-
-
interface FormText
-
extends ComponentClass<
-
PropsWithChildren<{
-
type?: FormTextTypes;
-
className?: string;
-
disabled?: boolean;
-
selectable?: boolean;
-
style?: CSSProperties;
-
}>
-
> {
-
Types: FormTextTypes;
-
}
-
-
declare enum SliderMarkerPosition {
-
ABOVE,
-
BELOW
-
}
-
-
declare enum ButtonLooks {
-
FILLED = "lookFilled",
-
INVERTED = "lookInverted",
-
OUTLINED = "lookOutlined",
-
LINK = "lookLink",
-
BLANK = "lookBlank"
-
}
-
declare enum ButtonColors {
-
BRAND = "colorBrand",
-
RED = "colorRed",
-
GREEN = "colorGreen",
-
YELLOW = "colorYellow",
-
PRIMARY = "colorPrimary",
-
LINK = "colorLink",
-
WHITE = "colorWhite",
-
BLACK = "colorBlack",
-
TRANSPARENT = "colorTransparent",
-
BRAND_NEW = "colorBrandNew",
-
CUSTOM = ""
-
}
-
declare enum ButtonBorderColors {
-
BRAND = "borderBrand",
-
RED = "borderRed",
-
GREEN = "borderGreen",
-
YELLOW = "borderYellow",
-
PRIMARY = "borderPrimary",
-
LINK = "borderLink",
-
WHITE = "borderWhite",
-
BLACK = "borderBlack",
-
TRANSPARENT = "borderTransparent",
-
BRAND_NEW = "borderBrandNew"
-
}
-
declare enum ButtonHovers {
-
DEFAULT = "",
-
BRAND = "hoverBrand",
-
RED = "hoverRed",
-
GREEN = "hoverGreen",
-
YELLOW = "hoverYellow",
-
PRIMARY = "hoverPrimary",
-
LINK = "hoverLink",
-
WHITE = "hoverWhite",
-
BLACK = "hoverBlack",
-
TRANSPARENT = "hoverTransparent"
-
}
-
declare enum ButtonSizes {
-
NONE = "",
-
TINY = "sizeTiny",
-
SMALL = "sizeSmall",
-
MEDIUM = "sizeMedium",
-
LARGE = "sizeLarge",
-
XLARGE = "sizeXlarge",
-
MIN = "sizeMin",
-
MAX = "sizeMax",
-
ICON = "sizeIcon"
-
}
-
-
type Button = ComponentType<
-
PropsWithChildren<{
-
look?: ButtonLooks;
-
color?: ButtonColors;
-
borderColor?: ButtonBorderColors;
-
hover?: ButtonHovers;
-
size?: ButtonSizes;
-
fullWidth?: boolean;
-
grow?: boolean;
-
disabled?: boolean;
-
submitting?: boolean;
-
type?: string;
-
style?: CSSProperties;
-
wrapperClassName?: string;
-
className?: string;
-
innerClassName?: string;
-
onClick?: MouseEventHandler;
-
onDoubleClick?: MouseEventHandler;
-
onMouseDown?: MouseEventHandler;
-
onMouseUp?: MouseEventHandler;
-
onMouseEnter?: MouseEventHandler;
-
onMouseLeave?: MouseEventHandler;
-
onKeyDown?: KeyboardEventHandler;
-
rel?: any;
-
buttonRef?: Ref<any>;
-
focusProps?: PropsWithChildren<any>;
-
"aria-label"?: string;
-
submittingStartedLabel?: string;
-
submittingFinishedLabel?: string;
-
}>
-
> & {
-
Looks: typeof ButtonLooks;
-
Colors: typeof ButtonColors;
-
BorderColors: typeof ButtonBorderColors;
-
Hovers: typeof ButtonHovers;
-
Sizes: typeof ButtonSizes;
-
};
-
-
export enum FlexDirection {
-
VERTICAL = "vertical",
-
HORIZONTAL = "horizontal",
-
HORIZONTAL_REVERSE = "horizontalReverse"
-
}
-
-
declare enum FlexAlign {
-
START = "alignStart",
-
END = "alignEnd",
-
CENTER = "alignCenter",
-
STRETCH = "alignStretch",
-
BASELINE = "alignBaseline"
-
}
-
declare enum FlexJustify {
-
START = "justifyStart",
-
END = "justifyEnd",
-
CENTER = "justifyCenter",
-
BETWEEN = "justifyBetween",
-
AROUND = "justifyAround"
-
}
-
declare enum FlexWrap {
-
NO_WRAP = "noWrap",
-
WRAP = "wrap",
-
WRAP_REVERSE = "wrapReverse"
-
}
-
interface Flex
-
extends ComponentClass<
-
PropsWithChildren<{
-
className?: string;
-
direction?: FlexDirection;
-
justify?: FlexJustify;
-
align?: FlexAlign;
-
wrap?: FlexWrap;
-
shrink?: CSS.Property.FlexShrink;
-
grow?: CSS.Property.FlexGrow;
-
basis?: CSS.Property.FlexBasis;
-
style?: CSSProperties;
-
}>
-
> {
-
Direction: typeof FlexDirection;
-
Align: typeof FlexAlign;
-
Justify: typeof FlexJustify;
-
Wrap: typeof FlexWrap;
-
Child: Component<
-
PropsWithChildren<{
-
className?: string;
-
shrink?: CSS.Property.FlexShrink;
-
grow?: CSS.Property.FlexGrow;
-
basis?: CSS.Property.FlexBasis;
-
style?: CSSProperties;
-
wrap?: boolean;
-
}>
-
>;
-
}
-
-
// TODO: wtaf is up with react types not working in jsx
-
export type CommonComponents = {
-
Clickable: ComponentClass<
-
PropsWithChildren<{
-
onClick?: () => void;
-
href?: any;
-
onKeyPress?: () => void;
-
ignoreKeyPress?: boolean;
-
innerRef?: Ref<any>;
-
focusProps?: any;
-
tag?: string | Component;
-
role?: any;
-
tabIndex?: any;
-
className?: string;
-
}>
-
>;
-
TextInput: TextInput;
-
FormSection: ComponentClass<
-
PropsWithChildren<{
-
className?: string;
-
titleClassName?: string;
-
title?: ReactNode;
-
icon?: ReactNode;
-
disabled?: boolean;
-
htmlFor?: any;
-
tag?: string;
-
}>
-
>;
-
FormText: FormText;
-
FormTitle: ComponentClass<
-
PropsWithChildren<{
-
tag?: string;
-
className?: string;
-
faded?: boolean;
-
disabled?: boolean;
-
required?: boolean;
-
error?: string;
-
}>
-
>;
-
FormSwitch: ComponentClass<PropsWithChildren<any>>;
-
FormItem: ComponentClass<PropsWithChildren<any>>;
-
Slider: ComponentClass<
-
PropsWithChildren<{
-
disabled?: boolean;
-
stickToMarkers?: boolean;
-
className?: string;
-
barStyles?: CSSProperties;
-
fillStyles?: CSSProperties;
-
mini?: boolean;
-
hideBubble?: boolean;
-
initialValue?: number;
-
orientation?: "horizontal" | "vertical";
-
onValueRender?: (value: number) => string;
-
renderMarker?: (marker: number) => ReactNode;
-
getAriaValueText?: (value: number) => string;
-
barClassName?: string;
-
grabberClassName?: string;
-
grabberStyles?: CSSProperties;
-
markerPosition?: SliderMarkerPosition;
-
"aria-hidden"?: "true" | "false";
-
"aria-label"?: string;
-
"aria-labelledby"?: string;
-
"aria-describedby"?: string;
-
minValue?: number;
-
maxValue?: number;
-
asValueChanges?: (value: number) => void;
-
onValueChange?: (value: number) => void;
-
keyboardStep?: number;
-
}>
-
>;
-
Switch: ComponentClass<PropsWithChildren<any>>;
-
Button: Button;
-
Tooltip: ComponentClass<PropsWithChildren<any>>;
-
SmallSlider: Component;
-
Avatar: Component;
-
Scroller: Component;
-
Text: ComponentClass<PropsWithChildren<any>>;
-
Heading: ComponentClass<PropsWithChildren<any>>;
-
LegacyText: Component;
-
Flex: Flex;
-
Card: ComponentClass<PropsWithChildren<any>>;
-
Popout: ComponentClass<PropsWithChildren<any>>;
-
Dialog: ComponentClass<PropsWithChildren<any>>;
-
Menu: ComponentClass<PropsWithChildren<any>>;
-
MenuItem: ComponentClass<PropsWithChildren<any>>;
-
MenuGroup: ComponentClass<PropsWithChildren<any>>;
-
MenuCheckboxItem: ComponentClass<PropsWithChildren<any>>;
-
CardClasses: {
-
card: string;
-
cardHeader: string;
-
};
-
ControlClasses: {
-
container: string;
-
control: string;
-
disabled: string;
-
dividerDefault: string;
-
labelRow: string;
-
note: string;
-
title: string;
-
titleDefault: string;
-
titleMini: string;
-
};
-
MarkdownParser: {
-
parse: (text: string) => ReactElement;
-
};
-
SettingsNotice: React.ComponentType<{
-
submitting: boolean;
-
onReset: () => void;
-
onSave: () => void;
-
}>;
-
TabBar: React.ComponentType<any> & {
-
Item: React.ComponentType<any>;
-
};
-
SingleSelect: React.ComponentType<{
-
autofocus?: boolean;
-
clearable?: boolean;
-
value?: string;
-
options?: {
-
value: string;
-
label: string;
-
}[];
-
onChange?: (value: string) => void;
-
}>;
-
Select: React.ComponentType<{
-
autofocus?: boolean;
-
clearable?: boolean;
-
value?: string[];
-
options?: {
-
value: string;
-
label: string;
-
}[];
-
onChange?: (value: string[]) => void;
-
}>;
-
-
// TODO
-
useVariableSelect: any;
-
multiSelect: any;
-
tokens: any;
-
};
+64
packages/types/src/coreExtensions/contextMenu.ts
···
+
import {
+
Menu,
+
MenuCheckboxItem,
+
MenuControlItem,
+
MenuGroup,
+
MenuRadioItem,
+
MenuSeparator,
+
MenuItem,
+
MenuElement
+
} from "@moonlight-mod/mappings/discord/components/common/index";
+
+
export type ContextMenu = {
+
/**
+
* Registers a new context menu item for a given context menu type.
+
* @param navId The navigation ID for the target context menu (e.g. "user-context", "message")
+
* @param item A React component
+
* @param anchor An existing item's ID to anchor the new item to
+
* @param before Whether to insert the new item before the anchor item
+
*/
+
addItem: (navId: string, item: React.FC<any>, anchor: string | RegExp, before?: boolean) => void;
+
+
MenuCheckboxItem: MenuCheckboxItem;
+
MenuControlItem: MenuControlItem;
+
MenuGroup: MenuGroup;
+
MenuItem: MenuItem;
+
MenuRadioItem: MenuRadioItem;
+
MenuSeparator: MenuSeparator;
+
};
+
+
export type InternalItem = {
+
type: string;
+
key?: string;
+
};
+
+
export type InternalSeparator = {
+
type: "separator";
+
navigable: false;
+
};
+
export type InternalGroupStart = {
+
type: "groupstart";
+
length: number;
+
navigable: false;
+
props: React.ComponentProps<MenuGroup>;
+
};
+
export type InternalGroupEnd = {
+
type: "groupend";
+
} & Omit<InternalGroupStart, "type">;
+
export type InternalCustomItem = {
+
type: "customitem";
+
key: any;
+
navigable?: boolean;
+
render: any;
+
props: Extract<React.ComponentProps<MenuItem>, { render: any }>;
+
};
+
export type InternalItem_ = {
+
type: "item";
+
key: any;
+
navigable: true;
+
label: string;
+
};
+
+
export type EvilItemParser = (el: MenuElement | MenuElement[]) => InternalItem[];
+
+
export type { Menu, MenuElement };
+107
packages/types/src/coreExtensions/markdown.ts
···
+
// {{{ simple-markdown
+
+
export type SingleASTNode = {
+
type: string;
+
[key: string]: any;
+
};
+
+
export type UntypedASTNode = {
+
[key: string]: any;
+
};
+
+
export type ASTNode = SingleASTNode | Array<SingleASTNode>;
+
+
export type Parser = (source: string, state?: State | null | undefined) => Array<SingleASTNode>;
+
+
export type ParseFunction = (capture: Capture, nestedParse: Parser, state: State) => UntypedASTNode | ASTNode;
+
+
export type Capture =
+
| (Array<string> & {
+
index: number;
+
})
+
| (Array<string> & {
+
index?: number;
+
});
+
+
export type State = {
+
key?: string | number | undefined;
+
inline?: boolean | null | undefined;
+
[key: string]: any;
+
};
+
+
export type MatchFunction = {
+
regex?: RegExp;
+
} & ((source: string, state: State, prevCapture: string) => Capture | null | undefined);
+
+
export type Output<Result> = (node: ASTNode, state?: State | null | undefined) => Result;
+
+
export type SingleNodeOutput<Result> = (node: SingleASTNode, nestedOutput: Output<Result>, state: State) => Result;
+
+
// }}}
+
+
export type ValidFlags = "g" | "i" | "m" | "s" | "u" | "y" | undefined;
+
+
export type MarkdownRule = {
+
order: number;
+
match: MatchFunction;
+
parse: ParseFunction;
+
react?: SingleNodeOutput<React.ReactNode>;
+
};
+
+
export type SlateRule =
+
| {
+
type: "skip";
+
}
+
| {
+
type: "verbatim";
+
}
+
| {
+
type: "inlineObject";
+
}
+
| {
+
type: "inlineStyle";
+
before: string;
+
after: string;
+
};
+
+
export type Ruleset =
+
| "RULES"
+
| "CHANNEL_TOPIC_RULES"
+
| "VOICE_CHANNEL_STATUS_RULES"
+
| "EMBED_TITLE_RULES"
+
| "INLINE_REPLY_RULES"
+
| "GUILD_VERIFICATION_FORM_RULES"
+
| "GUILD_EVENT_RULES"
+
| "PROFILE_BIO_RULES"
+
| "AUTO_MODERATION_SYSTEM_MESSAGE_RULES"
+
| "NATIVE_SEARCH_RESULT_LINK_RULES";
+
+
export type Markdown = {
+
rules: Record<string, MarkdownRule>;
+
slateRules: Record<string, SlateRule>;
+
slateDecorators: Record<string, string>;
+
ruleBlacklists: Record<Ruleset, Record<string, boolean>>;
+
+
/**
+
* Registers a new Markdown rule with simple-markdown.
+
* @param name The name of the rule
+
* @param markdown A function that returns simple-markdown rules
+
* @param slate A function that returns Slate rules
+
* @param decorator A decorator name for Slate
+
* @see https://www.npmjs.com/package/simple-markdown#adding-a-simple-extension
+
* @see https://docs.slatejs.org/
+
*/
+
addRule: (
+
name: string,
+
markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule,
+
slate: (rules: Record<string, SlateRule>) => SlateRule,
+
decorator?: string | undefined
+
) => void;
+
+
/**
+
* Blacklist a rule from a ruleset.
+
* @param ruleset The ruleset name
+
* @param name The rule name
+
*/
+
blacklistFromRuleset: (ruleset: Ruleset, name: string) => void;
+
};
+17
packages/types/src/coreExtensions/moonbase.ts
···
+
export type CustomComponentProps = {
+
value: any;
+
setValue: (value: any) => void;
+
};
+
+
export type CustomComponent = React.FC<CustomComponentProps>;
+
+
export type Moonbase = {
+
/**
+
* Registers a custom component for an extension setting.
+
* The extension setting must be of type "custom".
+
* @param ext The extension ID
+
* @param option The setting ID
+
* @param component A React component
+
*/
+
registerConfigComponent: (ext: string, option: string, component: CustomComponent) => void;
+
};
+36
packages/types/src/coreExtensions/notices.ts
···
+
import type { Store } from "@moonlight-mod/mappings/discord/packages/flux/Store";
+
+
export type NoticeButton = {
+
name: string;
+
onClick: () => boolean; // return true to dismiss the notice after the button is clicked
+
};
+
+
export type Notice = {
+
element: React.ReactNode;
+
color?: string;
+
showClose?: boolean;
+
buttons?: NoticeButton[];
+
onDismiss?: () => void;
+
};
+
+
export type Notices = Store<any> & {
+
/**
+
* Adds a custom notice to the top of the screen.
+
*/
+
addNotice: (notice: Notice) => void;
+
+
/**
+
* Removes the current notice from the top of the screen.
+
*/
+
popNotice: () => void;
+
+
/**
+
* @private
+
*/
+
getCurrentNotice: () => Notice | null;
+
+
/**
+
* @private
+
*/
+
shouldShowNotice: () => boolean;
+
};
+71
packages/types/src/coreExtensions/settings.ts
···
+
import React, { ReactElement } from "react";
+
import type { Store } from "@moonlight-mod/mappings/discord/packages/flux/Store";
+
+
export type NoticeProps = {
+
stores: Store<any>[];
+
element: React.FunctionComponent;
+
};
+
+
export type SettingsSection =
+
| { section: "DIVIDER"; pos: number | ((sections: SettingsSection[]) => number) }
+
| { section: "HEADER"; label: string; pos: number | ((sections: SettingsSection[]) => number) }
+
| {
+
section: string;
+
label: string;
+
color: string | null;
+
element: React.FunctionComponent;
+
pos: number | ((sections: SettingsSection[]) => number);
+
notice?: NoticeProps;
+
onClick?: () => void;
+
_moonlight_submenu?: () => ReactElement | ReactElement[];
+
};
+
+
export type Settings = {
+
ourSections: SettingsSection[];
+
sectionNames: string[];
+
sectionMenuItems: Record<string, ReactElement[]>;
+
+
/**
+
* Registers a new section in the settings menu.
+
* @param section The section ID
+
* @param label The label for the section
+
* @param element The React component to render
+
* @param color A color to use for the section
+
* @param pos The position in the settings menu to place the section
+
* @param notice A notice to display when in the section
+
* @param onClick A custom action to execute when clicked from the context menu
+
*/
+
addSection: (
+
section: string,
+
label: string,
+
element: React.FunctionComponent,
+
color?: string | null,
+
pos?: number | ((sections: SettingsSection[]) => number),
+
notice?: NoticeProps,
+
onClick?: () => void
+
) => void;
+
+
/**
+
* Adds new items to a section in the settings menu.
+
* @param section The section ID
+
* @param items The React components to render
+
*/
+
addSectionMenuItems: (section: string, ...items: ReactElement[]) => void;
+
+
/**
+
* Places a divider in the settings menu.
+
* @param pos The position in the settings menu to place the divider
+
*/
+
addDivider: (pos: number | ((sections: SettingsSection[]) => number) | null) => void;
+
+
/**
+
* Places a header in the settings menu.
+
* @param pos The position in the settings menu to place the header
+
*/
+
addHeader: (label: string, pos: number | ((sections: SettingsSection[]) => number) | null) => void;
+
+
/**
+
* @private
+
*/
+
_mutateSections: (sections: SettingsSection[]) => SettingsSection[];
+
};
+97
packages/types/src/coreExtensions/spacepack.ts
···
+
import { WebpackModule, WebpackModuleFunc, WebpackRequireType } from "../discord";
+
+
export type Spacepack = {
+
/**
+
* Given a Webpack module ID, returns the function for the Webpack module.
+
* Can be double clicked to inspect in DevTools.
+
* @param module The module ID
+
* @returns The Webpack module, if found
+
*/
+
inspect: (module: number | string) => WebpackModuleFunc | null;
+
+
/**
+
* Find Webpack modules based on matches in code.
+
* @param args A list of finds to match against
+
* @returns The Webpack modules, if found
+
*/
+
findByCode: (...args: (string | RegExp)[]) => WebpackModule[];
+
+
/**
+
* Find Webpack modules based on their exports.
+
* @deprecated This has race conditions. Consider using findByCode instead.
+
* @param args A list of finds to match exports against
+
* @returns The Webpack modules, if found
+
*/
+
findByExports: (...args: string[]) => WebpackModule[];
+
+
/**
+
* The Webpack require function.
+
*/
+
require: WebpackRequireType;
+
+
/**
+
* The Webpack module list.
+
* Re-export of require.m.
+
*/
+
modules: Record<string, WebpackModuleFunc>;
+
+
/**
+
* The Webpack module cache.
+
* Re-export of require.c.
+
*/
+
cache: Record<string, any>;
+
+
/**
+
* Finds an object from a module's exports using the given key.
+
* @param exports Exports from a Webpack module
+
* @param key The key to find with
+
* @returns The object, if found
+
*/
+
findObjectFromKey: (exports: Record<string, any>, key: string) => any | null;
+
+
/**
+
* Finds an object from a module's exports using the given value.
+
* @param exports Exports from a Webpack module
+
* @param value The value to find with
+
* @returns The object, if found
+
*/
+
findObjectFromValue: (exports: Record<string, any>, value: any) => any | null;
+
+
/**
+
* Finds an object from a module's exports using the given key-value pair.
+
* @param exports Exports from a Webpack module
+
* @param key The key to find with
+
* @param value The value to find with
+
* @returns The object, if found
+
*/
+
findObjectFromKeyValuePair: (exports: Record<string, any>, key: string, value: any) => any | null;
+
+
/**
+
* Finds a function from a module's exports using the given source find.
+
* This behaves like findByCode but localized to the exported function.
+
* @param exports A module's exports
+
* @param strings A list of finds to use
+
* @returns The function, if found
+
*/
+
findFunctionByStrings: (
+
exports: Record<string, any>,
+
...strings: (string | RegExp)[]
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
+
) => Function | null;
+
+
/**
+
* Lazy load a Webpack module.
+
* @param find A list of finds to discover a target module with
+
* @param chunk A RegExp to match chunks to load
+
* @param module A RegExp to match the target Webpack module
+
* @returns The target Webpack module
+
*/
+
lazyLoad: (find: string | RegExp | (string | RegExp)[], chunk: RegExp, module: RegExp) => Promise<any>;
+
+
/**
+
* Filter a list of Webpack modules to "real" ones from the Discord client.
+
* @param modules A list of Webpack modules
+
* @returns A filtered list of Webpack modules
+
*/
+
filterReal: (modules: WebpackModule[]) => WebpackModule[];
+
};
+10 -67
packages/types/src/coreExtensions.ts
···
-
import { FluxDefault, Store } from "./discord/common/Flux";
-
import { CommonComponents as CommonComponents_ } from "./coreExtensions/components";
-
import { Dispatcher } from "flux";
-
import React from "react";
-
import { WebpackModuleFunc, WebpackRequireType } from "./discord";
-
-
export type Spacepack = {
-
inspect: (module: number | string) => WebpackModuleFunc | null;
-
findByCode: (...args: (string | RegExp)[]) => any[];
-
findByExports: (...args: string[]) => any[];
-
require: WebpackRequireType;
-
modules: Record<string, WebpackModuleFunc>;
-
cache: Record<string, any>;
-
findObjectFromKey: (exports: Record<string, any>, key: string) => any | null;
-
findObjectFromValue: (exports: Record<string, any>, value: any) => any | null;
-
findObjectFromKeyValuePair: (
-
exports: Record<string, any>,
-
key: string,
-
value: any
-
) => any | null;
-
findFunctionByStrings: (
-
exports: Record<string, any>,
-
...strings: (string | RegExp)[]
-
// eslint-disable-next-line @typescript-eslint/ban-types
-
) => Function | null;
-
};
-
-
export type NoticeProps = {
-
stores: Store<any>[];
-
element: React.FunctionComponent;
-
};
-
-
export type SettingsSection =
-
| { section: "DIVIDER"; pos: number }
-
| { section: "HEADER"; label: string; pos: number }
-
| {
-
section: string;
-
label: string;
-
color: string | null;
-
element: React.FunctionComponent;
-
pos: number;
-
notice?: NoticeProps;
-
_moonlight_submenu?: () => any;
-
};
-
-
export type Settings = {
-
ourSections: SettingsSection[];
-
sectionNames: string[];
-
-
addSection: (
-
section: string,
-
label: string,
-
element: React.FunctionComponent,
-
color?: string | null,
-
pos?: number,
-
notice?: NoticeProps
-
) => void;
-
-
addDivider: (pos: number | null) => void;
-
addHeader: (label: string, pos: number | null) => void;
-
_mutateSections: (sections: SettingsSection[]) => SettingsSection[];
-
};
-
-
export type CommonReact = typeof import("react");
-
export type CommonFlux = FluxDefault;
-
export type CommonComponents = CommonComponents_; // lol
-
export type CommonFluxDispatcher = Dispatcher<any>;
+
export * as Spacepack from "./coreExtensions/spacepack";
+
export * as Settings from "./coreExtensions/settings";
+
export * as Markdown from "./coreExtensions/markdown";
+
export * as ContextMenu from "./coreExtensions/contextMenu";
+
export * as Notices from "./coreExtensions/notices";
+
export * as Moonbase from "./coreExtensions/moonbase";
+
export * as AppPanels from "./coreExtensions/appPanels";
+
export * as Commands from "./coreExtensions/commands";
+
export * as ComponentEditor from "./coreExtensions/componentEditor";
+
export * as Common from "./coreExtensions/common";
-57
packages/types/src/discord/common/Flux.ts
···
-
/*
-
It seems like Discord maintains their own version of Flux that doesn't match
-
the types on NPM. This is a heavy work in progress - if you encounter rough
-
edges, please contribute!
-
*/
-
-
import { DependencyList } from "react";
-
import { Store as FluxStore } from "flux/utils";
-
import { Dispatcher as FluxDispatcher } from "flux";
-
import { ComponentConstructor } from "flux/lib/FluxContainer";
-
-
export declare abstract class Store<T> extends FluxStore<T> {
-
static getAll: () => Store<any>[];
-
getName: () => string;
-
emitChange: () => void;
-
}
-
-
interface ConnectStores {
-
<T>(
-
stores: Store<any>[],
-
callback: T,
-
context?: any
-
): ComponentConstructor<T>;
-
}
-
-
export type FluxDefault = {
-
DeviceSettingsStore: any; // TODO
-
Emitter: any; // @types/fbemitter
-
OfflineCacheStore: any; // TODO
-
PersistedStore: any; // TODO
-
Store: typeof Store;
-
Dispatcher: typeof FluxDispatcher;
-
connectStores: ConnectStores;
-
initialize: () => void;
-
initialized: Promise<boolean>;
-
destroy: () => void;
-
useStateFromStores: UseStateFromStores;
-
useStateFromStoresArray: UseStateFromStoresArray;
-
useStateFromStoresObject: UseStateFromStoresObject;
-
};
-
-
interface UseStateFromStores {
-
<T>(
-
stores: Store<any>[],
-
callback: () => T,
-
deps?: DependencyList,
-
shouldUpdate?: (oldState: T, newState: T) => boolean
-
): T;
-
}
-
-
interface UseStateFromStoresArray {
-
<T>(stores: Store<any>[], callback: () => T, deps?: DependencyList): T;
-
}
-
-
interface UseStateFromStoresObject {
-
<T>(stores: Store<any>[], callback: () => T, deps?: DependencyList): T;
-
}
+34 -18
packages/types/src/discord/require.ts
···
-
import {
-
Spacepack,
-
CommonReact,
-
CommonFlux,
-
Settings,
-
CommonComponents,
-
CommonFluxDispatcher
-
} from "../coreExtensions";
+
import { AppPanels } from "../coreExtensions/appPanels";
+
import { Commands } from "../coreExtensions/commands";
+
import { ErrorBoundary, Icons } from "../coreExtensions/common";
+
import { DMList, MemberList, Messages } from "../coreExtensions/componentEditor";
+
import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu";
+
import { Markdown } from "../coreExtensions/markdown";
+
import { Moonbase } from "../coreExtensions/moonbase";
+
import { Notices } from "../coreExtensions/notices";
+
import { Settings } from "../coreExtensions/settings";
+
import { Spacepack } from "../coreExtensions/spacepack";
declare function WebpackRequire(id: string): any;
-
declare function WebpackRequire(id: "spacepack_spacepack"): {
-
default: Spacepack;
-
spacepack: Spacepack;
-
};
-
declare function WebpackRequire(id: "common_components"): CommonComponents;
-
declare function WebpackRequire(id: "common_flux"): CommonFlux;
-
declare function WebpackRequire(
-
id: "common_fluxDispatcher"
-
): CommonFluxDispatcher;
-
declare function WebpackRequire(id: "common_react"): CommonReact;
+
declare function WebpackRequire(id: "appPanels_appPanels"): AppPanels;
+
+
declare function WebpackRequire(id: "commands_commands"): Commands;
+
+
declare function WebpackRequire(id: "common_ErrorBoundary"): ErrorBoundary;
+
declare function WebpackRequire(id: "common_icons"): Icons;
+
+
declare function WebpackRequire(id: "componentEditor_dmList"): DMList;
+
declare function WebpackRequire(id: "componentEditor_memberList"): MemberList;
+
declare function WebpackRequire(id: "componentEditor_messages"): Messages;
+
+
declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser;
+
declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;
+
+
declare function WebpackRequire(id: "markdown_markdown"): Markdown;
+
+
declare function WebpackRequire(id: "moonbase_moonbase"): Moonbase;
+
+
declare function WebpackRequire(id: "notices_notices"): Notices;
declare function WebpackRequire(id: "settings_settings"): {
Settings: Settings;
default: Settings;
+
};
+
+
declare function WebpackRequire(id: "spacepack_spacepack"): {
+
default: Spacepack;
+
spacepack: Spacepack;
};
export default WebpackRequire;
+10 -16
packages/types/src/discord/webpack.ts
···
import WebpackRequire from "./require";
+
import { WebpackRequire as MappingsWebpackRequire } from "@moonlight-mod/mappings";
-
export type WebpackRequireType = typeof WebpackRequire & {
-
c: Record<string, WebpackModule>;
-
m: Record<string, WebpackModuleFunc>;
-
el: (module: number | string) => Promise<void>;
-
};
+
export type WebpackRequireType = typeof MappingsWebpackRequire &
+
typeof WebpackRequire & {
+
c: Record<string, WebpackModule>;
+
m: Record<string, WebpackModuleFunc>;
+
e: (module: number | string) => Promise<void>;
+
};
export type WebpackModule = {
id: string | number;
-
loaded: boolean;
+
loaded?: boolean;
exports: any;
};
-
export type WebpackModuleFunc = ((
-
module: any,
-
exports: any,
-
require: WebpackRequireType
-
) => void) & {
+
export type WebpackModuleFunc = ((module: any, exports: any, require: WebpackRequireType) => void) & {
__moonlight?: boolean;
};
-
export type WebpackJsonpEntry = [
-
number[],
-
{ [id: string]: WebpackModuleFunc },
-
(require: WebpackRequireType) => any
-
];
+
export type WebpackJsonpEntry = [number[], { [id: string]: WebpackModuleFunc }, (require: WebpackRequireType) => any];
export type WebpackJsonp = WebpackJsonpEntry[] & {
push: {
+118 -5
packages/types/src/extension.ts
···
};
export type ExtensionManifest = {
+
$schema?: string;
+
+
/**
+
* A unique identifier for your extension.
+
*/
id: string;
+
+
/**
+
* A version string for your extension - doesn't need to follow a specific format. Required for publishing.
+
*/
version?: string;
+
/**
+
* The API level this extension targets. If it does not match the current version, the extension will not be loaded.
+
*/
+
apiLevel?: number;
+
+
/**
+
* Which environment this extension is capable of running in.
+
*/
+
environment?: ExtensionEnvironment;
+
+
/**
+
* Metadata about your extension for use in Moonbase.
+
*/
meta?: {
+
/**
+
* A human friendly name for your extension as a proper noun.
+
*/
name?: string;
+
+
/**
+
* A short tagline that appears below the name.
+
*/
tagline?: string;
+
+
/**
+
* A longer description that can use Markdown.
+
*/
description?: string;
+
+
/**
+
* List of authors that worked on this extension - accepts string or object with ID.
+
*/
authors?: ExtensionAuthor[];
-
deprecated?: boolean;
+
+
/**
+
* A list of tags that are relevant to the extension.
+
*/
tags?: ExtensionTag[];
+
+
/**
+
* The URL to the source repository.
+
*/
source?: string;
+
+
/**
+
* A donation link (or other method of support). If you don't want financial contributions, consider putting your favorite charity here!
+
*/
+
donate?: string;
+
+
/**
+
* A changelog to show in Moonbase.
+
* Moonbase will show the changelog for the latest version, even if it is not installed.
+
*/
+
changelog?: string;
+
+
/**
+
* Whether the extension is deprecated and no longer receiving updates.
+
*/
+
deprecated?: boolean;
};
+
/**
+
* A list of extension IDs that are required for the extension to load.
+
*/
dependencies?: string[];
+
+
/**
+
* A list of extension IDs that the user may want to install.
+
*/
suggested?: string[];
-
incompatible?: string[]; // TODO: implement
+
/**
+
* A list of extension IDs that the extension is incompatible with.
+
* If two incompatible extensions are enabled, one of them will not load.
+
*/
+
incompatible?: string[];
+
+
/**
+
* A list of settings for your extension, where the key is the settings ID.
+
*/
settings?: Record<string, ExtensionSettingsManifest>;
+
+
/**
+
* A list of URLs to bypass CORS for.
+
* This is implemented by checking if the start of the URL matches.
+
* @example https://moonlight-mod.github.io/
+
*/
cors?: string[];
+
+
/**
+
* A list of URLs to block all requests to.
+
* This is implemented by checking if the start of the URL matches.
+
* @example https://moonlight-mod.github.io/
+
*/
+
blocked?: string[];
+
+
/**
+
* A mapping from CSP directives to URLs to allow.
+
* @example { "script-src": ["https://example.com"] }
+
*/
+
csp?: Record<string, string[]>;
};
+
export enum ExtensionEnvironment {
+
/**
+
* The extension will run on both platforms, the host/native modules MAY be loaded
+
*/
+
Both = "both",
+
+
/**
+
* Extension will run on desktop only, the host/native modules are guaranteed to load
+
*/
+
Desktop = "desktop",
+
+
/**
+
* Currently equivalent to Both
+
*/
+
Web = "web"
+
}
+
export enum ExtensionLoadSource {
Developer,
Core,
···
webpackModules?: Record<string, string>;
nodePath?: string;
hostPath?: string;
+
style?: string;
};
};
···
export type Patch = {
find: PatchMatch;
replace: PatchReplace | PatchReplace[];
+
hardFail?: boolean; // if any patches fail, all fail
prerequisite?: () => boolean;
};
export type ExplicitExtensionDependency = {
-
ext: string;
+
ext?: string;
id: string;
};
···
export type ExtensionWebExports = {
patches?: Patch[];
webpackModules?: Record<string, ExtensionWebpackModule>;
+
styles?: string[];
};
export type IdentifiedPatch = Patch & {
···
id: number;
};
-
export type IdentifiedWebpackModule = ExtensionWebpackModule &
-
ExplicitExtensionDependency;
+
export type IdentifiedWebpackModule = ExtensionWebpackModule & ExplicitExtensionDependency;
+19
packages/types/src/fs.ts
···
+
export type MoonlightFS = {
+
readFile: (path: string) => Promise<Uint8Array>;
+
readFileString: (path: string) => Promise<string>;
+
writeFile: (path: string, data: Uint8Array) => Promise<void>;
+
writeFileString: (path: string, data: string) => Promise<void>;
+
unlink: (path: string) => Promise<void>;
+
+
readdir: (path: string) => Promise<string[]>;
+
mkdir: (path: string) => Promise<void>;
+
rmdir: (path: string) => Promise<void>;
+
+
exists: (path: string) => Promise<boolean>;
+
isFile: (path: string) => Promise<boolean>;
+
isDir: (path: string) => Promise<boolean>;
+
+
join: (...parts: string[]) => string;
+
dirname: (path: string) => string;
+
basename: (path: string) => string;
+
};
+68 -14
packages/types/src/globals.ts
···
-
import { Logger } from "./logger";
-
import { Config, ConfigExtension } from "./config";
-
import {
-
DetectedExtension,
-
IdentifiedPatch,
-
ProcessedExtensions
-
} from "./extension";
-
import EventEmitter from "events";
+
import type { Logger } from "./logger";
+
import type { Config, ConfigExtension } from "./config";
+
import type { DetectedExtension, IdentifiedPatch, IdentifiedWebpackModule, ProcessedExtensions } from "./extension";
+
import type EventEmitter from "events";
+
import type LunAST from "@moonlight-mod/lunast";
+
import type Moonmap from "@moonlight-mod/moonmap";
+
import type {
+
WebEventPayloads,
+
WebEventType,
+
MoonlightEventEmitter,
+
NodeEventType,
+
NodeEventPayloads
+
} from "./core/event";
+
import type { MoonlightFS } from "./fs";
export type MoonlightHost = {
-
asarPath: string;
config: Config;
-
events: EventEmitter;
extensions: DetectedExtension[];
processedExtensions: ProcessedExtensions;
+
asarPath: string;
+
events: EventEmitter;
+
+
version: string;
+
branch: MoonlightBranch;
getConfig: (ext: string) => ConfigExtension["config"];
+
getConfigPath: () => Promise<string>;
getConfigOption: <T>(ext: string, name: string) => T | undefined;
+
setConfigOption: <T>(ext: string, name: string, value: T) => void;
+
writeConfig: (config: Config) => Promise<void>;
+
getLogger: (id: string) => Logger;
+
getMoonlightDir: () => string;
+
getExtensionDir: (ext: string) => string;
};
export type MoonlightNode = {
···
extensions: DetectedExtension[];
processedExtensions: ProcessedExtensions;
nativesCache: Record<string, any>;
+
isBrowser: boolean;
+
events: MoonlightEventEmitter<NodeEventType, NodeEventPayloads>;
+
+
version: string;
+
branch: MoonlightBranch;
getConfig: (ext: string) => ConfigExtension["config"];
getConfigOption: <T>(ext: string, name: string) => T | undefined;
+
setConfigOption: <T>(ext: string, name: string, value: T) => Promise<void>;
+
writeConfig: (config: Config) => Promise<void>;
+
getNatives: (ext: string) => any | undefined;
getLogger: (id: string) => Logger;
+
getMoonlightDir: () => string;
+
getExtensionDir: (ext: string) => string;
+
};
-
getExtensionDir: (ext: string) => string;
-
writeConfig: (config: Config) => void;
+
export type MoonlightNodeSandboxed = {
+
fs: MoonlightFS;
+
addCors: (url: string) => void;
+
addBlocked: (url: string) => void;
};
export type MoonlightWeb = {
+
patched: Map<string, Set<string>>;
unpatched: Set<IdentifiedPatch>;
+
pendingModules: Set<IdentifiedWebpackModule>;
enabledExtensions: Set<string>;
+
events: MoonlightEventEmitter<WebEventType, WebEventPayloads>;
+
patchingInternals: {
+
onModuleLoad: (moduleId: string | string[], callback: (moduleId: string) => void) => void;
+
registerPatch: (patch: IdentifiedPatch) => void;
+
registerWebpackModule: (module: IdentifiedWebpackModule) => void;
+
};
+
localStorage: Storage;
-
getConfig: (ext: string) => ConfigExtension["config"];
-
getConfigOption: <T>(ext: string, name: string) => T | undefined;
+
version: string;
+
branch: MoonlightBranch;
+
apiLevel: number;
+
+
// Re-exports for ease of use
+
getConfig: MoonlightNode["getConfig"];
+
getConfigOption: MoonlightNode["getConfigOption"];
+
setConfigOption: MoonlightNode["setConfigOption"];
+
writeConfig: MoonlightNode["writeConfig"];
+
getNatives: (ext: string) => any | undefined;
getLogger: (id: string) => Logger;
+
+
lunast: LunAST;
+
moonmap: Moonmap;
};
export enum MoonlightEnv {
···
NodePreload = "node-preload",
WebPreload = "web-preload"
}
+
+
export enum MoonlightBranch {
+
STABLE = "stable",
+
NIGHTLY = "nightly",
+
DEV = "dev"
+
}
+62 -16
packages/types/src/import.d.ts
···
-
declare module "@moonlight-mod/wp/spacepack_spacepack" {
+
declare module "@moonlight-mod/wp/appPanels_appPanels" {
import { CoreExtensions } from "@moonlight-mod/types";
-
export const spacepack: CoreExtensions.Spacepack;
-
export default spacepack;
+
const AppPanels: CoreExtensions.AppPanels.AppPanels;
+
export = AppPanels;
}
-
declare module "@moonlight-mod/wp/common_components" {
+
declare module "@moonlight-mod/wp/commands_commands" {
import { CoreExtensions } from "@moonlight-mod/types";
-
const components: CoreExtensions.CommonComponents;
-
export default components;
+
export const commands: CoreExtensions.Commands.Commands;
+
export default commands;
}
-
declare module "@moonlight-mod/wp/common_flux" {
+
declare module "@moonlight-mod/wp/common_ErrorBoundary" {
import { CoreExtensions } from "@moonlight-mod/types";
-
const Flux: CoreExtensions.CommonFlux;
-
export default Flux;
+
const ErrorBoundary: CoreExtensions.Common.ErrorBoundary;
+
export = ErrorBoundary;
+
}
+
declare module "@moonlight-mod/wp/common_icons" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
export const icons: CoreExtensions.Common.Icons;
+
export default icons;
}
+
declare module "@moonlight-mod/wp/common_stores";
-
declare module "@moonlight-mod/wp/common_fluxDispatcher" {
+
declare module "@moonlight-mod/wp/componentEditor_dmList" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
export const dmList: CoreExtensions.ComponentEditor.DMList;
+
export default dmList;
+
}
+
declare module "@moonlight-mod/wp/componentEditor_memberList" {
import { CoreExtensions } from "@moonlight-mod/types";
-
const Dispatcher: CoreExtensions.CommonFluxDispatcher;
-
export default Dispatcher;
+
export const memberList: CoreExtensions.ComponentEditor.MemberList;
+
export default memberList;
+
}
+
declare module "@moonlight-mod/wp/componentEditor_messages" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
export const message: CoreExtensions.ComponentEditor.Messages;
+
export default message;
}
-
declare module "@moonlight-mod/wp/common_react" {
-
import React from "react";
-
export = React;
+
declare module "@moonlight-mod/wp/contextMenu_evilMenu" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
const EvilParser: CoreExtensions.ContextMenu.EvilItemParser;
+
export = EvilParser;
+
}
+
declare module "@moonlight-mod/wp/contextMenu_contextMenu" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
const ContextMenu: CoreExtensions.ContextMenu.ContextMenu;
+
export = ContextMenu;
+
}
+
+
declare module "@moonlight-mod/wp/markdown_markdown" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
const Markdown: CoreExtensions.Markdown.Markdown;
+
export = Markdown;
+
}
+
+
declare module "@moonlight-mod/wp/moonbase_moonbase" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
const Moonbase: CoreExtensions.Moonbase.Moonbase;
+
export = Moonbase;
+
}
+
+
declare module "@moonlight-mod/wp/notices_notices" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
const Notices: CoreExtensions.Notices.Notices;
+
export = Notices;
}
declare module "@moonlight-mod/wp/settings_settings" {
import { CoreExtensions } from "@moonlight-mod/types";
-
export const Settings: CoreExtensions.Settings;
+
export const Settings: CoreExtensions.Settings.Settings;
export default Settings;
}
+
+
declare module "@moonlight-mod/wp/spacepack_spacepack" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
export const spacepack: CoreExtensions.Spacepack.Spacepack;
+
export default spacepack;
+
}
+14 -8
packages/types/src/index.ts
···
-
/// <reference types="node" />
/// <reference types="standalone-electron-types" />
/// <reference types="react" />
-
/// <reference types="flux" />
/// <reference types="./import" />
+
/// <reference types="./mappings" />
/* eslint-disable no-var */
-
import {
-
MoonlightEnv,
-
MoonlightHost,
-
MoonlightNode,
-
MoonlightWeb
-
} from "./globals";
+
import { MoonlightEnv, MoonlightHost, MoonlightNode, MoonlightNodeSandboxed, MoonlightWeb } from "./globals";
export * from "./discord";
export * from "./config";
···
export * from "./globals";
export * from "./logger";
export * as constants from "./constants";
+
export * from "./fs";
+
+
export type { AST } from "@moonlight-mod/lunast";
+
export { ModuleExport, ModuleExportType } from "@moonlight-mod/moonmap";
declare global {
const MOONLIGHT_ENV: MoonlightEnv;
···
const MOONLIGHT_INJECTOR: boolean;
const MOONLIGHT_NODE_PRELOAD: boolean;
const MOONLIGHT_WEB_PRELOAD: boolean;
+
const MOONLIGHT_BROWSER: boolean;
+
const MOONLIGHT_BRANCH: string;
+
const MOONLIGHT_VERSION: string;
var moonlightHost: MoonlightHost;
var moonlightNode: MoonlightNode;
+
var moonlightNodeSandboxed: MoonlightNodeSandboxed;
var moonlight: MoonlightWeb;
+
var _moonlight_coreExtensionsStr: string;
+
+
var _moonlightBrowserInit: undefined | (() => Promise<void>);
+
var _moonlightWebLoad: undefined | (() => Promise<void>);
}
+888
packages/types/src/mappings.d.ts
···
+
// auto-generated
+
declare module "@moonlight-mod/wp/chroma-js" {}
+
+
declare module "@moonlight-mod/wp/classnames" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["classnames"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/dependency-graph" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const DepGraph: MappedModules["dependency-graph"]["DepGraph"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/Constants" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const ActivityFlags: MappedModules["discord/Constants"]["ActivityFlags"];
+
export const ActivityTypes: MappedModules["discord/Constants"]["ActivityTypes"];
+
export const AnalyticsLocations: MappedModules["discord/Constants"]["AnalyticsLocations"];
+
export const ChannelLayouts: MappedModules["discord/Constants"]["ChannelLayouts"];
+
export const ChannelModes: MappedModules["discord/Constants"]["ChannelModes"];
+
export const ChannelTypes: MappedModules["discord/Constants"]["ChannelTypes"];
+
export const ChannelStreamTypes: MappedModules["discord/Constants"]["ChannelStreamTypes"];
+
export const ComponentActions: MappedModules["discord/Constants"]["ComponentActions"];
+
export const DEFAULT_ROLE_COLOR: MappedModules["discord/Constants"]["DEFAULT_ROLE_COLOR"];
+
export const Endpoints: MappedModules["discord/Constants"]["Endpoints"];
+
export const MessageFlags: MappedModules["discord/Constants"]["MessageFlags"];
+
export const MessageTypes: MappedModules["discord/Constants"]["MessageTypes"];
+
export const Permissions: MappedModules["discord/Constants"]["Permissions"];
+
export const PlatformTypes: MappedModules["discord/Constants"]["PlatformTypes"];
+
export const RelationshipTypes: MappedModules["discord/Constants"]["RelationshipTypes"];
+
export const Routes: MappedModules["discord/Constants"]["Routes"];
+
export const StatusTypes: MappedModules["discord/Constants"]["StatusTypes"];
+
export const Themes: MappedModules["discord/Constants"]["Themes"];
+
export const UserSettingsSections: MappedModules["discord/Constants"]["UserSettingsSections"];
+
export const UserFlags: MappedModules["discord/Constants"]["UserFlags"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/Dispatcher" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/Dispatcher"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/actions/ContextMenuActionCreators" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const closeContextMenu: MappedModules["discord/actions/ContextMenuActionCreators"]["closeContextMenu"];
+
export const openContextMenu: MappedModules["discord/actions/ContextMenuActionCreators"]["openContextMenu"];
+
export const openContextMenuLazy: MappedModules["discord/actions/ContextMenuActionCreators"]["openContextMenuLazy"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/actions/UserSettingsModalActionCreators"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/common/AppStartPerformance" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/common/AppStartPerformance"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/Alerts" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/components/common/Alerts"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/BaseHeaderBar" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const Icon: MappedModules["discord/components/common/BaseHeaderBar"]["Icon"];
+
export const Divider: MappedModules["discord/components/common/BaseHeaderBar"]["Divider"];
+
const _default: MappedModules["discord/components/common/BaseHeaderBar"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/Card" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/components/common/Card"]["default"];
+
export default _default;
+
export const Types: MappedModules["discord/components/common/Card"]["Types"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/FileUpload" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/components/common/FileUpload"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/FormSwitch.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const container: MappedModules["discord/components/common/FormSwitch.css"]["container"];
+
export const labelRow: MappedModules["discord/components/common/FormSwitch.css"]["labelRow"];
+
export const control: MappedModules["discord/components/common/FormSwitch.css"]["control"];
+
export const disabled: MappedModules["discord/components/common/FormSwitch.css"]["disabled"];
+
export const title: MappedModules["discord/components/common/FormSwitch.css"]["title"];
+
export const note: MappedModules["discord/components/common/FormSwitch.css"]["note"];
+
export const disabledText: MappedModules["discord/components/common/FormSwitch.css"]["disabledText"];
+
export const dividerDefault: MappedModules["discord/components/common/FormSwitch.css"]["dividerDefault"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/HeaderBar.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const caret: MappedModules["discord/components/common/HeaderBar.css"]["caret"];
+
export const children: MappedModules["discord/components/common/HeaderBar.css"]["children"];
+
export const clickable: MappedModules["discord/components/common/HeaderBar.css"]["clickable"];
+
export const container: MappedModules["discord/components/common/HeaderBar.css"]["container"];
+
export const divider: MappedModules["discord/components/common/HeaderBar.css"]["divider"];
+
export const dot: MappedModules["discord/components/common/HeaderBar.css"]["dot"];
+
export const hamburger: MappedModules["discord/components/common/HeaderBar.css"]["hamburger"];
+
export const icon: MappedModules["discord/components/common/HeaderBar.css"]["icon"];
+
export const iconBadge: MappedModules["discord/components/common/HeaderBar.css"]["iconBadge"];
+
export const iconBadgeBottom: MappedModules["discord/components/common/HeaderBar.css"]["iconBadgeBottom"];
+
export const iconBadgeTop: MappedModules["discord/components/common/HeaderBar.css"]["iconBadgeTop"];
+
export const iconWrapper: MappedModules["discord/components/common/HeaderBar.css"]["iconWrapper"];
+
export const scrollable: MappedModules["discord/components/common/HeaderBar.css"]["scrollable"];
+
export const selected: MappedModules["discord/components/common/HeaderBar.css"]["selected"];
+
export const themed: MappedModules["discord/components/common/HeaderBar.css"]["themed"];
+
export const themedMobile: MappedModules["discord/components/common/HeaderBar.css"]["themedMobile"];
+
export const title: MappedModules["discord/components/common/HeaderBar.css"]["title"];
+
export const titleWrapper: MappedModules["discord/components/common/HeaderBar.css"]["titleWrapper"];
+
export const toolbar: MappedModules["discord/components/common/HeaderBar.css"]["toolbar"];
+
export const transparent: MappedModules["discord/components/common/HeaderBar.css"]["transparent"];
+
export const upperContainer: MappedModules["discord/components/common/HeaderBar.css"]["upperContainer"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/HelpMessage.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const container: MappedModules["discord/components/common/HelpMessage.css"]["container"];
+
export const icon: MappedModules["discord/components/common/HelpMessage.css"]["icon"];
+
export const iconDiv: MappedModules["discord/components/common/HelpMessage.css"]["iconDiv"];
+
export const text: MappedModules["discord/components/common/HelpMessage.css"]["text"];
+
export const positive: MappedModules["discord/components/common/HelpMessage.css"]["positive"];
+
export const warning: MappedModules["discord/components/common/HelpMessage.css"]["warning"];
+
export const info: MappedModules["discord/components/common/HelpMessage.css"]["info"];
+
export const error: MappedModules["discord/components/common/HelpMessage.css"]["error"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/Image" {}
+
+
declare module "@moonlight-mod/wp/discord/components/common/PanelButton" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/components/common/PanelButton"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/Scroller.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const auto: MappedModules["discord/components/common/Scroller.css"]["auto"];
+
export const content: MappedModules["discord/components/common/Scroller.css"]["content"];
+
export const customTheme: MappedModules["discord/components/common/Scroller.css"]["customTheme"];
+
export const disableScrollAnchor: MappedModules["discord/components/common/Scroller.css"]["disableScrollAnchor"];
+
export const fade: MappedModules["discord/components/common/Scroller.css"]["fade"];
+
export const managedReactiveScroller: MappedModules["discord/components/common/Scroller.css"]["managedReactiveScroller"];
+
export const none: MappedModules["discord/components/common/Scroller.css"]["none"];
+
export const pointerCover: MappedModules["discord/components/common/Scroller.css"]["pointerCover"];
+
export const scrolling: MappedModules["discord/components/common/Scroller.css"]["scrolling"];
+
export const thin: MappedModules["discord/components/common/Scroller.css"]["thin"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/common/index" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const Clickable: MappedModules["discord/components/common/index"]["Clickable"];
+
export const TextInput: MappedModules["discord/components/common/index"]["TextInput"];
+
export const TextArea: MappedModules["discord/components/common/index"]["TextArea"];
+
export const FormDivider: MappedModules["discord/components/common/index"]["FormDivider"];
+
export const FormSection: MappedModules["discord/components/common/index"]["FormSection"];
+
export const FormText: MappedModules["discord/components/common/index"]["FormText"];
+
export const FormTitle: MappedModules["discord/components/common/index"]["FormTitle"];
+
export const FormSwitch: MappedModules["discord/components/common/index"]["FormSwitch"];
+
export const FormItem: MappedModules["discord/components/common/index"]["FormItem"];
+
export const Slider: MappedModules["discord/components/common/index"]["Slider"];
+
export const Switch: MappedModules["discord/components/common/index"]["Switch"];
+
export const Button: MappedModules["discord/components/common/index"]["Button"];
+
export const Tooltip: MappedModules["discord/components/common/index"]["Tooltip"];
+
export const Avatar: MappedModules["discord/components/common/index"]["Avatar"];
+
export const AvatarSizes: MappedModules["discord/components/common/index"]["AvatarSizes"];
+
export const AvatarSizeSpecs: MappedModules["discord/components/common/index"]["AvatarSizeSpecs"];
+
export const Scroller: MappedModules["discord/components/common/index"]["Scroller"];
+
export const Text: MappedModules["discord/components/common/index"]["Text"];
+
export const Heading: MappedModules["discord/components/common/index"]["Heading"];
+
export const Card: MappedModules["discord/components/common/index"]["Card"];
+
export const Popout: MappedModules["discord/components/common/index"]["Popout"];
+
export const Dialog: MappedModules["discord/components/common/index"]["Dialog"];
+
export const Menu: MappedModules["discord/components/common/index"]["Menu"];
+
export const TabBar: MappedModules["discord/components/common/index"]["TabBar"];
+
export const SingleSelect: MappedModules["discord/components/common/index"]["SingleSelect"];
+
export const Select: MappedModules["discord/components/common/index"]["Select"];
+
export const NoticeColors: MappedModules["discord/components/common/index"]["NoticeColors"];
+
export const Notice: MappedModules["discord/components/common/index"]["Notice"];
+
export const NoticeCloseButton: MappedModules["discord/components/common/index"]["NoticeCloseButton"];
+
export const PrimaryCTANoticeButton: MappedModules["discord/components/common/index"]["PrimaryCTANoticeButton"];
+
export const Breadcrumbs: MappedModules["discord/components/common/index"]["Breadcrumbs"];
+
export const Image: MappedModules["discord/components/common/index"]["Image"];
+
export const tokens: MappedModules["discord/components/common/index"]["tokens"];
+
export const useVariableSelect: MappedModules["discord/components/common/index"]["useVariableSelect"];
+
export const useMultiSelect: MappedModules["discord/components/common/index"]["useMultiSelect"];
+
export const multiSelect: MappedModules["discord/components/common/index"]["multiSelect"];
+
export const openModal: MappedModules["discord/components/common/index"]["openModal"];
+
export const openModalLazy: MappedModules["discord/components/common/index"]["openModalLazy"];
+
export const closeModal: MappedModules["discord/components/common/index"]["closeModal"];
+
export const AngleBracketsIcon: MappedModules["discord/components/common/index"]["AngleBracketsIcon"];
+
export const ArrowAngleLeftUpIcon: MappedModules["discord/components/common/index"]["ArrowAngleLeftUpIcon"];
+
export const ArrowAngleRightUpIcon: MappedModules["discord/components/common/index"]["ArrowAngleRightUpIcon"];
+
export const ArrowsUpDownIcon: MappedModules["discord/components/common/index"]["ArrowsUpDownIcon"];
+
export const BookCheckIcon: MappedModules["discord/components/common/index"]["BookCheckIcon"];
+
export const ChannelListIcon: MappedModules["discord/components/common/index"]["ChannelListIcon"];
+
export const ChevronSmallDownIcon: MappedModules["discord/components/common/index"]["ChevronSmallDownIcon"];
+
export const ChevronSmallUpIcon: MappedModules["discord/components/common/index"]["ChevronSmallUpIcon"];
+
export const CircleInformationIcon: MappedModules["discord/components/common/index"]["CircleInformationIcon"];
+
export const CircleWarningIcon: MappedModules["discord/components/common/index"]["CircleWarningIcon"];
+
export const CircleXIcon: MappedModules["discord/components/common/index"]["CircleXIcon"];
+
export const ClydeIcon: MappedModules["discord/components/common/index"]["ClydeIcon"];
+
export const CopyIcon: MappedModules["discord/components/common/index"]["CopyIcon"];
+
export const DownloadIcon: MappedModules["discord/components/common/index"]["DownloadIcon"];
+
export const FullscreenEnterIcon: MappedModules["discord/components/common/index"]["FullscreenEnterIcon"];
+
export const GameControllerIcon: MappedModules["discord/components/common/index"]["GameControllerIcon"];
+
export const GlobeEarthIcon: MappedModules["discord/components/common/index"]["GlobeEarthIcon"];
+
export const HeartIcon: MappedModules["discord/components/common/index"]["HeartIcon"];
+
export const LinkIcon: MappedModules["discord/components/common/index"]["LinkIcon"];
+
export const MaximizeIcon: MappedModules["discord/components/common/index"]["MaximizeIcon"];
+
export const MinusIcon: MappedModules["discord/components/common/index"]["MinusIcon"];
+
export const MobilePhoneIcon: MappedModules["discord/components/common/index"]["MobilePhoneIcon"];
+
export const PauseIcon: MappedModules["discord/components/common/index"]["PauseIcon"];
+
export const PlayIcon: MappedModules["discord/components/common/index"]["PlayIcon"];
+
export const PlusLargeIcon: MappedModules["discord/components/common/index"]["PlusLargeIcon"];
+
export const RetryIcon: MappedModules["discord/components/common/index"]["RetryIcon"];
+
export const ScienceIcon: MappedModules["discord/components/common/index"]["ScienceIcon"];
+
export const ScreenIcon: MappedModules["discord/components/common/index"]["ScreenIcon"];
+
export const StarIcon: MappedModules["discord/components/common/index"]["StarIcon"];
+
export const TrashIcon: MappedModules["discord/components/common/index"]["TrashIcon"];
+
export const WarningIcon: MappedModules["discord/components/common/index"]["WarningIcon"];
+
export const WindowLaunchIcon: MappedModules["discord/components/common/index"]["WindowLaunchIcon"];
+
export const WindowTopOutlineIcon: MappedModules["discord/components/common/index"]["WindowTopOutlineIcon"];
+
export const XLargeIcon: MappedModules["discord/components/common/index"]["XLargeIcon"];
+
export const XSmallIcon: MappedModules["discord/components/common/index"]["XSmallIcon"];
+
export const ConfirmModal: MappedModules["discord/components/common/index"]["ConfirmModal"];
+
export const H: MappedModules["discord/components/common/index"]["H"];
+
export const HelpMessage: MappedModules["discord/components/common/index"]["HelpMessage"];
+
export const ModalCloseButton: MappedModules["discord/components/common/index"]["ModalCloseButton"];
+
export const ModalContent: MappedModules["discord/components/common/index"]["ModalContent"];
+
export const ModalFooter: MappedModules["discord/components/common/index"]["ModalFooter"];
+
export const ModalHeader: MappedModules["discord/components/common/index"]["ModalHeader"];
+
export const ModalRoot: MappedModules["discord/components/common/index"]["ModalRoot"];
+
export const NumberInputStepper: MappedModules["discord/components/common/index"]["NumberInputStepper"];
+
export const SearchableSelect: MappedModules["discord/components/common/index"]["SearchableSelect"];
+
export const createToast: MappedModules["discord/components/common/index"]["createToast"];
+
export const popToast: MappedModules["discord/components/common/index"]["popToast"];
+
export const showToast: MappedModules["discord/components/common/index"]["showToast"];
+
export const useThemeContext: MappedModules["discord/components/common/index"]["useThemeContext"];
+
export const AccessibilityAnnouncer: MappedModules["discord/components/common/index"]["AccessibilityAnnouncer"];
+
export const BackdropStyles: MappedModules["discord/components/common/index"]["BackdropStyles"];
+
export const BadgeShapes: MappedModules["discord/components/common/index"]["BadgeShapes"];
+
export const CardTypes: MappedModules["discord/components/common/index"]["CardTypes"];
+
export const CircleIconButtonColors: MappedModules["discord/components/common/index"]["CircleIconButtonColors"];
+
export const CircleIconButtonSizes: MappedModules["discord/components/common/index"]["CircleIconButtonSizes"];
+
export const FormErrorBlockColors: MappedModules["discord/components/common/index"]["FormErrorBlockColors"];
+
export const FormNoticeImagePositions: MappedModules["discord/components/common/index"]["FormNoticeImagePositions"];
+
export const FormTitleTags: MappedModules["discord/components/common/index"]["FormTitleTags"];
+
export const HelpMessageTypes: MappedModules["discord/components/common/index"]["HelpMessageTypes"];
+
export const ModalSize: MappedModules["discord/components/common/index"]["ModalSize"];
+
export const ModalTransitionState: MappedModules["discord/components/common/index"]["ModalTransitionState"];
+
export const PRETTY_KEYS: MappedModules["discord/components/common/index"]["PRETTY_KEYS"];
+
export const SelectLooks: MappedModules["discord/components/common/index"]["SelectLooks"];
+
export const SpinnerTypes: MappedModules["discord/components/common/index"]["SpinnerTypes"];
+
export const StatusTypes: MappedModules["discord/components/common/index"]["StatusTypes"];
+
export const ToastPosition: MappedModules["discord/components/common/index"]["ToastPosition"];
+
export const ToastType: MappedModules["discord/components/common/index"]["ToastType"];
+
export const TransitionStates: MappedModules["discord/components/common/index"]["TransitionStates"];
+
export const DEFAULT_MODAL_CONTEXT: MappedModules["discord/components/common/index"]["DEFAULT_MODAL_CONTEXT"];
+
export const LOW_SATURATION_THRESHOLD: MappedModules["discord/components/common/index"]["LOW_SATURATION_THRESHOLD"];
+
export const LayerClassName: MappedModules["discord/components/common/index"]["LayerClassName"];
+
export const POPOUT_MODAL_CONTEXT: MappedModules["discord/components/common/index"]["POPOUT_MODAL_CONTEXT"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/components/modals/ConfirmModal" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/components/modals/ConfirmModal"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/lib/BaseRecord" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/lib/BaseRecord"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/lib/web/Storage" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const ObjectStorage: MappedModules["discord/lib/web/Storage"]["ObjectStorage"];
+
export const impl: MappedModules["discord/lib/web/Storage"]["impl"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/build_overrides/web/BuildOverride.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const wrapper: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["wrapper"];
+
export const titleRegion: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["titleRegion"];
+
export const title: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["title"];
+
export const infoIcon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["infoIcon"];
+
export const copyLink: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copyLink"];
+
export const copied: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copied"];
+
export const copyLinkIcon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copyLinkIcon"];
+
export const content: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["content"];
+
export const infoLink: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["infoLink"];
+
export const buildInfo: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buildInfo"];
+
export const button: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["button"];
+
export const buttonSize: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buttonSize"];
+
export const subHead: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["subHead"];
+
export const icon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["icon"];
+
export const buildDetails: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buildDetails"];
+
export const barLoader: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["barLoader"];
+
export const barTitle: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["barTitle"];
+
export const buttonLoader: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buttonLoader"];
+
export const disabledButtonOverride: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["disabledButtonOverride"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const header: MappedModules["discord/modules/discovery/web/Discovery.css"]["header"];
+
export const headerImage: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImage"];
+
export const headerImageSimple: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImageSimple"];
+
export const headerImageBG: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImageBG"];
+
export const searchTitle: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchTitle"];
+
export const searchSubtitle: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchSubtitle"];
+
export const headerContentWrapper: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContentWrapper"];
+
export const headerContent: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContent"];
+
export const headerContentSmall: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContentSmall"];
+
export const searchBox: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchBox"];
+
export const searchBoxInput: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchBoxInput"];
+
export const closeIcon: MappedModules["discord/modules/discovery/web/Discovery.css"]["closeIcon"];
+
export const searchIcon: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchIcon"];
+
export const tabBar: MappedModules["discord/modules/discovery/web/Discovery.css"]["tabBar"];
+
export const tabBarItem: MappedModules["discord/modules/discovery/web/Discovery.css"]["tabBarItem"];
+
export const sectionHeader: MappedModules["discord/modules/discovery/web/Discovery.css"]["sectionHeader"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/Forums.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const container: MappedModules["discord/modules/forums/web/Forums.css"]["container"];
+
export const uploadArea: MappedModules["discord/modules/forums/web/Forums.css"]["uploadArea"];
+
export const label: MappedModules["discord/modules/forums/web/Forums.css"]["label"];
+
export const content: MappedModules["discord/modules/forums/web/Forums.css"]["content"];
+
export const noListContainer: MappedModules["discord/modules/forums/web/Forums.css"]["noListContainer"];
+
export const list: MappedModules["discord/modules/forums/web/Forums.css"]["list"];
+
export const grid: MappedModules["discord/modules/forums/web/Forums.css"]["grid"];
+
export const headerRow: MappedModules["discord/modules/forums/web/Forums.css"]["headerRow"];
+
export const card: MappedModules["discord/modules/forums/web/Forums.css"]["card"];
+
export const columnsSpan: MappedModules["discord/modules/forums/web/Forums.css"]["columnsSpan"];
+
export const emptyStateRow: MappedModules["discord/modules/forums/web/Forums.css"]["emptyStateRow"];
+
export const newMemberBanner: MappedModules["discord/modules/forums/web/Forums.css"]["newMemberBanner"];
+
export const gridViewBanner: MappedModules["discord/modules/forums/web/Forums.css"]["gridViewBanner"];
+
export const placeholder: MappedModules["discord/modules/forums/web/Forums.css"]["placeholder"];
+
export const mainCard: MappedModules["discord/modules/forums/web/Forums.css"]["mainCard"];
+
export const emptyMainCard: MappedModules["discord/modules/forums/web/Forums.css"]["emptyMainCard"];
+
export const outOfDate: MappedModules["discord/modules/forums/web/Forums.css"]["outOfDate"];
+
export const header: MappedModules["discord/modules/forums/web/Forums.css"]["header"];
+
export const matchingPostsRow: MappedModules["discord/modules/forums/web/Forums.css"]["matchingPostsRow"];
+
export const headerWithMatchingPosts: MappedModules["discord/modules/forums/web/Forums.css"]["headerWithMatchingPosts"];
+
export const noForm: MappedModules["discord/modules/forums/web/Forums.css"]["noForm"];
+
export const sortContainer: MappedModules["discord/modules/forums/web/Forums.css"]["sortContainer"];
+
export const sort: MappedModules["discord/modules/forums/web/Forums.css"]["sort"];
+
export const sortPopout: MappedModules["discord/modules/forums/web/Forums.css"]["sortPopout"];
+
export const archivedDividerRow: MappedModules["discord/modules/forums/web/Forums.css"]["archivedDividerRow"];
+
export const archivedDivider: MappedModules["discord/modules/forums/web/Forums.css"]["archivedDivider"];
+
export const newPostsButton: MappedModules["discord/modules/forums/web/Forums.css"]["newPostsButton"];
+
export const loadingCard: MappedModules["discord/modules/forums/web/Forums.css"]["loadingCard"];
+
export const enterIcon: MappedModules["discord/modules/forums/web/Forums.css"]["enterIcon"];
+
export const warnIcon: MappedModules["discord/modules/forums/web/Forums.css"]["warnIcon"];
+
export const searchIcon: MappedModules["discord/modules/forums/web/Forums.css"]["searchIcon"];
+
export const missingReadHistoryPermission: MappedModules["discord/modules/forums/web/Forums.css"]["missingReadHistoryPermission"];
+
export const divider: MappedModules["discord/modules/forums/web/Forums.css"]["divider"];
+
export const tagsContainer: MappedModules["discord/modules/forums/web/Forums.css"]["tagsContainer"];
+
export const filterIcon: MappedModules["discord/modules/forums/web/Forums.css"]["filterIcon"];
+
export const tagList: MappedModules["discord/modules/forums/web/Forums.css"]["tagList"];
+
export const tagListInner: MappedModules["discord/modules/forums/web/Forums.css"]["tagListInner"];
+
export const tag: MappedModules["discord/modules/forums/web/Forums.css"]["tag"];
+
export const tagsButton: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButton"];
+
export const tagsButtonInner: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonInner"];
+
export const tagsButtonPlaceholder: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonPlaceholder"];
+
export const tagsButtonWithCount: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonWithCount"];
+
export const sortDropdown: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdown"];
+
export const sortDropdownInner: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdownInner"];
+
export const sortDropdownText: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdownText"];
+
export const clear: MappedModules["discord/modules/forums/web/Forums.css"]["clear"];
+
export const matchingPosts: MappedModules["discord/modules/forums/web/Forums.css"]["matchingPosts"];
+
export const startPostHelp: MappedModules["discord/modules/forums/web/Forums.css"]["startPostHelp"];
+
export const tagsSpacer: MappedModules["discord/modules/forums/web/Forums.css"]["tagsSpacer"];
+
export const keyboardShortcut: MappedModules["discord/modules/forums/web/Forums.css"]["keyboardShortcut"];
+
export const key: MappedModules["discord/modules/forums/web/Forums.css"]["key"];
+
export const countContainer: MappedModules["discord/modules/forums/web/Forums.css"]["countContainer"];
+
export const countText: MappedModules["discord/modules/forums/web/Forums.css"]["countText"];
+
export const optInNotice: MappedModules["discord/modules/forums/web/Forums.css"]["optInNotice"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/Header.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const container: MappedModules["discord/modules/forums/web/Header.css"]["container"];
+
export const header: MappedModules["discord/modules/forums/web/Header.css"]["header"];
+
export const headerLeft: MappedModules["discord/modules/forums/web/Header.css"]["headerLeft"];
+
export const headerText: MappedModules["discord/modules/forums/web/Header.css"]["headerText"];
+
export const countContainer: MappedModules["discord/modules/forums/web/Header.css"]["countContainer"];
+
export const countText: MappedModules["discord/modules/forums/web/Header.css"]["countText"];
+
export const tagContainer: MappedModules["discord/modules/forums/web/Header.css"]["tagContainer"];
+
export const tag: MappedModules["discord/modules/forums/web/Header.css"]["tag"];
+
export const clear: MappedModules["discord/modules/forums/web/Header.css"]["clear"];
+
export const row: MappedModules["discord/modules/forums/web/Header.css"]["row"];
+
export const separator: MappedModules["discord/modules/forums/web/Header.css"]["separator"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/SortMenu.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const container: MappedModules["discord/modules/forums/web/SortMenu.css"]["container"];
+
export const clearText: MappedModules["discord/modules/forums/web/SortMenu.css"]["clearText"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/Tag" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/modules/forums/web/Tag"]["default"];
+
export default _default;
+
export const TagBar: MappedModules["discord/modules/forums/web/Tag"]["TagBar"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const addButton: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["addButton"];
+
export const container: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["container"];
+
export const emptyRowContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["emptyRowContainer"];
+
export const emptyRowText: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["emptyRowText"];
+
export const headerContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["headerContainer"];
+
export const list: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["list"];
+
export const memberDetails: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["memberDetails"];
+
export const memberRow: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["memberRow"];
+
export const removeButton: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButton"];
+
export const removeButtonContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButtonContainer"];
+
export const removeButtonDisabled: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButtonDisabled"];
+
export const removeTip: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeTip"];
+
export const searchContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["searchContainer"];
+
export const searchWarning: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["searchWarning"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCard.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const card: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["card"];
+
export const inModal: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["inModal"];
+
export const cardHeader: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["cardHeader"];
+
export const title: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["title"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCardItem.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const icon: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["icon"];
+
export const identifier: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["identifier"];
+
export const item: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["item"];
+
export const statusContainer: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusContainer"];
+
export const statusLine: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusLine"];
+
export const statusIcon: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusIcon"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/SearchSection.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const container: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["container"];
+
export const headerContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["headerContainer"];
+
export const searchContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["searchContainer"];
+
export const searchWarning: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["searchWarning"];
+
export const addButton: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["addButton"];
+
export const memberRow: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["memberRow"];
+
export const emptyRowContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["emptyRowContainer"];
+
export const emptyRowText: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["emptyRowText"];
+
export const memberDetails: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["memberDetails"];
+
export const list: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["list"];
+
export const removeButtonContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButtonContainer"];
+
export const removeButton: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButton"];
+
export const removeButtonDisabled: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButtonDisabled"];
+
export const removeTip: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeTip"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/guild_sidebar/web/CategoryChannel.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const containerDefault: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDefault"];
+
export const containerDragBefore: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDragBefore"];
+
export const containerDragAfter: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDragAfter"];
+
export const addButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["addButton"];
+
export const forceVisible: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["forceVisible"];
+
export const iconVisibility: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["iconVisibility"];
+
export const addButtonIcon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["addButtonIcon"];
+
export const wrapper: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["wrapper"];
+
export const wrapperStatic: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["wrapperStatic"];
+
export const clickable: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["clickable"];
+
export const children: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["children"];
+
export const mainContent: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["mainContent"];
+
export const icon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["icon"];
+
export const collapsed: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["collapsed"];
+
export const muted: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["muted"];
+
export const name: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["name"];
+
export const dismissWrapper: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismissWrapper"];
+
export const dismissButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismissButton"];
+
export const dismiss: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismiss"];
+
export const voiceChannelsButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["voiceChannelsButton"];
+
export const voiceChannelsToggleIcon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["voiceChannelsToggleIcon"];
+
export const refreshVoiceChannelsButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["refreshVoiceChannelsButton"];
+
export const refreshVoiceChannelsButtonInner: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["refreshVoiceChannelsButtonInner"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/markup/MarkupUtils" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/modules/markup/MarkupUtils"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/menus/web/Menu" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const MenuSpinner: MappedModules["discord/modules/menus/web/Menu"]["MenuSpinner"];
+
export const Menu: MappedModules["discord/modules/menus/web/Menu"]["Menu"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/messages/web/Markup.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const markup: MappedModules["discord/modules/messages/web/Markup.css"]["markup"];
+
export const inlineFormat: MappedModules["discord/modules/messages/web/Markup.css"]["inlineFormat"];
+
export const codeContainer: MappedModules["discord/modules/messages/web/Markup.css"]["codeContainer"];
+
export const codeActions: MappedModules["discord/modules/messages/web/Markup.css"]["codeActions"];
+
export const blockquoteContainer: MappedModules["discord/modules/messages/web/Markup.css"]["blockquoteContainer"];
+
export const blockquoteDivider: MappedModules["discord/modules/messages/web/Markup.css"]["blockquoteDivider"];
+
export const slateBlockquoteContainer: MappedModules["discord/modules/messages/web/Markup.css"]["slateBlockquoteContainer"];
+
export const roleMention: MappedModules["discord/modules/messages/web/Markup.css"]["roleMention"];
+
export const rolePopout: MappedModules["discord/modules/messages/web/Markup.css"]["rolePopout"];
+
export const roleHeader: MappedModules["discord/modules/messages/web/Markup.css"]["roleHeader"];
+
export const roleScroller: MappedModules["discord/modules/messages/web/Markup.css"]["roleScroller"];
+
export const timestamp: MappedModules["discord/modules/messages/web/Markup.css"]["timestamp"];
+
export const timestampTooltip: MappedModules["discord/modules/messages/web/Markup.css"]["timestampTooltip"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/messages/web/Message.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const wrapper: MappedModules["discord/modules/messages/web/Message.css"]["wrapper"];
+
export const compact: MappedModules["discord/modules/messages/web/Message.css"]["compact"];
+
export const cozy: MappedModules["discord/modules/messages/web/Message.css"]["cozy"];
+
export const contentOnly: MappedModules["discord/modules/messages/web/Message.css"]["contentOnly"];
+
export const repliedMessage: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessage"];
+
export const threadMessageAccessory: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessory"];
+
export const executedCommand: MappedModules["discord/modules/messages/web/Message.css"]["executedCommand"];
+
export const latin12CompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["latin12CompactTimeStamp"];
+
export const latin24CompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["latin24CompactTimeStamp"];
+
export const asianCompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["asianCompactTimeStamp"];
+
export const contextCommandMessage: MappedModules["discord/modules/messages/web/Message.css"]["contextCommandMessage"];
+
export const messageSpine: MappedModules["discord/modules/messages/web/Message.css"]["messageSpine"];
+
export const repliedMessageClickableSpine: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageClickableSpine"];
+
export const repliedMessageContentHovered: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageContentHovered"];
+
export const threadMessageAccessoryAvatar: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryAvatar"];
+
export const replyAvatar: MappedModules["discord/modules/messages/web/Message.css"]["replyAvatar"];
+
export const replyBadge: MappedModules["discord/modules/messages/web/Message.css"]["replyBadge"];
+
export const executedCommandAvatar: MappedModules["discord/modules/messages/web/Message.css"]["executedCommandAvatar"];
+
export const replyChatIconContainer: MappedModules["discord/modules/messages/web/Message.css"]["replyChatIconContainer"];
+
export const replyIcon: MappedModules["discord/modules/messages/web/Message.css"]["replyIcon"];
+
export const clanTagChiplet: MappedModules["discord/modules/messages/web/Message.css"]["clanTagChiplet"];
+
export const userJoinSystemMessageIcon: MappedModules["discord/modules/messages/web/Message.css"]["userJoinSystemMessageIcon"];
+
export const ticketIcon: MappedModules["discord/modules/messages/web/Message.css"]["ticketIcon"];
+
export const commandIcon: MappedModules["discord/modules/messages/web/Message.css"]["commandIcon"];
+
export const username: MappedModules["discord/modules/messages/web/Message.css"]["username"];
+
export const roleDot: MappedModules["discord/modules/messages/web/Message.css"]["roleDot"];
+
export const commandName: MappedModules["discord/modules/messages/web/Message.css"]["commandName"];
+
export const appsIcon: MappedModules["discord/modules/messages/web/Message.css"]["appsIcon"];
+
export const appLauncherOnboardingCommandName: MappedModules["discord/modules/messages/web/Message.css"]["appLauncherOnboardingCommandName"];
+
export const targetUsername: MappedModules["discord/modules/messages/web/Message.css"]["targetUsername"];
+
export const executedCommandSeparator: MappedModules["discord/modules/messages/web/Message.css"]["executedCommandSeparator"];
+
export const repliedTextPreview: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextPreview"];
+
export const threadMessageAccessoryPreview: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryPreview"];
+
export const repliedTextContent: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContent"];
+
export const clickable: MappedModules["discord/modules/messages/web/Message.css"]["clickable"];
+
export const repliedMessageClickableSpineHovered: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageClickableSpineHovered"];
+
export const threadMessageAccessoryContent: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContent"];
+
export const repliedTextPlaceholder: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextPlaceholder"];
+
export const threadMessageAccessoryPlaceholder: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryPlaceholder"];
+
export const repliedTextContentTrailingIcon: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContentTrailingIcon"];
+
export const threadMessageAccessoryContentTrailingIcon: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContentTrailingIcon"];
+
export const repliedTextContentLeadingIcon: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContentLeadingIcon"];
+
export const threadMessageAccessoryContentLeadingIcon: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContentLeadingIcon"];
+
export const contents: MappedModules["discord/modules/messages/web/Message.css"]["contents"];
+
export const zalgo: MappedModules["discord/modules/messages/web/Message.css"]["zalgo"];
+
export const messageContent: MappedModules["discord/modules/messages/web/Message.css"]["messageContent"];
+
export const header: MappedModules["discord/modules/messages/web/Message.css"]["header"];
+
export const buttonContainer: MappedModules["discord/modules/messages/web/Message.css"]["buttonContainer"];
+
export const avatar: MappedModules["discord/modules/messages/web/Message.css"]["avatar"];
+
export const avatarDecoration: MappedModules["discord/modules/messages/web/Message.css"]["avatarDecoration"];
+
export const roleIcon: MappedModules["discord/modules/messages/web/Message.css"]["roleIcon"];
+
export const timestamp: MappedModules["discord/modules/messages/web/Message.css"]["timestamp"];
+
export const timestampInline: MappedModules["discord/modules/messages/web/Message.css"]["timestampInline"];
+
export const alt: MappedModules["discord/modules/messages/web/Message.css"]["alt"];
+
export const timestampTooltip: MappedModules["discord/modules/messages/web/Message.css"]["timestampTooltip"];
+
export const timestampVisibleOnHover: MappedModules["discord/modules/messages/web/Message.css"]["timestampVisibleOnHover"];
+
export const nitroAuthorBadgeTootip: MappedModules["discord/modules/messages/web/Message.css"]["nitroAuthorBadgeTootip"];
+
export const headerText: MappedModules["discord/modules/messages/web/Message.css"]["headerText"];
+
export const hasRoleIcon: MappedModules["discord/modules/messages/web/Message.css"]["hasRoleIcon"];
+
export const hasBadges: MappedModules["discord/modules/messages/web/Message.css"]["hasBadges"];
+
export const botTagCompact: MappedModules["discord/modules/messages/web/Message.css"]["botTagCompact"];
+
export const botTagCozy: MappedModules["discord/modules/messages/web/Message.css"]["botTagCozy"];
+
export const nitroBadgeSvg: MappedModules["discord/modules/messages/web/Message.css"]["nitroBadgeSvg"];
+
export const nitroAuthorBadgeContainer: MappedModules["discord/modules/messages/web/Message.css"]["nitroAuthorBadgeContainer"];
+
export const separator: MappedModules["discord/modules/messages/web/Message.css"]["separator"];
+
export const hasThread: MappedModules["discord/modules/messages/web/Message.css"]["hasThread"];
+
export const isSystemMessage: MappedModules["discord/modules/messages/web/Message.css"]["isSystemMessage"];
+
export const hasReply: MappedModules["discord/modules/messages/web/Message.css"]["hasReply"];
+
export const markupRtl: MappedModules["discord/modules/messages/web/Message.css"]["markupRtl"];
+
export const isSending: MappedModules["discord/modules/messages/web/Message.css"]["isSending"];
+
export const isFailed: MappedModules["discord/modules/messages/web/Message.css"]["isFailed"];
+
export const isUnsupported: MappedModules["discord/modules/messages/web/Message.css"]["isUnsupported"];
+
export const edited: MappedModules["discord/modules/messages/web/Message.css"]["edited"];
+
export const communicationDisabled: MappedModules["discord/modules/messages/web/Message.css"]["communicationDisabled"];
+
export const compactCommunicationDisabled: MappedModules["discord/modules/messages/web/Message.css"]["compactCommunicationDisabled"];
+
export const communicationDisabledOpacity: MappedModules["discord/modules/messages/web/Message.css"]["communicationDisabledOpacity"];
+
export const badgesContainer: MappedModules["discord/modules/messages/web/Message.css"]["badgesContainer"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/modals/Modals" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const closeAllModals: MappedModules["discord/modules/modals/Modals"]["closeAllModals"];
+
export const closeAllModalsForContext: MappedModules["discord/modules/modals/Modals"]["closeAllModalsForContext"];
+
export const closeModal: MappedModules["discord/modules/modals/Modals"]["closeModal"];
+
export const getInteractingModalContext: MappedModules["discord/modules/modals/Modals"]["getInteractingModalContext"];
+
export const hasAnyModalOpen: MappedModules["discord/modules/modals/Modals"]["hasAnyModalOpen"];
+
export const hasAnyModalOpenSelector: MappedModules["discord/modules/modals/Modals"]["hasAnyModalOpenSelector"];
+
export const hasModalOpen: MappedModules["discord/modules/modals/Modals"]["hasModalOpen"];
+
export const hasModalOpenSelector: MappedModules["discord/modules/modals/Modals"]["hasModalOpenSelector"];
+
export const openModal: MappedModules["discord/modules/modals/Modals"]["openModal"];
+
export const openModalLazy: MappedModules["discord/modules/modals/Modals"]["openModalLazy"];
+
export const updateModal: MappedModules["discord/modules/modals/Modals"]["updateModal"];
+
export const useHasAnyModalOpen: MappedModules["discord/modules/modals/Modals"]["useHasAnyModalOpen"];
+
export const useIsModalAtTop: MappedModules["discord/modules/modals/Modals"]["useIsModalAtTop"];
+
export const useModalsStore: MappedModules["discord/modules/modals/Modals"]["useModalsStore"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/oauth2/index" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const OAuth2AuthorizeModal: MappedModules["discord/modules/oauth2/index"]["OAuth2AuthorizeModal"];
+
export const OAuth2AuthorizePage: MappedModules["discord/modules/oauth2/index"]["OAuth2AuthorizePage"];
+
export const getOAuth2AuthorizeProps: MappedModules["discord/modules/oauth2/index"]["getOAuth2AuthorizeProps"];
+
export const openOAuth2Modal: MappedModules["discord/modules/oauth2/index"]["openOAuth2Modal"];
+
export const openOAuth2ModalWithCreateGuildModal: MappedModules["discord/modules/oauth2/index"]["openOAuth2ModalWithCreateGuildModal"];
+
export const useOAuth2AuthorizeForm: MappedModules["discord/modules/oauth2/index"]["useOAuth2AuthorizeForm"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/people/web/PeoplePage.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const addFriend: MappedModules["discord/modules/people/web/PeoplePage.css"]["addFriend"];
+
export const badge: MappedModules["discord/modules/people/web/PeoplePage.css"]["badge"];
+
export const container: MappedModules["discord/modules/people/web/PeoplePage.css"]["container"];
+
export const inviteToolbar: MappedModules["discord/modules/people/web/PeoplePage.css"]["inviteToolbar"];
+
export const item: MappedModules["discord/modules/people/web/PeoplePage.css"]["item"];
+
export const nowPlayingColumn: MappedModules["discord/modules/people/web/PeoplePage.css"]["nowPlayingColumn"];
+
export const peopleColumn: MappedModules["discord/modules/people/web/PeoplePage.css"]["peopleColumn"];
+
export const tabBar: MappedModules["discord/modules/people/web/PeoplePage.css"]["tabBar"];
+
export const tabBody: MappedModules["discord/modules/people/web/PeoplePage.css"]["tabBody"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/modules/user_profile/web/BiteSizeActivity.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const header: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["header"];
+
export const headerTag: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["headerTag"];
+
export const body: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["body"];
+
export const footer: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["footer"];
+
export const backdrop: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["backdrop"];
+
export const toast: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["toast"];
+
export const activity: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["activity"];
+
export const upsell: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["upsell"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/packages/flux" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const BatchedStoreListener: MappedModules["discord/packages/flux"]["BatchedStoreListener"];
+
export const Dispatcher: MappedModules["discord/packages/flux"]["Dispatcher"];
+
export const Store: MappedModules["discord/packages/flux"]["Store"];
+
const _default: MappedModules["discord/packages/flux"]["default"];
+
export default _default;
+
export const statesWillNeverBeEqual: MappedModules["discord/packages/flux"]["statesWillNeverBeEqual"];
+
export const useStateFromStores: MappedModules["discord/packages/flux"]["useStateFromStores"];
+
export const useStateFromStoresArray: MappedModules["discord/packages/flux"]["useStateFromStoresArray"];
+
export const useStateFromStoresObject: MappedModules["discord/packages/flux"]["useStateFromStoresObject"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/packages/flux/BatchedStoreListener" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/packages/flux/BatchedStoreListener"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/packages/flux/ChangeListeners" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/packages/flux/ChangeListeners"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/packages/flux/Dispatcher" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const Dispatcher: MappedModules["discord/packages/flux/Dispatcher"]["Dispatcher"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/packages/flux/Emitter" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/packages/flux/Emitter"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/packages/flux/LoggingUtils" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/packages/flux/LoggingUtils"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/packages/flux/PersistedStore" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const PersistedStore: MappedModules["discord/packages/flux/PersistedStore"]["PersistedStore"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/packages/flux/Store" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const Store: MappedModules["discord/packages/flux/Store"]["Store"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/packages/flux/connectStores" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/packages/flux/connectStores"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/records/UserRecord" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/records/UserRecord"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/styles/shared/Margins.css" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const marginReset: MappedModules["discord/styles/shared/Margins.css"]["marginReset"];
+
export const marginTop4: MappedModules["discord/styles/shared/Margins.css"]["marginTop4"];
+
export const marginBottom4: MappedModules["discord/styles/shared/Margins.css"]["marginBottom4"];
+
export const marginTop8: MappedModules["discord/styles/shared/Margins.css"]["marginTop8"];
+
export const marginBottom8: MappedModules["discord/styles/shared/Margins.css"]["marginBottom8"];
+
export const marginTop20: MappedModules["discord/styles/shared/Margins.css"]["marginTop20"];
+
export const marginBottom20: MappedModules["discord/styles/shared/Margins.css"]["marginBottom20"];
+
export const marginTop40: MappedModules["discord/styles/shared/Margins.css"]["marginTop40"];
+
export const marginBottom40: MappedModules["discord/styles/shared/Margins.css"]["marginBottom40"];
+
export const marginTop60: MappedModules["discord/styles/shared/Margins.css"]["marginTop60"];
+
export const marginBottom60: MappedModules["discord/styles/shared/Margins.css"]["marginBottom60"];
+
export const marginCenterHorz: MappedModules["discord/styles/shared/Margins.css"]["marginCenterHorz"];
+
export const marginLeft8: MappedModules["discord/styles/shared/Margins.css"]["marginLeft8"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/uikit/Flex" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/uikit/Flex"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/discord/utils/ClipboardUtils" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const SUPPORTS_COPY: MappedModules["discord/utils/ClipboardUtils"]["SUPPORTS_COPY"];
+
export const copy: MappedModules["discord/utils/ClipboardUtils"]["copy"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/utils/ComponentDispatchUtils" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const ComponentDispatcher: MappedModules["discord/utils/ComponentDispatchUtils"]["ComponentDispatcher"];
+
export const ComponentDispatch: MappedModules["discord/utils/ComponentDispatchUtils"]["ComponentDispatch"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/utils/HTTPUtils" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const HTTP: MappedModules["discord/utils/HTTPUtils"]["HTTP"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/utils/MaskedLinkUtils" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const isLinkTrusted: MappedModules["discord/utils/MaskedLinkUtils"]["isLinkTrusted"];
+
export const handleClick: MappedModules["discord/utils/MaskedLinkUtils"]["handleClick"];
+
}
+
+
declare module "@moonlight-mod/wp/discord/utils/NativeUtils" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _default: MappedModules["discord/utils/NativeUtils"]["default"];
+
export default _default;
+
}
+
+
declare module "@moonlight-mod/wp/highlight.js" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const highlight: MappedModules["highlight.js"]["highlight"];
+
export const highlightAuto: MappedModules["highlight.js"]["highlightAuto"];
+
export const fixMarkup: MappedModules["highlight.js"]["fixMarkup"];
+
export const highlightBlock: MappedModules["highlight.js"]["highlightBlock"];
+
export const configure: MappedModules["highlight.js"]["configure"];
+
export const initHighlighting: MappedModules["highlight.js"]["initHighlighting"];
+
export const initHighlightingOnLoad: MappedModules["highlight.js"]["initHighlightingOnLoad"];
+
export const registerLanguage: MappedModules["highlight.js"]["registerLanguage"];
+
export const listLanguages: MappedModules["highlight.js"]["listLanguages"];
+
export const getLanguage: MappedModules["highlight.js"]["getLanguage"];
+
export const inherit: MappedModules["highlight.js"]["inherit"];
+
export const COMMENT: MappedModules["highlight.js"]["COMMENT"];
+
export const IDENT_RE: MappedModules["highlight.js"]["IDENT_RE"];
+
export const UNDERSCORE_IDENT_RE: MappedModules["highlight.js"]["UNDERSCORE_IDENT_RE"];
+
export const NUMBER_RE: MappedModules["highlight.js"]["NUMBER_RE"];
+
export const C_NUMBER_RE: MappedModules["highlight.js"]["C_NUMBER_RE"];
+
export const BINARY_NUMBER_RE: MappedModules["highlight.js"]["BINARY_NUMBER_RE"];
+
export const RE_STARTERS_RE: MappedModules["highlight.js"]["RE_STARTERS_RE"];
+
export const BACKSLASH_ESCAPE: MappedModules["highlight.js"]["BACKSLASH_ESCAPE"];
+
export const APOS_STRING_MODE: MappedModules["highlight.js"]["APOS_STRING_MODE"];
+
export const QUOTE_STRING_MODE: MappedModules["highlight.js"]["QUOTE_STRING_MODE"];
+
export const PHRASAL_WORDS_MODE: MappedModules["highlight.js"]["PHRASAL_WORDS_MODE"];
+
export const C_LINE_COMMENT_MODE: MappedModules["highlight.js"]["C_LINE_COMMENT_MODE"];
+
export const C_BLOCK_COMMENT_MODE: MappedModules["highlight.js"]["C_BLOCK_COMMENT_MODE"];
+
export const HASH_COMMENT_MODE: MappedModules["highlight.js"]["HASH_COMMENT_MODE"];
+
export const NUMBER_MODE: MappedModules["highlight.js"]["NUMBER_MODE"];
+
export const C_NUMBER_MODE: MappedModules["highlight.js"]["C_NUMBER_MODE"];
+
export const BINARY_NUMBER_MODE: MappedModules["highlight.js"]["BINARY_NUMBER_MODE"];
+
export const CSS_NUMBER_MODE: MappedModules["highlight.js"]["CSS_NUMBER_MODE"];
+
export const REGEX_MODE: MappedModules["highlight.js"]["REGEX_MODE"];
+
export const TITLE_MODE: MappedModules["highlight.js"]["TITLE_MODE"];
+
export const UNDERSCORE_TITLE_MODE: MappedModules["highlight.js"]["UNDERSCORE_TITLE_MODE"];
+
const _default: MappedModules["highlight.js"]["default"];
+
export default _default;
+
export const HighlightJS: MappedModules["highlight.js"]["HighlightJS"];
+
}
+
+
declare module "@moonlight-mod/wp/highlight.js/lib/core" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const highlight: MappedModules["highlight.js/lib/core"]["highlight"];
+
export const highlightAuto: MappedModules["highlight.js/lib/core"]["highlightAuto"];
+
export const fixMarkup: MappedModules["highlight.js/lib/core"]["fixMarkup"];
+
export const highlightBlock: MappedModules["highlight.js/lib/core"]["highlightBlock"];
+
export const configure: MappedModules["highlight.js/lib/core"]["configure"];
+
export const initHighlighting: MappedModules["highlight.js/lib/core"]["initHighlighting"];
+
export const initHighlightingOnLoad: MappedModules["highlight.js/lib/core"]["initHighlightingOnLoad"];
+
export const registerLanguage: MappedModules["highlight.js/lib/core"]["registerLanguage"];
+
export const listLanguages: MappedModules["highlight.js/lib/core"]["listLanguages"];
+
export const getLanguage: MappedModules["highlight.js/lib/core"]["getLanguage"];
+
export const inherit: MappedModules["highlight.js/lib/core"]["inherit"];
+
export const COMMENT: MappedModules["highlight.js/lib/core"]["COMMENT"];
+
export const IDENT_RE: MappedModules["highlight.js/lib/core"]["IDENT_RE"];
+
export const UNDERSCORE_IDENT_RE: MappedModules["highlight.js/lib/core"]["UNDERSCORE_IDENT_RE"];
+
export const NUMBER_RE: MappedModules["highlight.js/lib/core"]["NUMBER_RE"];
+
export const C_NUMBER_RE: MappedModules["highlight.js/lib/core"]["C_NUMBER_RE"];
+
export const BINARY_NUMBER_RE: MappedModules["highlight.js/lib/core"]["BINARY_NUMBER_RE"];
+
export const RE_STARTERS_RE: MappedModules["highlight.js/lib/core"]["RE_STARTERS_RE"];
+
export const BACKSLASH_ESCAPE: MappedModules["highlight.js/lib/core"]["BACKSLASH_ESCAPE"];
+
export const APOS_STRING_MODE: MappedModules["highlight.js/lib/core"]["APOS_STRING_MODE"];
+
export const QUOTE_STRING_MODE: MappedModules["highlight.js/lib/core"]["QUOTE_STRING_MODE"];
+
export const PHRASAL_WORDS_MODE: MappedModules["highlight.js/lib/core"]["PHRASAL_WORDS_MODE"];
+
export const C_LINE_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["C_LINE_COMMENT_MODE"];
+
export const C_BLOCK_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["C_BLOCK_COMMENT_MODE"];
+
export const HASH_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["HASH_COMMENT_MODE"];
+
export const NUMBER_MODE: MappedModules["highlight.js/lib/core"]["NUMBER_MODE"];
+
export const C_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["C_NUMBER_MODE"];
+
export const BINARY_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["BINARY_NUMBER_MODE"];
+
export const CSS_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["CSS_NUMBER_MODE"];
+
export const REGEX_MODE: MappedModules["highlight.js/lib/core"]["REGEX_MODE"];
+
export const TITLE_MODE: MappedModules["highlight.js/lib/core"]["TITLE_MODE"];
+
export const UNDERSCORE_TITLE_MODE: MappedModules["highlight.js/lib/core"]["UNDERSCORE_TITLE_MODE"];
+
}
+
+
declare module "@moonlight-mod/wp/lodash" {}
+
+
declare module "@moonlight-mod/wp/murmurhash" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const v2: MappedModules["murmurhash"]["v2"];
+
export const v3: MappedModules["murmurhash"]["v3"];
+
}
+
+
declare module "@moonlight-mod/wp/platform.js" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
export const description: MappedModules["platform.js"]["description"];
+
export const layout: MappedModules["platform.js"]["layout"];
+
export const manufacturer: MappedModules["platform.js"]["manufacturer"];
+
export const name: MappedModules["platform.js"]["name"];
+
export const prerelease: MappedModules["platform.js"]["prerelease"];
+
export const product: MappedModules["platform.js"]["product"];
+
export const ua: MappedModules["platform.js"]["ua"];
+
export const version: MappedModules["platform.js"]["version"];
+
export const os: MappedModules["platform.js"]["os"];
+
export const parse: MappedModules["platform.js"]["parse"];
+
export const toString: MappedModules["platform.js"]["toString"];
+
}
+
+
declare module "@moonlight-mod/wp/react" {
+
import { MappedModules } from "@moonlight-mod/mappings";
+
const _: Omit<MappedModules["react"], "__mappings_exportEquals">;
+
export = _;
+
}
+
+
declare module "@moonlight-mod/wp/uuid/v4" {}
+7 -8
packages/types/tsconfig.json
···
{
"compilerOptions": {
-
"target": "es2016",
-
"module": "es6",
+
"target": "ES2016",
+
"jsx": "react",
+
"module": "ES6",
+
"moduleResolution": "bundler",
+
"strict": true,
+
"declaration": true,
"esModuleInterop": true,
-
"forceConsistentCasingInFileNames": true,
-
"strict": true,
-
"skipLibCheck": true,
-
"moduleResolution": "bundler",
-
"jsx": "react",
-
"declaration": true
+
"forceConsistentCasingInFileNames": true
},
"include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"]
}
+13 -1
packages/web-preload/package.json
···
{
"name": "@moonlight-mod/web-preload",
"private": true,
+
"main": "src/index.ts",
+
"engineStrict": true,
+
"engines": {
+
"node": ">=22",
+
"pnpm": ">=10",
+
"npm": "pnpm",
+
"yarn": "pnpm"
+
},
"dependencies": {
-
"@moonlight-mod/core": "workspace:*"
+
"@moonlight-mod/core": "workspace:*",
+
"@moonlight-mod/lunast": "catalog:prod",
+
"@moonlight-mod/mappings": "catalog:prod",
+
"@moonlight-mod/moonmap": "catalog:prod",
+
"@moonlight-mod/types": "workspace:*"
}
}
+44 -6
packages/web-preload/src/index.ts
···
import { loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
-
import { installWebpackPatcher } from "@moonlight-mod/core/patch";
-
import Logger from "@moonlight-mod/core/util/logger";
+
import { installWebpackPatcher, onModuleLoad, registerPatch, registerWebpackModule } from "@moonlight-mod/core/patch";
+
import { constants, MoonlightBranch } from "@moonlight-mod/types";
+
import { installStyles } from "@moonlight-mod/core/styles";
+
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
+
import LunAST from "@moonlight-mod/lunast";
+
import Moonmap from "@moonlight-mod/moonmap";
+
import loadMappings from "@moonlight-mod/mappings";
+
import { createEventEmitter } from "@moonlight-mod/core/util/event";
+
import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event";
-
(async () => {
+
async function load() {
+
delete window._moonlightWebLoad;
+
initLogger(moonlightNode.config);
const logger = new Logger("web-preload");
window.moonlight = {
+
patched: new Map(),
unpatched: new Set(),
+
pendingModules: new Set(),
enabledExtensions: new Set(),
+
events: createEventEmitter<WebEventType, WebEventPayloads>(),
+
patchingInternals: {
+
onModuleLoad,
+
registerPatch,
+
registerWebpackModule
+
},
+
localStorage: window.localStorage,
+
+
version: MOONLIGHT_VERSION,
+
branch: MOONLIGHT_BRANCH as MoonlightBranch,
+
apiLevel: constants.apiLevel,
+
getConfig: moonlightNode.getConfig.bind(moonlightNode),
getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode),
+
setConfigOption: moonlightNode.setConfigOption.bind(moonlightNode),
+
writeConfig: moonlightNode.writeConfig.bind(moonlightNode),
+
getNatives: moonlightNode.getNatives.bind(moonlightNode),
-
getLogger: (id: string) => {
+
getLogger(id) {
return new Logger(id);
-
}
+
},
+
+
lunast: new LunAST(),
+
moonmap: new Moonmap()
};
try {
+
loadMappings(window.moonlight.moonmap, window.moonlight.lunast);
await loadProcessedExtensions(moonlightNode.processedExtensions);
await installWebpackPatcher();
} catch (e) {
logger.error("Error setting up web-preload", e);
}
-
})();
+
+
if (document.readyState === "complete") {
+
installStyles();
+
} else {
+
window.addEventListener("load", installStyles);
+
}
+
}
+
+
window._moonlightWebLoad = load;
+4 -1
packages/web-preload/tsconfig.json
···
{
-
"extends": "../../tsconfig.json"
+
"extends": "../../tsconfig.json",
+
"compilerOptions": {
+
"lib": ["ESNext", "DOM"]
+
}
}
+2448 -1615
pnpm-lock.yaml
···
-
lockfileVersion: '6.0'
+
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
+
catalogs:
+
dev:
+
'@moonlight-mod/eslint-config':
+
specifier: github:moonlight-mod/eslint-config
+
version: 1.0.1
+
'@types/chrome':
+
specifier: ^0.0.313
+
version: 0.0.313
+
'@types/node':
+
specifier: ^22.14.0
+
version: 22.14.0
+
esbuild:
+
specifier: ^0.19.3
+
version: 0.19.3
+
esbuild-copy-static-files:
+
specifier: ^0.1.0
+
version: 0.1.0
+
eslint:
+
specifier: ^9.12.0
+
version: 9.23.0
+
husky:
+
specifier: ^8.0.3
+
version: 8.0.3
+
prettier:
+
specifier: ^3.1.0
+
version: 3.1.0
+
taze:
+
specifier: ^19.0.4
+
version: 19.0.4
+
typescript:
+
specifier: ^5.3.3
+
version: 5.8.2
+
prod:
+
'@moonlight-mod/lunast':
+
specifier: ^1.0.1
+
version: 1.0.1
+
'@moonlight-mod/mappings':
+
specifier: ^1.1.25
+
version: 1.1.25
+
'@moonlight-mod/moonmap':
+
specifier: ^1.0.5
+
version: 1.0.5
+
'@zenfs/core':
+
specifier: ^2.0.0
+
version: 2.0.0
+
'@zenfs/dom':
+
specifier: ^1.1.3
+
version: 1.1.6
+
microdiff:
+
specifier: ^1.5.0
+
version: 1.5.0
+
nanotar:
+
specifier: ^0.1.1
+
version: 0.1.1
+
importers:
.:
devDependencies:
-
'@typescript-eslint/eslint-plugin':
-
specifier: ^6.13.2
-
version: 6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.2)
-
'@typescript-eslint/parser':
-
specifier: ^6.13.2
-
version: 6.13.2(eslint@8.55.0)(typescript@5.3.2)
+
'@moonlight-mod/eslint-config':
+
specifier: catalog:dev
+
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)
+
'@types/node':
+
specifier: catalog:dev
+
version: 22.14.0
esbuild:
-
specifier: ^0.19.3
+
specifier: catalog:dev
version: 0.19.3
esbuild-copy-static-files:
-
specifier: ^0.1.0
+
specifier: catalog:dev
version: 0.1.0
eslint:
-
specifier: ^8.55.0
-
version: 8.55.0
-
eslint-config-prettier:
-
specifier: ^9.1.0
-
version: 9.1.0(eslint@8.55.0)
-
eslint-plugin-prettier:
-
specifier: ^5.0.1
-
version: 5.0.1(eslint-config-prettier@9.1.0)(eslint@8.55.0)(prettier@3.1.0)
-
eslint-plugin-react:
-
specifier: ^7.33.2
-
version: 7.33.2(eslint@8.55.0)
+
specifier: catalog:dev
+
version: 9.23.0(jiti@2.4.2)
+
husky:
+
specifier: catalog:dev
+
version: 8.0.3
prettier:
-
specifier: ^3.1.0
+
specifier: catalog:dev
version: 3.1.0
+
taze:
+
specifier: catalog:dev
+
version: 19.0.4
typescript:
-
specifier: ^5.3.2
-
version: 5.3.2
+
specifier: catalog:dev
+
version: 5.8.2
+
+
packages/browser:
+
dependencies:
+
'@moonlight-mod/core':
+
specifier: workspace:*
+
version: link:../core
+
'@moonlight-mod/types':
+
specifier: workspace:*
+
version: link:../types
+
'@moonlight-mod/web-preload':
+
specifier: workspace:*
+
version: link:../web-preload
+
'@zenfs/core':
+
specifier: catalog:prod
+
version: 2.0.0
+
'@zenfs/dom':
+
specifier: catalog:prod
+
version: 1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1)
+
devDependencies:
+
'@types/chrome':
+
specifier: catalog:dev
+
version: 0.0.313
packages/core:
dependencies:
···
packages/core-extensions:
dependencies:
-
'@electron/asar':
-
specifier: ^3.2.5
-
version: 3.2.5
+
'@moonlight-mod/core':
+
specifier: workspace:*
+
version: link:../core
'@moonlight-mod/types':
specifier: workspace:*
version: link:../types
+
microdiff:
+
specifier: catalog:prod
+
version: 1.5.0
+
nanotar:
+
specifier: catalog:prod
+
version: 0.1.1
packages/injector:
dependencies:
···
packages/types:
dependencies:
-
'@types/flux':
-
specifier: ^3.1.12
-
version: 3.1.12
-
'@types/node':
-
specifier: ^20.6.2
-
version: 20.6.2
+
'@moonlight-mod/lunast':
+
specifier: ^1.0.1
+
version: 1.0.1
+
'@moonlight-mod/mappings':
+
specifier: ^1.1.25
+
version: 1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)
+
'@moonlight-mod/moonmap':
+
specifier: ^1.0.5
+
version: 1.0.5
'@types/react':
-
specifier: ^18.2.22
-
version: 18.2.22
+
specifier: ^18.3.10
+
version: 18.3.20
csstype:
-
specifier: ^3.1.2
-
version: 3.1.2
+
specifier: ^3.1.3
+
version: 3.1.3
standalone-electron-types:
specifier: ^1.0.0
version: 1.0.0
···
'@moonlight-mod/core':
specifier: workspace:*
version: link:../core
+
'@moonlight-mod/lunast':
+
specifier: catalog:prod
+
version: 1.0.1
+
'@moonlight-mod/mappings':
+
specifier: catalog:prod
+
version: 1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)
+
'@moonlight-mod/moonmap':
+
specifier: catalog:prod
+
version: 1.0.5
+
'@moonlight-mod/types':
+
specifier: workspace:*
+
version: link:../types
packages:
-
/@aashutoshrathi/word-wrap@1.2.6:
+
'@aashutoshrathi/word-wrap@1.2.6':
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
engines: {node: '>=0.10.0'}
-
dev: true
-
/@electron/asar@3.2.5:
-
resolution: {integrity: sha512-Ypahc2ElTj9YOrFvUHuoXv5Z/V1nPA5enlhmQapc578m/HZBHKTbqhoL5JZQjje2+/6Ti5AHh7Gj1/haeJa63Q==}
-
engines: {node: '>=10.12.0'}
+
'@antfu/ni@24.3.0':
+
resolution: {integrity: sha512-wBSav4mBxvHEW9RbdSo1SWLQ6MAlT0Dc423weC58yOWqW4OcMvtnNDdDrxOZeJ88fEIyPK93gDUWIelBxzSf8g==}
hasBin: true
-
dependencies:
-
commander: 5.1.0
-
glob: 7.2.3
-
minimatch: 3.1.2
-
dev: false
-
/@esbuild/android-arm64@0.19.3:
+
'@esbuild/android-arm64@0.19.3':
resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/android-arm@0.19.3:
+
'@esbuild/android-arm@0.19.3':
resolution: {integrity: sha512-Lemgw4io4VZl9GHJmjiBGzQ7ONXRfRPHcUEerndjwiSkbxzrpq0Uggku5MxxrXdwJ+pTj1qyw4jwTu7hkPsgIA==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/android-x64@0.19.3:
+
'@esbuild/android-x64@0.19.3':
resolution: {integrity: sha512-FKQJKkK5MXcBHoNZMDNUAg1+WcZlV/cuXrWCoGF/TvdRiYS4znA0m5Il5idUwfxrE20bG/vU1Cr5e1AD6IEIjQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/darwin-arm64@0.19.3:
+
'@esbuild/darwin-arm64@0.19.3':
resolution: {integrity: sha512-kw7e3FXU+VsJSSSl2nMKvACYlwtvZB8RUIeVShIEY6PVnuZ3c9+L9lWB2nWeeKWNNYDdtL19foCQ0ZyUL7nqGw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/darwin-x64@0.19.3:
+
'@esbuild/darwin-x64@0.19.3':
resolution: {integrity: sha512-tPfZiwF9rO0jW6Jh9ipi58N5ZLoSjdxXeSrAYypy4psA2Yl1dAMhM71KxVfmjZhJmxRjSnb29YlRXXhh3GqzYw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/freebsd-arm64@0.19.3:
+
'@esbuild/freebsd-arm64@0.19.3':
resolution: {integrity: sha512-ERDyjOgYeKe0Vrlr1iLrqTByB026YLPzTytDTz1DRCYM+JI92Dw2dbpRHYmdqn6VBnQ9Bor6J8ZlNwdZdxjlSg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/freebsd-x64@0.19.3:
+
'@esbuild/freebsd-x64@0.19.3':
resolution: {integrity: sha512-nXesBZ2Ad1qL+Rm3crN7NmEVJ5uvfLFPLJev3x1j3feCQXfAhoYrojC681RhpdOph8NsvKBBwpYZHR7W0ifTTA==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/linux-arm64@0.19.3:
+
'@esbuild/linux-arm64@0.19.3':
resolution: {integrity: sha512-qXvYKmXj8GcJgWq3aGvxL/JG1ZM3UR272SdPU4QSTzD0eymrM7leiZH77pvY3UetCy0k1xuXZ+VPvoJNdtrsWQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/linux-arm@0.19.3:
+
'@esbuild/linux-arm@0.19.3':
resolution: {integrity: sha512-zr48Cg/8zkzZCzDHNxXO/89bf9e+r4HtzNUPoz4GmgAkF1gFAFmfgOdCbR8zMbzFDGb1FqBBhdXUpcTQRYS1cQ==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/linux-ia32@0.19.3:
+
'@esbuild/linux-ia32@0.19.3':
resolution: {integrity: sha512-7XlCKCA0nWcbvYpusARWkFjRQNWNGlt45S+Q18UeS///K6Aw8bB2FKYe9mhVWy/XLShvCweOLZPrnMswIaDXQA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/linux-loong64@0.19.3:
+
'@esbuild/linux-loong64@0.19.3':
resolution: {integrity: sha512-qGTgjweER5xqweiWtUIDl9OKz338EQqCwbS9c2Bh5jgEH19xQ1yhgGPNesugmDFq+UUSDtWgZ264st26b3de8A==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/linux-mips64el@0.19.3:
+
'@esbuild/linux-mips64el@0.19.3':
resolution: {integrity: sha512-gy1bFskwEyxVMFRNYSvBauDIWNggD6pyxUksc0MV9UOBD138dKTzr8XnM2R4mBsHwVzeuIH8X5JhmNs2Pzrx+A==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/linux-ppc64@0.19.3:
+
'@esbuild/linux-ppc64@0.19.3':
resolution: {integrity: sha512-UrYLFu62x1MmmIe85rpR3qou92wB9lEXluwMB/STDzPF9k8mi/9UvNsG07Tt9AqwPQXluMQ6bZbTzYt01+Ue5g==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/linux-riscv64@0.19.3:
+
'@esbuild/linux-riscv64@0.19.3':
resolution: {integrity: sha512-9E73TfyMCbE+1AwFOg3glnzZ5fBAFK4aawssvuMgCRqCYzE0ylVxxzjEfut8xjmKkR320BEoMui4o/t9KA96gA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/linux-s390x@0.19.3:
+
'@esbuild/linux-s390x@0.19.3':
resolution: {integrity: sha512-LlmsbuBdm1/D66TJ3HW6URY8wO6IlYHf+ChOUz8SUAjVTuaisfuwCOAgcxo3Zsu3BZGxmI7yt//yGOxV+lHcEA==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/linux-x64@0.19.3:
+
'@esbuild/linux-x64@0.19.3':
resolution: {integrity: sha512-ogV0+GwEmvwg/8ZbsyfkYGaLACBQWDvO0Kkh8LKBGKj9Ru8VM39zssrnu9Sxn1wbapA2qNS6BiLdwJZGouyCwQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/netbsd-x64@0.19.3:
+
'@esbuild/netbsd-x64@0.19.3':
resolution: {integrity: sha512-o1jLNe4uzQv2DKXMlmEzf66Wd8MoIhLNO2nlQBHLtWyh2MitDG7sMpfCO3NTcoTMuqHjfufgUQDFRI5C+xsXQw==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/openbsd-x64@0.19.3:
+
'@esbuild/openbsd-x64@0.19.3':
resolution: {integrity: sha512-AZJCnr5CZgZOdhouLcfRdnk9Zv6HbaBxjcyhq0StNcvAdVZJSKIdOiPB9az2zc06ywl0ePYJz60CjdKsQacp5Q==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/sunos-x64@0.19.3:
+
'@esbuild/sunos-x64@0.19.3':
resolution: {integrity: sha512-Acsujgeqg9InR4glTRvLKGZ+1HMtDm94ehTIHKhJjFpgVzZG9/pIcWW/HA/DoMfEyXmANLDuDZ2sNrWcjq1lxw==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/win32-arm64@0.19.3:
+
'@esbuild/win32-arm64@0.19.3':
resolution: {integrity: sha512-FSrAfjVVy7TifFgYgliiJOyYynhQmqgPj15pzLyJk8BUsnlWNwP/IAy6GAiB1LqtoivowRgidZsfpoYLZH586A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/win32-ia32@0.19.3:
+
'@esbuild/win32-ia32@0.19.3':
resolution: {integrity: sha512-xTScXYi12xLOWZ/sc5RBmMN99BcXp/eEf7scUC0oeiRoiT5Vvo9AycuqCp+xdpDyAU+LkrCqEpUS9fCSZF8J3Q==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@esbuild/win32-x64@0.19.3:
+
'@esbuild/win32-x64@0.19.3':
resolution: {integrity: sha512-FbUN+0ZRXsypPyWE2IwIkVjDkDnJoMJARWOcFZn4KPPli+QnKqF0z1anvfaYe3ev5HFCpRDLLBDHyOALLppWHw==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
-
requiresBuild: true
-
dev: true
-
optional: true
-
/@eslint-community/eslint-utils@4.4.0(eslint@8.55.0):
-
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
+
'@eslint-community/eslint-utils@4.5.1':
+
resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
-
dependencies:
-
eslint: 8.55.0
-
eslint-visitor-keys: 3.4.3
-
dev: true
-
/@eslint-community/regexpp@4.10.0:
-
resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==}
+
'@eslint-community/regexpp@4.12.1':
+
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
-
dev: true
+
+
'@eslint/config-array@0.19.2':
+
resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@eslint/config-helpers@0.2.1':
+
resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@eslint/core@0.12.0':
+
resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@eslint/core@0.13.0':
+
resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@eslint/eslintrc@3.3.1':
+
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@eslint/js@9.23.0':
+
resolution: {integrity: sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@eslint/object-schema@2.1.6':
+
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@eslint/plugin-kit@0.2.8':
+
resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@humanfs/core@0.19.1':
+
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+
engines: {node: '>=18.18.0'}
+
+
'@humanfs/node@0.16.6':
+
resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
+
engines: {node: '>=18.18.0'}
+
+
'@humanwhocodes/module-importer@1.0.1':
+
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+
engines: {node: '>=12.22'}
+
+
'@humanwhocodes/retry@0.3.1':
+
resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
+
engines: {node: '>=18.18'}
+
+
'@humanwhocodes/retry@0.4.2':
+
resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
+
engines: {node: '>=18.18'}
+
+
'@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9':
+
resolution: {tarball: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9}
+
version: 1.0.1
+
peerDependencies:
+
eslint: '>= 9'
+
typescript: '>= 5.3'
+
+
'@moonlight-mod/lunast@1.0.1':
+
resolution: {integrity: sha512-K3vxzDlfFuYKjciIW2FMlcZ1qrrkAGDGpSBlNqYGtJ0sMt9bRCd2lpSpg6AX/giSljDtmAUXa/5mOfUoDQxjBA==}
+
+
'@moonlight-mod/mappings@1.1.25':
+
resolution: {integrity: sha512-bgnSN9H/IBdMGxGev6RQKXuzhQxwo1090NhIDHnflguZnjiu2pg/usPfh76bqyhxRuX4SS7tiZSNTwBoSflCLg==}
+
engines: {node: '>=22', npm: pnpm, pnpm: '>=10', yarn: pnpm}
+
peerDependencies:
+
'@moonlight-mod/lunast': ^1.0.1
+
'@moonlight-mod/moonmap': ^1.0.5
+
+
'@moonlight-mod/moonmap@1.0.5':
+
resolution: {integrity: sha512-Fdpxj8ghdulKB6TlTnchlCPey2YUKgEf1chuO1ofOIcvlqnVPBcQwSf2S80naOUQpXCDo4dQ+LWSE2fmhdDiiw==}
+
+
'@nodelib/fs.scandir@2.1.5':
+
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+
engines: {node: '>= 8'}
-
/@eslint/eslintrc@2.1.4:
-
resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
+
'@nodelib/fs.stat@2.0.5':
+
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+
engines: {node: '>= 8'}
+
+
'@nodelib/fs.walk@1.2.8':
+
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+
engines: {node: '>= 8'}
+
+
'@pkgr/core@0.2.0':
+
resolution: {integrity: sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==}
+
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+
+
'@quansync/fs@0.1.2':
+
resolution: {integrity: sha512-ezIadUb1aFhwJLd++WVqVpi9rnlX8vnd4ju7saPhwLHJN1mJgOv0puePTGV+FbtSnWtwoHDT8lAm4kagDZmpCg==}
+
engines: {node: '>=20.0.0'}
+
+
'@types/chroma-js@3.1.0':
+
resolution: {integrity: sha512-Uwl3SOtUkbQ6Ye6ZYu4q4xdLGBzmY839sEHYtOT7i691neeyd+7fXWT5VIkcUSfNwIFrIjQutNYQn9h4q5HFvg==}
+
+
'@types/chrome@0.0.313':
+
resolution: {integrity: sha512-9R5T7gTaYZhkxlu+Ho4wk9FL+y/werWQY2yjGWSqCuiTsqS7nL/BE5UMTP6rU7J+oIG2FRKqrEycHhJATeltVA==}
+
+
'@types/eslint@9.6.1':
+
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
+
+
'@types/estree-jsx@1.0.5':
+
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
+
+
'@types/estree@1.0.6':
+
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+
+
'@types/estree@1.0.7':
+
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
+
+
'@types/fbemitter@2.0.35':
+
resolution: {integrity: sha512-Xem6d7qUfmouCHntCrRYgDBwbf+WWRd6G+7WEFlEZFZ67LZXiYRvT2LV8wcZa6mIaAil95+ABQdKgB6hPIsnng==}
+
+
'@types/filesystem@0.0.36':
+
resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==}
+
+
'@types/filewriter@0.0.33':
+
resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==}
+
+
'@types/flux@3.1.14':
+
resolution: {integrity: sha512-WRXN0kQPCnqxN0/PgNgc7WBF6c8rbSHsEep3/qBLpsQ824RONdOmTs0TV7XhIW2GDNRAHO2CqCgAFLR5PChosw==}
+
+
'@types/har-format@1.2.16':
+
resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==}
+
+
'@types/highlightjs@9.12.6':
+
resolution: {integrity: sha512-Qfd1DUrwE851Hc3tExADJY4qY8yeZMt06Xw9AJm/UtpneepJS3MZY29c33BY0wP899veaaHD4gZzYiSuQm84Fg==}
+
+
'@types/json-schema@7.0.15':
+
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+
'@types/lodash@4.17.14':
+
resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==}
+
+
'@types/node@18.17.17':
+
resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==}
+
+
'@types/node@22.13.6':
+
resolution: {integrity: sha512-GYmF65GI7417CpZXsEXMjT8goQQDnpRnJnDw6jIYa+le3V/lMazPZ4vZmK1B/9R17fh2VLr2zuy9d/h5xgrLAg==}
+
+
'@types/node@22.14.0':
+
resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==}
+
+
'@types/platform@1.3.6':
+
resolution: {integrity: sha512-ZmSaqHuvzv+jC232cFoz2QqPUkaj6EvMmCrWcx3WRr7xTPVFCMUOTcOq8m2d+Zw1iKRc1kDiaA+jtNrV0hkVew==}
+
+
'@types/prop-types@15.7.13':
+
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
+
+
'@types/react@18.3.20':
+
resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==}
+
+
'@typescript-eslint/eslint-plugin@8.29.0':
+
resolution: {integrity: sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
peerDependencies:
+
'@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
+
eslint: ^8.57.0 || ^9.0.0
+
typescript: '>=4.8.4 <5.9.0'
+
+
'@typescript-eslint/parser@8.29.0':
+
resolution: {integrity: sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
peerDependencies:
+
eslint: ^8.57.0 || ^9.0.0
+
typescript: '>=4.8.4 <5.9.0'
+
+
'@typescript-eslint/scope-manager@8.29.0':
+
resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@typescript-eslint/type-utils@8.29.0':
+
resolution: {integrity: sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
peerDependencies:
+
eslint: ^8.57.0 || ^9.0.0
+
typescript: '>=4.8.4 <5.9.0'
+
+
'@typescript-eslint/types@8.29.0':
+
resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@typescript-eslint/typescript-estree@8.29.0':
+
resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
peerDependencies:
+
typescript: '>=4.8.4 <5.9.0'
+
+
'@typescript-eslint/utils@8.29.0':
+
resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
peerDependencies:
+
eslint: ^8.57.0 || ^9.0.0
+
typescript: '>=4.8.4 <5.9.0'
+
+
'@typescript-eslint/visitor-keys@8.29.0':
+
resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
'@xterm/xterm@5.5.0':
+
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
+
+
'@zenfs/core@2.0.0':
+
resolution: {integrity: sha512-wOKNFTY1DJ1vdLqKdU7M8cRh0nVYZcDVu7WHuk/3u49hrSwTZVm4PzGxJUjFd8O9Wi3U5nYTbZoN7RX5mS2ldA==}
+
engines: {node: '>= 18'}
+
hasBin: true
+
+
'@zenfs/dom@1.1.6':
+
resolution: {integrity: sha512-7SBTWgA0esuEv/TE+N/xk6W/XJf8uBF+LhlPNHQdXds0H7aOy/UYsWv/8glvARe+meDMMidoeWFLzUWoMXfjlA==}
+
engines: {node: '>= 18'}
+
peerDependencies:
+
'@zenfs/core': ^2.0.0
+
utilium: ^1.9.0
+
+
abort-controller@3.0.0:
+
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
+
engines: {node: '>=6.5'}
+
+
acorn-jsx@5.3.2:
+
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+
peerDependencies:
+
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+
acorn@8.14.1:
+
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
+
engines: {node: '>=0.4.0'}
+
hasBin: true
+
+
ajv@6.12.6:
+
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+
ansi-styles@4.3.0:
+
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+
engines: {node: '>=8'}
+
+
ansis@3.17.0:
+
resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==}
+
engines: {node: '>=14'}
+
+
argparse@2.0.1:
+
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+
array-buffer-byte-length@1.0.2:
+
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+
engines: {node: '>= 0.4'}
+
+
array-includes@3.1.8:
+
resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
+
engines: {node: '>= 0.4'}
+
+
array.prototype.findlast@1.2.5:
+
resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
+
engines: {node: '>= 0.4'}
+
+
array.prototype.flat@1.3.3:
+
resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+
engines: {node: '>= 0.4'}
+
+
array.prototype.flatmap@1.3.3:
+
resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
+
engines: {node: '>= 0.4'}
+
+
array.prototype.tosorted@1.1.4:
+
resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+
engines: {node: '>= 0.4'}
+
+
arraybuffer.prototype.slice@1.0.4:
+
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+
engines: {node: '>= 0.4'}
+
+
astring@1.9.0:
+
resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
+
hasBin: true
+
+
async-function@1.0.0:
+
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+
engines: {node: '>= 0.4'}
+
+
available-typed-arrays@1.0.7:
+
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+
engines: {node: '>= 0.4'}
+
+
balanced-match@1.0.2:
+
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+
base64-js@1.5.1:
+
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+
brace-expansion@1.1.11:
+
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+
brace-expansion@2.0.1:
+
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+
+
braces@3.0.3:
+
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+
engines: {node: '>=8'}
+
+
buffer@6.0.3:
+
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
+
cac@6.7.14:
+
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+
engines: {node: '>=8'}
+
+
call-bind-apply-helpers@1.0.2:
+
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+
engines: {node: '>= 0.4'}
+
+
call-bind@1.0.8:
+
resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+
engines: {node: '>= 0.4'}
+
+
call-bound@1.0.4:
+
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+
engines: {node: '>= 0.4'}
+
+
callsites@3.1.0:
+
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+
engines: {node: '>=6'}
+
+
chalk@4.1.2:
+
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+
engines: {node: '>=10'}
+
+
color-convert@2.0.1:
+
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+
engines: {node: '>=7.0.0'}
+
+
color-name@1.1.4:
+
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+
concat-map@0.0.1:
+
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+
cross-spawn@7.0.6:
+
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+
engines: {node: '>= 8'}
+
+
csstype@3.1.3:
+
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+
data-view-buffer@1.0.2:
+
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+
engines: {node: '>= 0.4'}
+
+
data-view-byte-length@1.0.2:
+
resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+
engines: {node: '>= 0.4'}
+
+
data-view-byte-offset@1.0.1:
+
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+
engines: {node: '>= 0.4'}
+
+
debug@4.4.0:
+
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
+
engines: {node: '>=6.0'}
+
peerDependencies:
+
supports-color: '*'
+
peerDependenciesMeta:
+
supports-color:
+
optional: true
+
+
deep-is@0.1.4:
+
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+
define-data-property@1.1.4:
+
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+
engines: {node: '>= 0.4'}
+
+
define-properties@1.2.1:
+
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+
engines: {node: '>= 0.4'}
+
+
defu@6.1.4:
+
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+
+
destr@2.0.4:
+
resolution: {integrity: sha512-FCAorltMy7QwX0QU38jOkhrv20LBpsHA8ogzvMhhPHCCKVCaN6GxrB0GGaWEWBUYI4eEjjfJ95RdP6dk9IdMQA==}
+
+
doctrine@2.1.0:
+
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+
engines: {node: '>=0.10.0'}
+
+
dunder-proto@1.0.1:
+
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+
engines: {node: '>= 0.4'}
+
+
es-abstract@1.23.9:
+
resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==}
+
engines: {node: '>= 0.4'}
+
+
es-define-property@1.0.1:
+
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+
engines: {node: '>= 0.4'}
+
+
es-errors@1.3.0:
+
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+
engines: {node: '>= 0.4'}
+
+
es-iterator-helpers@1.2.1:
+
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
+
engines: {node: '>= 0.4'}
+
+
es-object-atoms@1.1.1:
+
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+
engines: {node: '>= 0.4'}
+
+
es-set-tostringtag@2.1.0:
+
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+
engines: {node: '>= 0.4'}
+
+
es-shim-unscopables@1.1.0:
+
resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+
engines: {node: '>= 0.4'}
+
+
es-to-primitive@1.3.0:
+
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+
engines: {node: '>= 0.4'}
+
+
esbuild-copy-static-files@0.1.0:
+
resolution: {integrity: sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w==}
+
+
esbuild@0.19.3:
+
resolution: {integrity: sha512-UlJ1qUUA2jL2nNib1JTSkifQTcYTroFqRjwCFW4QYEKEsixXD5Tik9xML7zh2gTxkYTBKGHNH9y7txMwVyPbjw==}
+
engines: {node: '>=12'}
+
hasBin: true
+
+
escape-string-regexp@4.0.0:
+
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+
engines: {node: '>=10'}
+
+
eslint-config-prettier@9.1.0:
+
resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
+
hasBin: true
+
peerDependencies:
+
eslint: '>=7.0.0'
+
+
eslint-plugin-prettier@5.2.6:
+
resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==}
+
engines: {node: ^14.18.0 || >=16.0.0}
+
peerDependencies:
+
'@types/eslint': '>=8.0.0'
+
eslint: '>=8.0.0'
+
eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0'
+
prettier: '>=3.0.0'
+
peerDependenciesMeta:
+
'@types/eslint':
+
optional: true
+
eslint-config-prettier:
+
optional: true
+
+
eslint-plugin-react@7.37.5:
+
resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+
engines: {node: '>=4'}
+
peerDependencies:
+
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+
+
eslint-scope@8.3.0:
+
resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
eslint-visitor-keys@3.4.3:
+
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+
eslint-visitor-keys@4.2.0:
+
resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
eslint@9.23.0:
+
resolution: {integrity: sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
hasBin: true
+
peerDependencies:
+
jiti: '*'
+
peerDependenciesMeta:
+
jiti:
+
optional: true
+
+
espree@10.3.0:
+
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+
esquery@1.6.0:
+
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+
engines: {node: '>=0.10'}
+
+
esrecurse@4.3.0:
+
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+
engines: {node: '>=4.0'}
+
+
estraverse@5.3.0:
+
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+
engines: {node: '>=4.0'}
+
+
estree-toolkit@1.7.8:
+
resolution: {integrity: sha512-v0Q0L+0agSDFe3x9Sj7aAzrI9afvsfr5r7AM2SNk/8bKYRQ3tUf4PQEUWe99LkWysmT1PsuSpW+W1w/xZmCKeg==}
+
+
esutils@2.0.3:
+
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+
engines: {node: '>=0.10.0'}
+
+
event-target-shim@5.0.1:
+
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
+
engines: {node: '>=6'}
+
+
eventemitter3@5.0.1:
+
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
+
+
events@3.3.0:
+
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+
engines: {node: '>=0.8.x'}
+
+
fast-deep-equal@3.1.3:
+
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+
fast-diff@1.3.0:
+
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+
+
fast-glob@3.3.2:
+
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+
engines: {node: '>=8.6.0'}
+
+
fast-json-stable-stringify@2.1.0:
+
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+
fast-levenshtein@2.0.6:
+
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+
fastq@1.17.1:
+
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
+
+
fdir@6.4.3:
+
resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
+
peerDependencies:
+
picomatch: ^3 || ^4
+
peerDependenciesMeta:
+
picomatch:
+
optional: true
+
+
file-entry-cache@8.0.0:
+
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+
engines: {node: '>=16.0.0'}
+
+
fill-range@7.1.1:
+
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+
engines: {node: '>=8'}
+
+
find-up-simple@1.0.1:
+
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
+
engines: {node: '>=18'}
+
+
find-up@5.0.0:
+
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+
engines: {node: '>=10'}
+
+
flat-cache@4.0.1:
+
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+
engines: {node: '>=16'}
+
+
flatted@3.2.9:
+
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
+
+
for-each@0.3.5:
+
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+
engines: {node: '>= 0.4'}
+
+
function-bind@1.1.2:
+
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+
function.prototype.name@1.1.8:
+
resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
+
engines: {node: '>= 0.4'}
+
+
functions-have-names@1.2.3:
+
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+
fzf@0.5.2:
+
resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==}
+
+
get-intrinsic@1.3.0:
+
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+
engines: {node: '>= 0.4'}
+
+
get-proto@1.0.1:
+
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+
engines: {node: '>= 0.4'}
+
+
get-symbol-description@1.1.0:
+
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+
engines: {node: '>= 0.4'}
+
+
glob-parent@5.1.2:
+
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+
engines: {node: '>= 6'}
+
+
glob-parent@6.0.2:
+
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+
engines: {node: '>=10.13.0'}
+
+
globals@14.0.0:
+
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+
engines: {node: '>=18'}
+
+
globalthis@1.0.4:
+
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+
engines: {node: '>= 0.4'}
+
+
gopd@1.2.0:
+
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+
engines: {node: '>= 0.4'}
+
+
graphemer@1.4.0:
+
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+
+
has-bigints@1.1.0:
+
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+
engines: {node: '>= 0.4'}
+
+
has-flag@4.0.0:
+
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+
engines: {node: '>=8'}
+
+
has-property-descriptors@1.0.2:
+
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+
has-proto@1.2.0:
+
resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+
engines: {node: '>= 0.4'}
+
+
has-symbols@1.1.0:
+
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+
engines: {node: '>= 0.4'}
+
+
has-tostringtag@1.0.2:
+
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+
engines: {node: '>= 0.4'}
+
+
hasown@2.0.2:
+
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+
engines: {node: '>= 0.4'}
+
+
husky@8.0.3:
+
resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
+
engines: {node: '>=14'}
+
hasBin: true
+
+
ieee754@1.2.1:
+
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
+
ignore@5.3.2:
+
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+
engines: {node: '>= 4'}
+
+
import-fresh@3.3.0:
+
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+
engines: {node: '>=6'}
+
+
imurmurhash@0.1.4:
+
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+
engines: {node: '>=0.8.19'}
+
+
internal-slot@1.1.0:
+
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+
engines: {node: '>= 0.4'}
+
+
is-array-buffer@3.0.5:
+
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+
engines: {node: '>= 0.4'}
+
+
is-async-function@2.1.1:
+
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+
engines: {node: '>= 0.4'}
+
+
is-bigint@1.1.0:
+
resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+
engines: {node: '>= 0.4'}
+
+
is-boolean-object@1.2.2:
+
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+
engines: {node: '>= 0.4'}
+
+
is-callable@1.2.7:
+
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+
engines: {node: '>= 0.4'}
+
+
is-core-module@2.16.1:
+
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+
engines: {node: '>= 0.4'}
+
+
is-data-view@1.0.2:
+
resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+
engines: {node: '>= 0.4'}
+
+
is-date-object@1.1.0:
+
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+
engines: {node: '>= 0.4'}
+
+
is-extglob@2.1.1:
+
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+
engines: {node: '>=0.10.0'}
+
+
is-finalizationregistry@1.1.1:
+
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+
engines: {node: '>= 0.4'}
+
+
is-generator-function@1.1.0:
+
resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
+
engines: {node: '>= 0.4'}
+
+
is-glob@4.0.3:
+
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+
engines: {node: '>=0.10.0'}
+
+
is-map@2.0.3:
+
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+
engines: {node: '>= 0.4'}
+
+
is-number-object@1.1.1:
+
resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+
engines: {node: '>= 0.4'}
+
+
is-number@7.0.0:
+
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+
engines: {node: '>=0.12.0'}
+
+
is-regex@1.2.1:
+
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+
engines: {node: '>= 0.4'}
+
+
is-set@2.0.3:
+
resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+
engines: {node: '>= 0.4'}
+
+
is-shared-array-buffer@1.0.4:
+
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+
engines: {node: '>= 0.4'}
+
+
is-string@1.1.1:
+
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+
engines: {node: '>= 0.4'}
+
+
is-symbol@1.1.1:
+
resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+
engines: {node: '>= 0.4'}
+
+
is-typed-array@1.1.15:
+
resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+
engines: {node: '>= 0.4'}
+
+
is-weakmap@2.0.2:
+
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+
engines: {node: '>= 0.4'}
+
+
is-weakref@1.1.1:
+
resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+
engines: {node: '>= 0.4'}
+
+
is-weakset@2.0.4:
+
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+
engines: {node: '>= 0.4'}
+
+
isarray@2.0.5:
+
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+
isexe@2.0.0:
+
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+
iterator.prototype@1.1.5:
+
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+
engines: {node: '>= 0.4'}
+
+
jiti@2.4.2:
+
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
+
hasBin: true
+
+
js-tokens@4.0.0:
+
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+
js-yaml@4.1.0:
+
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+
hasBin: true
+
+
json-buffer@3.0.1:
+
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+
json-schema-traverse@0.4.1:
+
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+
json-stable-stringify-without-jsonify@1.0.1:
+
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+
jsx-ast-utils@3.3.5:
+
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+
engines: {node: '>=4.0'}
+
+
keyv@4.5.4:
+
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+
levn@0.4.1:
+
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+
engines: {node: '>= 0.8.0'}
+
+
locate-path@6.0.0:
+
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+
engines: {node: '>=10'}
+
+
lodash.merge@4.6.2:
+
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+
loose-envify@1.4.0:
+
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+
hasBin: true
+
+
math-intrinsics@1.1.0:
+
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+
engines: {node: '>= 0.4'}
+
+
merge2@1.4.1:
+
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+
engines: {node: '>= 8'}
+
+
meriyah@6.0.1:
+
resolution: {integrity: sha512-OyvYIOgpzXREySYJ1cqEb2pOKdeQMTfF9M8dRU6nC4hi/GXMmNpe9ssZCrSoTHazu05BSAoRBN/uYeco+ymfOg==}
+
engines: {node: '>=18.0.0'}
+
+
microdiff@1.5.0:
+
resolution: {integrity: sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==}
+
+
micromatch@4.0.8:
+
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+
engines: {node: '>=8.6'}
+
+
mimic-function@5.0.1:
+
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+
engines: {node: '>=18'}
+
+
minimatch@3.1.2:
+
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+
minimatch@9.0.5:
+
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+
engines: {node: '>=16 || 14 >=14.17'}
+
+
ms@2.1.3:
+
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+
nanotar@0.1.1:
+
resolution: {integrity: sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ==}
+
+
natural-compare@1.4.0:
+
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+
node-fetch-native@1.6.6:
+
resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==}
+
+
object-assign@4.1.1:
+
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+
engines: {node: '>=0.10.0'}
+
+
object-inspect@1.13.4:
+
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+
engines: {node: '>= 0.4'}
+
+
object-keys@1.1.1:
+
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+
engines: {node: '>= 0.4'}
+
+
object.assign@4.1.7:
+
resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+
engines: {node: '>= 0.4'}
+
+
object.entries@1.1.9:
+
resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
+
engines: {node: '>= 0.4'}
+
+
object.fromentries@2.0.8:
+
resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+
engines: {node: '>= 0.4'}
+
+
object.values@1.2.1:
+
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
+
engines: {node: '>= 0.4'}
+
+
ofetch@1.4.1:
+
resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==}
+
+
onetime@7.0.0:
+
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
+
engines: {node: '>=18'}
+
+
optionator@0.9.3:
+
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
+
engines: {node: '>= 0.8.0'}
+
+
own-keys@1.0.1:
+
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+
engines: {node: '>= 0.4'}
+
+
p-limit@3.1.0:
+
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+
engines: {node: '>=10'}
+
+
p-locate@5.0.0:
+
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+
engines: {node: '>=10'}
+
+
package-manager-detector@1.1.0:
+
resolution: {integrity: sha512-Y8f9qUlBzW8qauJjd/eu6jlpJZsuPJm2ZAV0cDVd420o4EdpH5RPdoCv+60/TdJflGatr4sDfpAL6ArWZbM5tA==}
+
+
parent-module@1.0.1:
+
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+
engines: {node: '>=6'}
+
+
path-exists@4.0.0:
+
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+
engines: {node: '>=8'}
+
+
path-key@3.1.1:
+
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+
engines: {node: '>=8'}
+
+
path-parse@1.0.7:
+
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+
pathe@2.0.3:
+
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+
picomatch@2.3.1:
+
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+
engines: {node: '>=8.6'}
+
+
picomatch@4.0.2:
+
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
+
engines: {node: '>=12'}
+
+
pnpm-workspace-yaml@0.3.1:
+
resolution: {integrity: sha512-3nW5RLmREmZ8Pm8MbPsO2RM+99RRjYd25ynj3NV0cFsN7CcEl4sDFzgoFmSyduFwxFQ2Qbu3y2UdCh6HlyUOeA==}
+
+
possible-typed-array-names@1.1.0:
+
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+
engines: {node: '>= 0.4'}
+
+
prelude-ls@1.2.1:
+
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+
engines: {node: '>= 0.8.0'}
+
+
prettier-linter-helpers@1.0.0:
+
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
+
engines: {node: '>=6.0.0'}
+
+
prettier@3.1.0:
+
resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==}
+
engines: {node: '>=14'}
+
hasBin: true
+
+
process@0.11.10:
+
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
+
engines: {node: '>= 0.6.0'}
+
+
prop-types@15.8.1:
+
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+
punycode@2.3.1:
+
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+
engines: {node: '>=6'}
+
+
quansync@0.2.10:
+
resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==}
+
+
queue-microtask@1.2.3:
+
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+
react-is@16.13.1:
+
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+
readable-stream@4.5.2:
+
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
+
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+
reflect.getprototypeof@1.0.10:
+
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+
engines: {node: '>= 0.4'}
+
+
regexp.prototype.flags@1.5.4:
+
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+
engines: {node: '>= 0.4'}
+
+
resolve-from@4.0.0:
+
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+
engines: {node: '>=4'}
+
+
resolve@2.0.0-next.5:
+
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
+
hasBin: true
+
+
restore-cursor@5.1.0:
+
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
+
engines: {node: '>=18'}
+
+
reusify@1.0.4:
+
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+
run-parallel@1.2.0:
+
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+
safe-array-concat@1.1.3:
+
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
+
engines: {node: '>=0.4'}
+
+
safe-buffer@5.2.1:
+
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+
safe-push-apply@1.0.0:
+
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+
engines: {node: '>= 0.4'}
+
+
safe-regex-test@1.1.0:
+
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+
engines: {node: '>= 0.4'}
+
+
semver@6.3.1:
+
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+
hasBin: true
+
+
semver@7.7.1:
+
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
+
engines: {node: '>=10'}
+
hasBin: true
+
+
set-function-length@1.2.2:
+
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+
engines: {node: '>= 0.4'}
+
+
set-function-name@2.0.2:
+
resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+
engines: {node: '>= 0.4'}
+
+
set-proto@1.0.0:
+
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+
engines: {node: '>= 0.4'}
+
+
shebang-command@2.0.0:
+
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+
engines: {node: '>=8'}
+
+
shebang-regex@3.0.0:
+
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+
engines: {node: '>=8'}
+
+
side-channel-list@1.0.0:
+
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+
engines: {node: '>= 0.4'}
+
+
side-channel-map@1.0.1:
+
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+
engines: {node: '>= 0.4'}
+
+
side-channel-weakmap@1.0.2:
+
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+
engines: {node: '>= 0.4'}
+
+
side-channel@1.1.0:
+
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+
engines: {node: '>= 0.4'}
+
+
signal-exit@4.1.0:
+
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+
engines: {node: '>=14'}
+
+
standalone-electron-types@1.0.0:
+
resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==}
+
+
string.prototype.matchall@4.0.12:
+
resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+
engines: {node: '>= 0.4'}
+
+
string.prototype.repeat@1.0.0:
+
resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
+
string.prototype.trim@1.2.10:
+
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
+
engines: {node: '>= 0.4'}
+
+
string.prototype.trimend@1.0.9:
+
resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+
engines: {node: '>= 0.4'}
+
+
string.prototype.trimstart@1.0.8:
+
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+
engines: {node: '>= 0.4'}
+
+
string_decoder@1.3.0:
+
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+
strip-json-comments@3.1.1:
+
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+
engines: {node: '>=8'}
+
+
supports-color@7.2.0:
+
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+
engines: {node: '>=8'}
+
+
supports-preserve-symlinks-flag@1.0.0:
+
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+
engines: {node: '>= 0.4'}
+
+
synckit@0.11.1:
+
resolution: {integrity: sha512-fWZqNBZNNFp/7mTUy1fSsydhKsAKJ+u90Nk7kOK5Gcq9vObaqLBLjWFDBkyVU9Vvc6Y71VbOevMuGhqv02bT+Q==}
+
engines: {node: ^14.18.0 || >=16.0.0}
+
+
taze@19.0.4:
+
resolution: {integrity: sha512-bviyNotzqcIWpVBCC4QYVb2yupzKyUDGQi2m/8GERdiPaudVMtgAqaE98+x0cDDaByYRMJCyhQWM04ikUL6+kQ==}
+
hasBin: true
+
+
tinyexec@1.0.1:
+
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
+
+
tinyglobby@0.2.12:
+
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
+
engines: {node: '>=12.0.0'}
+
+
to-regex-range@5.0.1:
+
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+
engines: {node: '>=8.0'}
+
+
ts-api-utils@2.1.0:
+
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
+
engines: {node: '>=18.12'}
+
peerDependencies:
+
typescript: '>=4.8.4'
+
+
tslib@2.8.1:
+
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+
type-check@0.4.0:
+
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+
engines: {node: '>= 0.8.0'}
+
+
typed-array-buffer@1.0.3:
+
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+
engines: {node: '>= 0.4'}
+
+
typed-array-byte-length@1.0.3:
+
resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+
engines: {node: '>= 0.4'}
+
+
typed-array-byte-offset@1.0.4:
+
resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+
engines: {node: '>= 0.4'}
+
+
typed-array-length@1.0.7:
+
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
+
engines: {node: '>= 0.4'}
+
+
typescript-eslint@8.29.0:
+
resolution: {integrity: sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==}
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
peerDependencies:
+
eslint: ^8.57.0 || ^9.0.0
+
typescript: '>=4.8.4 <5.9.0'
+
+
typescript@5.8.2:
+
resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
+
engines: {node: '>=14.17'}
+
hasBin: true
+
+
ufo@1.5.4:
+
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
+
+
unbox-primitive@1.1.0:
+
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+
engines: {node: '>= 0.4'}
+
+
unconfig@7.3.1:
+
resolution: {integrity: sha512-LH5WL+un92tGAzWS87k7LkAfwpMdm7V0IXG2FxEjZz/QxiIW5J5LkcrKQThj0aRz6+h/lFmKI9EUXmK/T0bcrw==}
+
+
undici-types@6.20.0:
+
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
+
+
undici-types@6.21.0:
+
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+
uri-js@4.4.1:
+
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+
utilium@1.10.1:
+
resolution: {integrity: sha512-GQINDTb/ocyz4acQj3GXAe0wipYxws6L+9ouqaq10KlInTk9DGvW9TJd0pYa/Xu3cppNnZuB4T/sBuSXpcN2ng==}
+
+
which-boxed-primitive@1.1.1:
+
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+
engines: {node: '>= 0.4'}
+
+
which-builtin-type@1.2.1:
+
resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+
engines: {node: '>= 0.4'}
+
+
which-collection@1.0.2:
+
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+
engines: {node: '>= 0.4'}
+
+
which-typed-array@1.1.19:
+
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
+
engines: {node: '>= 0.4'}
+
+
which@2.0.2:
+
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+
engines: {node: '>= 8'}
+
hasBin: true
+
+
yaml@2.7.1:
+
resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==}
+
engines: {node: '>= 14'}
+
hasBin: true
+
+
yocto-queue@0.1.0:
+
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+
engines: {node: '>=10'}
+
+
zustand@5.0.3:
+
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
+
engines: {node: '>=12.20.0'}
+
peerDependencies:
+
'@types/react': '>=18.0.0'
+
immer: '>=9.0.6'
+
react: '>=18.0.0'
+
use-sync-external-store: '>=1.2.0'
+
peerDependenciesMeta:
+
'@types/react':
+
optional: true
+
immer:
+
optional: true
+
react:
+
optional: true
+
use-sync-external-store:
+
optional: true
+
+
snapshots:
+
+
'@aashutoshrathi/word-wrap@1.2.6': {}
+
+
'@antfu/ni@24.3.0':
+
dependencies:
+
ansis: 3.17.0
+
fzf: 0.5.2
+
package-manager-detector: 1.1.0
+
tinyexec: 1.0.1
+
+
'@esbuild/android-arm64@0.19.3':
+
optional: true
+
+
'@esbuild/android-arm@0.19.3':
+
optional: true
+
+
'@esbuild/android-x64@0.19.3':
+
optional: true
+
+
'@esbuild/darwin-arm64@0.19.3':
+
optional: true
+
+
'@esbuild/darwin-x64@0.19.3':
+
optional: true
+
+
'@esbuild/freebsd-arm64@0.19.3':
+
optional: true
+
+
'@esbuild/freebsd-x64@0.19.3':
+
optional: true
+
+
'@esbuild/linux-arm64@0.19.3':
+
optional: true
+
+
'@esbuild/linux-arm@0.19.3':
+
optional: true
+
+
'@esbuild/linux-ia32@0.19.3':
+
optional: true
+
+
'@esbuild/linux-loong64@0.19.3':
+
optional: true
+
+
'@esbuild/linux-mips64el@0.19.3':
+
optional: true
+
+
'@esbuild/linux-ppc64@0.19.3':
+
optional: true
+
+
'@esbuild/linux-riscv64@0.19.3':
+
optional: true
+
+
'@esbuild/linux-s390x@0.19.3':
+
optional: true
+
+
'@esbuild/linux-x64@0.19.3':
+
optional: true
+
+
'@esbuild/netbsd-x64@0.19.3':
+
optional: true
+
+
'@esbuild/openbsd-x64@0.19.3':
+
optional: true
+
+
'@esbuild/sunos-x64@0.19.3':
+
optional: true
+
+
'@esbuild/win32-arm64@0.19.3':
+
optional: true
+
+
'@esbuild/win32-ia32@0.19.3':
+
optional: true
+
+
'@esbuild/win32-x64@0.19.3':
+
optional: true
+
+
'@eslint-community/eslint-utils@4.5.1(eslint@9.23.0(jiti@2.4.2))':
+
dependencies:
+
eslint: 9.23.0(jiti@2.4.2)
+
eslint-visitor-keys: 3.4.3
+
+
'@eslint-community/regexpp@4.12.1': {}
+
+
'@eslint/config-array@0.19.2':
+
dependencies:
+
'@eslint/object-schema': 2.1.6
+
debug: 4.4.0
+
minimatch: 3.1.2
+
transitivePeerDependencies:
+
- supports-color
+
+
'@eslint/config-helpers@0.2.1': {}
+
+
'@eslint/core@0.12.0':
+
dependencies:
+
'@types/json-schema': 7.0.15
+
+
'@eslint/core@0.13.0':
+
dependencies:
+
'@types/json-schema': 7.0.15
+
+
'@eslint/eslintrc@3.3.1':
dependencies:
ajv: 6.12.6
-
debug: 4.3.4
-
espree: 9.6.1
-
globals: 13.23.0
-
ignore: 5.3.0
+
debug: 4.4.0
+
espree: 10.3.0
+
globals: 14.0.0
+
ignore: 5.3.2
import-fresh: 3.3.0
js-yaml: 4.1.0
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
-
dev: true
-
/@eslint/js@8.55.0:
-
resolution: {integrity: sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==}
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-
dev: true
+
'@eslint/js@9.23.0': {}
-
/@humanwhocodes/config-array@0.11.13:
-
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
-
engines: {node: '>=10.10.0'}
+
'@eslint/object-schema@2.1.6': {}
+
+
'@eslint/plugin-kit@0.2.8':
dependencies:
-
'@humanwhocodes/object-schema': 2.0.1
-
debug: 4.3.4
-
minimatch: 3.1.2
+
'@eslint/core': 0.13.0
+
levn: 0.4.1
+
+
'@humanfs/core@0.19.1': {}
+
+
'@humanfs/node@0.16.6':
+
dependencies:
+
'@humanfs/core': 0.19.1
+
'@humanwhocodes/retry': 0.3.1
+
+
'@humanwhocodes/module-importer@1.0.1': {}
+
+
'@humanwhocodes/retry@0.3.1': {}
+
+
'@humanwhocodes/retry@0.4.2': {}
+
+
'@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)':
+
dependencies:
+
'@eslint/js': 9.23.0
+
eslint: 9.23.0(jiti@2.4.2)
+
eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@2.4.2))
+
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)
+
eslint-plugin-react: 7.37.5(eslint@9.23.0(jiti@2.4.2))
+
typescript: 5.8.2
+
typescript-eslint: 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
transitivePeerDependencies:
+
- '@types/eslint'
+
- prettier
- supports-color
-
dev: true
-
/@humanwhocodes/module-importer@1.0.1:
-
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
-
engines: {node: '>=12.22'}
-
dev: true
+
'@moonlight-mod/lunast@1.0.1':
+
dependencies:
+
astring: 1.9.0
+
estree-toolkit: 1.7.8
+
meriyah: 6.0.1
+
+
'@moonlight-mod/mappings@1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)':
+
dependencies:
+
'@moonlight-mod/lunast': 1.0.1
+
'@moonlight-mod/moonmap': 1.0.5
+
'@types/chroma-js': 3.1.0
+
'@types/flux': 3.1.14
+
'@types/highlightjs': 9.12.6
+
'@types/lodash': 4.17.14
+
'@types/platform': 1.3.6
+
'@types/react': 18.3.20
+
csstype: 3.1.3
+
zustand: 5.0.3(@types/react@18.3.20)
+
transitivePeerDependencies:
+
- immer
+
- react
+
- use-sync-external-store
-
/@humanwhocodes/object-schema@2.0.1:
-
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
-
dev: true
+
'@moonlight-mod/moonmap@1.0.5': {}
-
/@nodelib/fs.scandir@2.1.5:
-
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
-
engines: {node: '>= 8'}
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
-
dev: true
-
/@nodelib/fs.stat@2.0.5:
-
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
-
engines: {node: '>= 8'}
-
dev: true
+
'@nodelib/fs.stat@2.0.5': {}
-
/@nodelib/fs.walk@1.2.8:
-
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
-
engines: {node: '>= 8'}
+
'@nodelib/fs.walk@1.2.8':
dependencies:
'@nodelib/fs.scandir': 2.1.5
-
fastq: 1.15.0
-
dev: true
+
fastq: 1.17.1
-
/@pkgr/utils@2.4.2:
-
resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
-
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+
'@pkgr/core@0.2.0': {}
+
+
'@quansync/fs@0.1.2':
dependencies:
-
cross-spawn: 7.0.3
-
fast-glob: 3.3.2
-
is-glob: 4.0.3
-
open: 9.1.0
-
picocolors: 1.0.0
-
tslib: 2.6.2
-
dev: true
+
quansync: 0.2.10
-
/@types/fbemitter@2.0.33:
-
resolution: {integrity: sha512-KcSilwdl0D8YgXGL6l9d+rTBm2W7pDyTZrDEw0+IzqQ724676KJtMeO+xHodJewKFWZT+GFWaJubA5mpMxSkcg==}
-
dev: false
+
'@types/chroma-js@3.1.0': {}
-
/@types/flux@3.1.12:
-
resolution: {integrity: sha512-HZ8o/DTVNgcgnXoDyn0ZnjqEZMT4Chr4w5ktMQSbQAnqVDklasmRqNGd2agZDsk5i0jYHQLgQQuM782bWG7fUA==}
+
'@types/chrome@0.0.313':
dependencies:
-
'@types/fbemitter': 2.0.33
-
'@types/react': 18.2.22
-
dev: false
+
'@types/filesystem': 0.0.36
+
'@types/har-format': 1.2.16
-
/@types/json-schema@7.0.15:
-
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
-
dev: true
+
'@types/eslint@9.6.1':
+
dependencies:
+
'@types/estree': 1.0.7
+
'@types/json-schema': 7.0.15
+
optional: true
-
/@types/node@18.17.17:
-
resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==}
-
dev: false
+
'@types/estree-jsx@1.0.5':
+
dependencies:
+
'@types/estree': 1.0.6
-
/@types/node@20.6.2:
-
resolution: {integrity: sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==}
-
dev: false
+
'@types/estree@1.0.6': {}
-
/@types/prop-types@15.7.6:
-
resolution: {integrity: sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==}
-
dev: false
+
'@types/estree@1.0.7':
+
optional: true
-
/@types/react@18.2.22:
-
resolution: {integrity: sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==}
+
'@types/fbemitter@2.0.35': {}
+
+
'@types/filesystem@0.0.36':
dependencies:
-
'@types/prop-types': 15.7.6
-
'@types/scheduler': 0.16.3
-
csstype: 3.1.2
-
dev: false
+
'@types/filewriter': 0.0.33
-
/@types/scheduler@0.16.3:
-
resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
-
dev: false
+
'@types/filewriter@0.0.33': {}
-
/@types/semver@7.5.6:
-
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
-
dev: true
+
'@types/flux@3.1.14':
+
dependencies:
+
'@types/fbemitter': 2.0.35
+
'@types/react': 18.3.20
-
/@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.2):
-
resolution: {integrity: sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==}
-
engines: {node: ^16.0.0 || >=18.0.0}
-
peerDependencies:
-
'@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
-
eslint: ^7.0.0 || ^8.0.0
-
typescript: '*'
-
peerDependenciesMeta:
-
typescript:
-
optional: true
+
'@types/har-format@1.2.16': {}
+
+
'@types/highlightjs@9.12.6': {}
+
+
'@types/json-schema@7.0.15': {}
+
+
'@types/lodash@4.17.14': {}
+
+
'@types/node@18.17.17': {}
+
+
'@types/node@22.13.6':
+
dependencies:
+
undici-types: 6.20.0
+
+
'@types/node@22.14.0':
+
dependencies:
+
undici-types: 6.21.0
+
+
'@types/platform@1.3.6': {}
+
+
'@types/prop-types@15.7.13': {}
+
+
'@types/react@18.3.20':
+
dependencies:
+
'@types/prop-types': 15.7.13
+
csstype: 3.1.3
+
+
'@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)':
dependencies:
-
'@eslint-community/regexpp': 4.10.0
-
'@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
-
'@typescript-eslint/scope-manager': 6.13.2
-
'@typescript-eslint/type-utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
-
'@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
-
'@typescript-eslint/visitor-keys': 6.13.2
-
debug: 4.3.4
-
eslint: 8.55.0
+
'@eslint-community/regexpp': 4.12.1
+
'@typescript-eslint/parser': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
+
'@typescript-eslint/scope-manager': 8.29.0
+
'@typescript-eslint/type-utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
+
'@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
+
'@typescript-eslint/visitor-keys': 8.29.0
+
eslint: 9.23.0(jiti@2.4.2)
graphemer: 1.4.0
-
ignore: 5.3.0
+
ignore: 5.3.2
natural-compare: 1.4.0
-
semver: 7.5.4
-
ts-api-utils: 1.0.3(typescript@5.3.2)
-
typescript: 5.3.2
+
ts-api-utils: 2.1.0(typescript@5.8.2)
+
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
-
dev: true
-
/@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2):
-
resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==}
-
engines: {node: ^16.0.0 || >=18.0.0}
-
peerDependencies:
-
eslint: ^7.0.0 || ^8.0.0
-
typescript: '*'
-
peerDependenciesMeta:
-
typescript:
-
optional: true
+
'@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
-
'@typescript-eslint/scope-manager': 6.13.2
-
'@typescript-eslint/types': 6.13.2
-
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
-
'@typescript-eslint/visitor-keys': 6.13.2
-
debug: 4.3.4
-
eslint: 8.55.0
-
typescript: 5.3.2
+
'@typescript-eslint/scope-manager': 8.29.0
+
'@typescript-eslint/types': 8.29.0
+
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2)
+
'@typescript-eslint/visitor-keys': 8.29.0
+
debug: 4.4.0
+
eslint: 9.23.0(jiti@2.4.2)
+
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
-
dev: true
-
/@typescript-eslint/scope-manager@6.13.2:
-
resolution: {integrity: sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==}
-
engines: {node: ^16.0.0 || >=18.0.0}
+
'@typescript-eslint/scope-manager@8.29.0':
dependencies:
-
'@typescript-eslint/types': 6.13.2
-
'@typescript-eslint/visitor-keys': 6.13.2
-
dev: true
+
'@typescript-eslint/types': 8.29.0
+
'@typescript-eslint/visitor-keys': 8.29.0
-
/@typescript-eslint/type-utils@6.13.2(eslint@8.55.0)(typescript@5.3.2):
-
resolution: {integrity: sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==}
-
engines: {node: ^16.0.0 || >=18.0.0}
-
peerDependencies:
-
eslint: ^7.0.0 || ^8.0.0
-
typescript: '*'
-
peerDependenciesMeta:
-
typescript:
-
optional: true
+
'@typescript-eslint/type-utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
-
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
-
'@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
-
debug: 4.3.4
-
eslint: 8.55.0
-
ts-api-utils: 1.0.3(typescript@5.3.2)
-
typescript: 5.3.2
+
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2)
+
'@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
+
debug: 4.4.0
+
eslint: 9.23.0(jiti@2.4.2)
+
ts-api-utils: 2.1.0(typescript@5.8.2)
+
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
-
dev: true
-
/@typescript-eslint/types@6.13.2:
-
resolution: {integrity: sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==}
-
engines: {node: ^16.0.0 || >=18.0.0}
-
dev: true
+
'@typescript-eslint/types@8.29.0': {}
-
/@typescript-eslint/typescript-estree@6.13.2(typescript@5.3.2):
-
resolution: {integrity: sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==}
-
engines: {node: ^16.0.0 || >=18.0.0}
-
peerDependencies:
-
typescript: '*'
-
peerDependenciesMeta:
-
typescript:
-
optional: true
+
'@typescript-eslint/typescript-estree@8.29.0(typescript@5.8.2)':
dependencies:
-
'@typescript-eslint/types': 6.13.2
-
'@typescript-eslint/visitor-keys': 6.13.2
-
debug: 4.3.4
-
globby: 11.1.0
+
'@typescript-eslint/types': 8.29.0
+
'@typescript-eslint/visitor-keys': 8.29.0
+
debug: 4.4.0
+
fast-glob: 3.3.2
is-glob: 4.0.3
-
semver: 7.5.4
-
ts-api-utils: 1.0.3(typescript@5.3.2)
-
typescript: 5.3.2
+
minimatch: 9.0.5
+
semver: 7.7.1
+
ts-api-utils: 2.1.0(typescript@5.8.2)
+
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
-
dev: true
-
/@typescript-eslint/utils@6.13.2(eslint@8.55.0)(typescript@5.3.2):
-
resolution: {integrity: sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==}
-
engines: {node: ^16.0.0 || >=18.0.0}
-
peerDependencies:
-
eslint: ^7.0.0 || ^8.0.0
+
'@typescript-eslint/utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
dependencies:
-
'@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0)
-
'@types/json-schema': 7.0.15
-
'@types/semver': 7.5.6
-
'@typescript-eslint/scope-manager': 6.13.2
-
'@typescript-eslint/types': 6.13.2
-
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
-
eslint: 8.55.0
-
semver: 7.5.4
+
'@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2))
+
'@typescript-eslint/scope-manager': 8.29.0
+
'@typescript-eslint/types': 8.29.0
+
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2)
+
eslint: 9.23.0(jiti@2.4.2)
+
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
-
- typescript
-
dev: true
-
/@typescript-eslint/visitor-keys@6.13.2:
-
resolution: {integrity: sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==}
-
engines: {node: ^16.0.0 || >=18.0.0}
+
'@typescript-eslint/visitor-keys@8.29.0':
+
dependencies:
+
'@typescript-eslint/types': 8.29.0
+
eslint-visitor-keys: 4.2.0
+
+
'@xterm/xterm@5.5.0':
+
optional: true
+
+
'@zenfs/core@2.0.0':
+
dependencies:
+
'@types/node': 22.13.6
+
buffer: 6.0.3
+
eventemitter3: 5.0.1
+
readable-stream: 4.5.2
+
utilium: 1.10.1
+
+
'@zenfs/dom@1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1)':
dependencies:
-
'@typescript-eslint/types': 6.13.2
-
eslint-visitor-keys: 3.4.3
-
dev: true
+
'@zenfs/core': 2.0.0
+
utilium: 1.10.1
-
/@ungap/structured-clone@1.2.0:
-
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
-
dev: true
+
abort-controller@3.0.0:
+
dependencies:
+
event-target-shim: 5.0.1
-
/acorn-jsx@5.3.2(acorn@8.11.2):
-
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
-
peerDependencies:
-
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
acorn-jsx@5.3.2(acorn@8.14.1):
dependencies:
-
acorn: 8.11.2
-
dev: true
+
acorn: 8.14.1
-
/acorn@8.11.2:
-
resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==}
-
engines: {node: '>=0.4.0'}
-
hasBin: true
-
dev: true
+
acorn@8.14.1: {}
-
/ajv@6.12.6:
-
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0
json-schema-traverse: 0.4.1
uri-js: 4.4.1
-
dev: true
-
/ansi-regex@5.0.1:
-
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
-
engines: {node: '>=8'}
-
dev: true
-
-
/ansi-styles@4.3.0:
-
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
-
engines: {node: '>=8'}
+
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
-
dev: true
-
/argparse@2.0.1:
-
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
-
dev: true
+
ansis@3.17.0: {}
-
/array-buffer-byte-length@1.0.0:
-
resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
+
argparse@2.0.1: {}
+
+
array-buffer-byte-length@1.0.2:
dependencies:
-
call-bind: 1.0.5
-
is-array-buffer: 3.0.2
-
dev: true
+
call-bound: 1.0.4
+
is-array-buffer: 3.0.5
-
/array-includes@3.1.7:
-
resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==}
-
engines: {node: '>= 0.4'}
+
array-includes@3.1.8:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
get-intrinsic: 1.2.2
-
is-string: 1.0.7
-
dev: true
-
-
/array-union@2.1.0:
-
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
-
engines: {node: '>=8'}
-
dev: true
+
es-abstract: 1.23.9
+
es-object-atoms: 1.1.1
+
get-intrinsic: 1.3.0
+
is-string: 1.1.1
-
/array.prototype.flat@1.3.2:
-
resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
-
engines: {node: '>= 0.4'}
+
array.prototype.findlast@1.2.5:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
es-shim-unscopables: 1.0.2
-
dev: true
+
es-abstract: 1.23.9
+
es-errors: 1.3.0
+
es-object-atoms: 1.1.1
+
es-shim-unscopables: 1.1.0
-
/array.prototype.flatmap@1.3.2:
-
resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
-
engines: {node: '>= 0.4'}
+
array.prototype.flat@1.3.3:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
es-shim-unscopables: 1.0.2
-
dev: true
+
es-abstract: 1.23.9
+
es-shim-unscopables: 1.1.0
-
/array.prototype.tosorted@1.1.2:
-
resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==}
+
array.prototype.flatmap@1.3.3:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
es-shim-unscopables: 1.0.2
-
get-intrinsic: 1.2.2
-
dev: true
+
es-abstract: 1.23.9
+
es-shim-unscopables: 1.1.0
-
/arraybuffer.prototype.slice@1.0.2:
-
resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==}
-
engines: {node: '>= 0.4'}
+
array.prototype.tosorted@1.1.4:
dependencies:
-
array-buffer-byte-length: 1.0.0
-
call-bind: 1.0.5
+
call-bind: 1.0.8
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
get-intrinsic: 1.2.2
-
is-array-buffer: 3.0.2
-
is-shared-array-buffer: 1.0.2
-
dev: true
+
es-abstract: 1.23.9
+
es-errors: 1.3.0
+
es-shim-unscopables: 1.1.0
-
/asynciterator.prototype@1.0.0:
-
resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==}
+
arraybuffer.prototype.slice@1.0.4:
dependencies:
-
has-symbols: 1.0.3
-
dev: true
+
array-buffer-byte-length: 1.0.2
+
call-bind: 1.0.8
+
define-properties: 1.2.1
+
es-abstract: 1.23.9
+
es-errors: 1.3.0
+
get-intrinsic: 1.3.0
+
is-array-buffer: 3.0.5
-
/available-typed-arrays@1.0.5:
-
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
-
engines: {node: '>= 0.4'}
-
dev: true
+
astring@1.9.0: {}
-
/balanced-match@1.0.2:
-
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
async-function@1.0.0: {}
-
/big-integer@1.6.52:
-
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
-
engines: {node: '>=0.6'}
-
dev: true
-
-
/bplist-parser@0.2.0:
-
resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==}
-
engines: {node: '>= 5.10.0'}
+
available-typed-arrays@1.0.7:
dependencies:
-
big-integer: 1.6.52
-
dev: true
+
possible-typed-array-names: 1.1.0
-
/brace-expansion@1.1.11:
-
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
balanced-match@1.0.2: {}
+
+
base64-js@1.5.1: {}
+
+
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
-
/braces@3.0.2:
-
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
-
engines: {node: '>=8'}
+
brace-expansion@2.0.1:
dependencies:
-
fill-range: 7.0.1
-
dev: true
+
balanced-match: 1.0.2
-
/bundle-name@3.0.0:
-
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
-
engines: {node: '>=12'}
+
braces@3.0.3:
dependencies:
-
run-applescript: 5.0.0
-
dev: true
+
fill-range: 7.1.1
-
/call-bind@1.0.5:
-
resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
+
buffer@6.0.3:
dependencies:
+
base64-js: 1.5.1
+
ieee754: 1.2.1
+
+
cac@6.7.14: {}
+
+
call-bind-apply-helpers@1.0.2:
+
dependencies:
+
es-errors: 1.3.0
function-bind: 1.1.2
-
get-intrinsic: 1.2.2
-
set-function-length: 1.1.1
-
dev: true
-
/callsites@3.1.0:
-
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
-
engines: {node: '>=6'}
-
dev: true
+
call-bind@1.0.8:
+
dependencies:
+
call-bind-apply-helpers: 1.0.2
+
es-define-property: 1.0.1
+
get-intrinsic: 1.3.0
+
set-function-length: 1.2.2
-
/chalk@4.1.2:
-
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
-
engines: {node: '>=10'}
+
call-bound@1.0.4:
+
dependencies:
+
call-bind-apply-helpers: 1.0.2
+
get-intrinsic: 1.3.0
+
+
callsites@3.1.0: {}
+
+
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
-
dev: true
-
/color-convert@2.0.1:
-
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
-
engines: {node: '>=7.0.0'}
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
-
dev: true
-
/color-name@1.1.4:
-
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
-
dev: true
+
color-name@1.1.4: {}
-
/commander@5.1.0:
-
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
-
engines: {node: '>= 6'}
-
dev: false
-
-
/concat-map@0.0.1:
-
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
+
concat-map@0.0.1: {}
-
/cross-spawn@7.0.3:
-
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
-
engines: {node: '>= 8'}
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
-
dev: true
-
/csstype@3.1.2:
-
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
-
dev: false
+
csstype@3.1.3: {}
-
/debug@4.3.4:
-
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
-
engines: {node: '>=6.0'}
-
peerDependencies:
-
supports-color: '*'
-
peerDependenciesMeta:
-
supports-color:
-
optional: true
+
data-view-buffer@1.0.2:
dependencies:
-
ms: 2.1.2
-
dev: true
+
call-bound: 1.0.4
+
es-errors: 1.3.0
+
is-data-view: 1.0.2
-
/deep-is@0.1.4:
-
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
-
dev: true
-
-
/default-browser-id@3.0.0:
-
resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==}
-
engines: {node: '>=12'}
+
data-view-byte-length@1.0.2:
dependencies:
-
bplist-parser: 0.2.0
-
untildify: 4.0.0
-
dev: true
+
call-bound: 1.0.4
+
es-errors: 1.3.0
+
is-data-view: 1.0.2
-
/default-browser@4.0.0:
-
resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==}
-
engines: {node: '>=14.16'}
+
data-view-byte-offset@1.0.1:
dependencies:
-
bundle-name: 3.0.0
-
default-browser-id: 3.0.0
-
execa: 7.2.0
-
titleize: 3.0.0
-
dev: true
+
call-bound: 1.0.4
+
es-errors: 1.3.0
+
is-data-view: 1.0.2
-
/define-data-property@1.1.1:
-
resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==}
-
engines: {node: '>= 0.4'}
+
debug@4.4.0:
dependencies:
-
get-intrinsic: 1.2.2
-
gopd: 1.0.1
-
has-property-descriptors: 1.0.1
-
dev: true
+
ms: 2.1.3
-
/define-lazy-prop@3.0.0:
-
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
-
engines: {node: '>=12'}
-
dev: true
+
deep-is@0.1.4: {}
-
/define-properties@1.2.1:
-
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
-
engines: {node: '>= 0.4'}
+
define-data-property@1.1.4:
dependencies:
-
define-data-property: 1.1.1
-
has-property-descriptors: 1.0.1
-
object-keys: 1.1.1
-
dev: true
+
es-define-property: 1.0.1
+
es-errors: 1.3.0
+
gopd: 1.2.0
-
/dir-glob@3.0.1:
-
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
-
engines: {node: '>=8'}
+
define-properties@1.2.1:
dependencies:
-
path-type: 4.0.0
-
dev: true
+
define-data-property: 1.1.4
+
has-property-descriptors: 1.0.2
+
object-keys: 1.1.1
-
/doctrine@2.1.0:
-
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
-
engines: {node: '>=0.10.0'}
+
defu@6.1.4: {}
+
+
destr@2.0.4: {}
+
+
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
-
dev: true
-
/doctrine@3.0.0:
-
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
-
engines: {node: '>=6.0.0'}
+
dunder-proto@1.0.1:
dependencies:
-
esutils: 2.0.3
-
dev: true
+
call-bind-apply-helpers: 1.0.2
+
es-errors: 1.3.0
+
gopd: 1.2.0
-
/es-abstract@1.22.3:
-
resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==}
-
engines: {node: '>= 0.4'}
+
es-abstract@1.23.9:
dependencies:
-
array-buffer-byte-length: 1.0.0
-
arraybuffer.prototype.slice: 1.0.2
-
available-typed-arrays: 1.0.5
-
call-bind: 1.0.5
-
es-set-tostringtag: 2.0.2
-
es-to-primitive: 1.2.1
-
function.prototype.name: 1.1.6
-
get-intrinsic: 1.2.2
-
get-symbol-description: 1.0.0
-
globalthis: 1.0.3
-
gopd: 1.0.1
-
has-property-descriptors: 1.0.1
-
has-proto: 1.0.1
-
has-symbols: 1.0.3
-
hasown: 2.0.0
-
internal-slot: 1.0.6
-
is-array-buffer: 3.0.2
+
array-buffer-byte-length: 1.0.2
+
arraybuffer.prototype.slice: 1.0.4
+
available-typed-arrays: 1.0.7
+
call-bind: 1.0.8
+
call-bound: 1.0.4
+
data-view-buffer: 1.0.2
+
data-view-byte-length: 1.0.2
+
data-view-byte-offset: 1.0.1
+
es-define-property: 1.0.1
+
es-errors: 1.3.0
+
es-object-atoms: 1.1.1
+
es-set-tostringtag: 2.1.0
+
es-to-primitive: 1.3.0
+
function.prototype.name: 1.1.8
+
get-intrinsic: 1.3.0
+
get-proto: 1.0.1
+
get-symbol-description: 1.1.0
+
globalthis: 1.0.4
+
gopd: 1.2.0
+
has-property-descriptors: 1.0.2
+
has-proto: 1.2.0
+
has-symbols: 1.1.0
+
hasown: 2.0.2
+
internal-slot: 1.1.0
+
is-array-buffer: 3.0.5
is-callable: 1.2.7
-
is-negative-zero: 2.0.2
-
is-regex: 1.1.4
-
is-shared-array-buffer: 1.0.2
-
is-string: 1.0.7
-
is-typed-array: 1.1.12
-
is-weakref: 1.0.2
-
object-inspect: 1.13.1
+
is-data-view: 1.0.2
+
is-regex: 1.2.1
+
is-shared-array-buffer: 1.0.4
+
is-string: 1.1.1
+
is-typed-array: 1.1.15
+
is-weakref: 1.1.1
+
math-intrinsics: 1.1.0
+
object-inspect: 1.13.4
object-keys: 1.1.1
-
object.assign: 4.1.5
-
regexp.prototype.flags: 1.5.1
-
safe-array-concat: 1.0.1
-
safe-regex-test: 1.0.0
-
string.prototype.trim: 1.2.8
-
string.prototype.trimend: 1.0.7
-
string.prototype.trimstart: 1.0.7
-
typed-array-buffer: 1.0.0
-
typed-array-byte-length: 1.0.0
-
typed-array-byte-offset: 1.0.0
-
typed-array-length: 1.0.4
-
unbox-primitive: 1.0.2
-
which-typed-array: 1.1.13
-
dev: true
+
object.assign: 4.1.7
+
own-keys: 1.0.1
+
regexp.prototype.flags: 1.5.4
+
safe-array-concat: 1.1.3
+
safe-push-apply: 1.0.0
+
safe-regex-test: 1.1.0
+
set-proto: 1.0.0
+
string.prototype.trim: 1.2.10
+
string.prototype.trimend: 1.0.9
+
string.prototype.trimstart: 1.0.8
+
typed-array-buffer: 1.0.3
+
typed-array-byte-length: 1.0.3
+
typed-array-byte-offset: 1.0.4
+
typed-array-length: 1.0.7
+
unbox-primitive: 1.1.0
+
which-typed-array: 1.1.19
-
/es-iterator-helpers@1.0.15:
-
resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==}
+
es-define-property@1.0.1: {}
+
+
es-errors@1.3.0: {}
+
+
es-iterator-helpers@1.2.1:
dependencies:
-
asynciterator.prototype: 1.0.0
-
call-bind: 1.0.5
+
call-bind: 1.0.8
+
call-bound: 1.0.4
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
es-set-tostringtag: 2.0.2
+
es-abstract: 1.23.9
+
es-errors: 1.3.0
+
es-set-tostringtag: 2.1.0
function-bind: 1.1.2
-
get-intrinsic: 1.2.2
-
globalthis: 1.0.3
-
has-property-descriptors: 1.0.1
-
has-proto: 1.0.1
-
has-symbols: 1.0.3
-
internal-slot: 1.0.6
-
iterator.prototype: 1.1.2
-
safe-array-concat: 1.0.1
-
dev: true
+
get-intrinsic: 1.3.0
+
globalthis: 1.0.4
+
gopd: 1.2.0
+
has-property-descriptors: 1.0.2
+
has-proto: 1.2.0
+
has-symbols: 1.1.0
+
internal-slot: 1.1.0
+
iterator.prototype: 1.1.5
+
safe-array-concat: 1.1.3
-
/es-set-tostringtag@2.0.2:
-
resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==}
-
engines: {node: '>= 0.4'}
+
es-object-atoms@1.1.1:
dependencies:
-
get-intrinsic: 1.2.2
-
has-tostringtag: 1.0.0
-
hasown: 2.0.0
-
dev: true
+
es-errors: 1.3.0
-
/es-shim-unscopables@1.0.2:
-
resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
+
es-set-tostringtag@2.1.0:
+
dependencies:
+
es-errors: 1.3.0
+
get-intrinsic: 1.3.0
+
has-tostringtag: 1.0.2
+
hasown: 2.0.2
+
+
es-shim-unscopables@1.1.0:
dependencies:
-
hasown: 2.0.0
-
dev: true
+
hasown: 2.0.2
-
/es-to-primitive@1.2.1:
-
resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
-
engines: {node: '>= 0.4'}
+
es-to-primitive@1.3.0:
dependencies:
is-callable: 1.2.7
-
is-date-object: 1.0.5
-
is-symbol: 1.0.4
-
dev: true
+
is-date-object: 1.1.0
+
is-symbol: 1.1.1
-
/esbuild-copy-static-files@0.1.0:
-
resolution: {integrity: sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w==}
-
dev: true
+
esbuild-copy-static-files@0.1.0: {}
-
/esbuild@0.19.3:
-
resolution: {integrity: sha512-UlJ1qUUA2jL2nNib1JTSkifQTcYTroFqRjwCFW4QYEKEsixXD5Tik9xML7zh2gTxkYTBKGHNH9y7txMwVyPbjw==}
-
engines: {node: '>=12'}
-
hasBin: true
-
requiresBuild: true
+
esbuild@0.19.3:
optionalDependencies:
'@esbuild/android-arm': 0.19.3
'@esbuild/android-arm64': 0.19.3
···
'@esbuild/win32-arm64': 0.19.3
'@esbuild/win32-ia32': 0.19.3
'@esbuild/win32-x64': 0.19.3
-
dev: true
-
/escape-string-regexp@4.0.0:
-
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
-
engines: {node: '>=10'}
-
dev: true
+
escape-string-regexp@4.0.0: {}
-
/eslint-config-prettier@9.1.0(eslint@8.55.0):
-
resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
-
hasBin: true
-
peerDependencies:
-
eslint: '>=7.0.0'
+
eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)):
dependencies:
-
eslint: 8.55.0
-
dev: true
+
eslint: 9.23.0(jiti@2.4.2)
-
/eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.1.0)(eslint@8.55.0)(prettier@3.1.0):
-
resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==}
-
engines: {node: ^14.18.0 || >=16.0.0}
-
peerDependencies:
-
'@types/eslint': '>=8.0.0'
-
eslint: '>=8.0.0'
-
eslint-config-prettier: '*'
-
prettier: '>=3.0.0'
-
peerDependenciesMeta:
-
'@types/eslint':
-
optional: true
-
eslint-config-prettier:
-
optional: true
+
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):
dependencies:
-
eslint: 8.55.0
-
eslint-config-prettier: 9.1.0(eslint@8.55.0)
+
eslint: 9.23.0(jiti@2.4.2)
prettier: 3.1.0
prettier-linter-helpers: 1.0.0
-
synckit: 0.8.6
-
dev: true
+
synckit: 0.11.1
+
optionalDependencies:
+
'@types/eslint': 9.6.1
+
eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@2.4.2))
-
/eslint-plugin-react@7.33.2(eslint@8.55.0):
-
resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==}
-
engines: {node: '>=4'}
-
peerDependencies:
-
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
+
eslint-plugin-react@7.37.5(eslint@9.23.0(jiti@2.4.2)):
dependencies:
-
array-includes: 3.1.7
-
array.prototype.flatmap: 1.3.2
-
array.prototype.tosorted: 1.1.2
+
array-includes: 3.1.8
+
array.prototype.findlast: 1.2.5
+
array.prototype.flatmap: 1.3.3
+
array.prototype.tosorted: 1.1.4
doctrine: 2.1.0
-
es-iterator-helpers: 1.0.15
-
eslint: 8.55.0
+
es-iterator-helpers: 1.2.1
+
eslint: 9.23.0(jiti@2.4.2)
estraverse: 5.3.0
+
hasown: 2.0.2
jsx-ast-utils: 3.3.5
minimatch: 3.1.2
-
object.entries: 1.1.7
-
object.fromentries: 2.0.7
-
object.hasown: 1.1.3
-
object.values: 1.1.7
+
object.entries: 1.1.9
+
object.fromentries: 2.0.8
+
object.values: 1.2.1
prop-types: 15.8.1
resolve: 2.0.0-next.5
semver: 6.3.1
-
string.prototype.matchall: 4.0.10
-
dev: true
+
string.prototype.matchall: 4.0.12
+
string.prototype.repeat: 1.0.0
-
/eslint-scope@7.2.2:
-
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
eslint-scope@8.3.0:
dependencies:
esrecurse: 4.3.0
estraverse: 5.3.0
-
dev: true
-
/eslint-visitor-keys@3.4.3:
-
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-
dev: true
+
eslint-visitor-keys@3.4.3: {}
-
/eslint@8.55.0:
-
resolution: {integrity: sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==}
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-
hasBin: true
+
eslint-visitor-keys@4.2.0: {}
+
+
eslint@9.23.0(jiti@2.4.2):
dependencies:
-
'@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0)
-
'@eslint-community/regexpp': 4.10.0
-
'@eslint/eslintrc': 2.1.4
-
'@eslint/js': 8.55.0
-
'@humanwhocodes/config-array': 0.11.13
+
'@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2))
+
'@eslint-community/regexpp': 4.12.1
+
'@eslint/config-array': 0.19.2
+
'@eslint/config-helpers': 0.2.1
+
'@eslint/core': 0.12.0
+
'@eslint/eslintrc': 3.3.1
+
'@eslint/js': 9.23.0
+
'@eslint/plugin-kit': 0.2.8
+
'@humanfs/node': 0.16.6
'@humanwhocodes/module-importer': 1.0.1
-
'@nodelib/fs.walk': 1.2.8
-
'@ungap/structured-clone': 1.2.0
+
'@humanwhocodes/retry': 0.4.2
+
'@types/estree': 1.0.6
+
'@types/json-schema': 7.0.15
ajv: 6.12.6
chalk: 4.1.2
-
cross-spawn: 7.0.3
-
debug: 4.3.4
-
doctrine: 3.0.0
+
cross-spawn: 7.0.6
+
debug: 4.4.0
escape-string-regexp: 4.0.0
-
eslint-scope: 7.2.2
-
eslint-visitor-keys: 3.4.3
-
espree: 9.6.1
-
esquery: 1.5.0
+
eslint-scope: 8.3.0
+
eslint-visitor-keys: 4.2.0
+
espree: 10.3.0
+
esquery: 1.6.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
-
file-entry-cache: 6.0.1
+
file-entry-cache: 8.0.0
find-up: 5.0.0
glob-parent: 6.0.2
-
globals: 13.23.0
-
graphemer: 1.4.0
-
ignore: 5.3.0
+
ignore: 5.3.2
imurmurhash: 0.1.4
is-glob: 4.0.3
-
is-path-inside: 3.0.3
-
js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1
-
levn: 0.4.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.3
-
strip-ansi: 6.0.1
-
text-table: 0.2.0
+
optionalDependencies:
+
jiti: 2.4.2
transitivePeerDependencies:
- supports-color
-
dev: true
-
/espree@9.6.1:
-
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
espree@10.3.0:
dependencies:
-
acorn: 8.11.2
-
acorn-jsx: 5.3.2(acorn@8.11.2)
-
eslint-visitor-keys: 3.4.3
-
dev: true
+
acorn: 8.14.1
+
acorn-jsx: 5.3.2(acorn@8.14.1)
+
eslint-visitor-keys: 4.2.0
-
/esquery@1.5.0:
-
resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
-
engines: {node: '>=0.10'}
+
esquery@1.6.0:
dependencies:
estraverse: 5.3.0
-
dev: true
-
/esrecurse@4.3.0:
-
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
-
engines: {node: '>=4.0'}
+
esrecurse@4.3.0:
dependencies:
estraverse: 5.3.0
-
dev: true
-
/estraverse@5.3.0:
-
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
-
engines: {node: '>=4.0'}
-
dev: true
+
estraverse@5.3.0: {}
-
/esutils@2.0.3:
-
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
-
engines: {node: '>=0.10.0'}
-
dev: true
-
-
/execa@5.1.1:
-
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
-
engines: {node: '>=10'}
+
estree-toolkit@1.7.8:
dependencies:
-
cross-spawn: 7.0.3
-
get-stream: 6.0.1
-
human-signals: 2.1.0
-
is-stream: 2.0.1
-
merge-stream: 2.0.0
-
npm-run-path: 4.0.1
-
onetime: 5.1.2
-
signal-exit: 3.0.7
-
strip-final-newline: 2.0.0
-
dev: true
+
'@types/estree': 1.0.6
+
'@types/estree-jsx': 1.0.5
-
/execa@7.2.0:
-
resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
-
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
-
dependencies:
-
cross-spawn: 7.0.3
-
get-stream: 6.0.1
-
human-signals: 4.3.1
-
is-stream: 3.0.0
-
merge-stream: 2.0.0
-
npm-run-path: 5.1.0
-
onetime: 6.0.0
-
signal-exit: 3.0.7
-
strip-final-newline: 3.0.0
-
dev: true
+
esutils@2.0.3: {}
-
/fast-deep-equal@3.1.3:
-
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
-
dev: true
+
event-target-shim@5.0.1: {}
-
/fast-diff@1.3.0:
-
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
-
dev: true
+
eventemitter3@5.0.1: {}
-
/fast-glob@3.3.2:
-
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
-
engines: {node: '>=8.6.0'}
+
events@3.3.0: {}
+
+
fast-deep-equal@3.1.3: {}
+
+
fast-diff@1.3.0: {}
+
+
fast-glob@3.3.2:
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
-
micromatch: 4.0.5
-
dev: true
+
micromatch: 4.0.8
-
/fast-json-stable-stringify@2.1.0:
-
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
-
dev: true
+
fast-json-stable-stringify@2.1.0: {}
-
/fast-levenshtein@2.0.6:
-
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
-
dev: true
+
fast-levenshtein@2.0.6: {}
-
/fastq@1.15.0:
-
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
+
fastq@1.17.1:
dependencies:
reusify: 1.0.4
-
dev: true
-
/file-entry-cache@6.0.1:
-
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
-
engines: {node: ^10.12.0 || >=12.0.0}
+
fdir@6.4.3(picomatch@4.0.2):
+
optionalDependencies:
+
picomatch: 4.0.2
+
+
file-entry-cache@8.0.0:
dependencies:
-
flat-cache: 3.2.0
-
dev: true
+
flat-cache: 4.0.1
-
/fill-range@7.0.1:
-
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
-
engines: {node: '>=8'}
+
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
-
dev: true
-
/find-up@5.0.0:
-
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
-
engines: {node: '>=10'}
+
find-up-simple@1.0.1: {}
+
+
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
-
dev: true
-
/flat-cache@3.2.0:
-
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
-
engines: {node: ^10.12.0 || >=12.0.0}
+
flat-cache@4.0.1:
dependencies:
flatted: 3.2.9
keyv: 4.5.4
-
rimraf: 3.0.2
-
dev: true
-
/flatted@3.2.9:
-
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
-
dev: true
+
flatted@3.2.9: {}
-
/for-each@0.3.3:
-
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
+
for-each@0.3.5:
dependencies:
is-callable: 1.2.7
-
dev: true
-
/fs.realpath@1.0.0:
-
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
function-bind@1.1.2: {}
-
/function-bind@1.1.2:
-
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
-
dev: true
-
-
/function.prototype.name@1.1.6:
-
resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
-
engines: {node: '>= 0.4'}
+
function.prototype.name@1.1.8:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
+
call-bound: 1.0.4
define-properties: 1.2.1
-
es-abstract: 1.22.3
functions-have-names: 1.2.3
-
dev: true
+
hasown: 2.0.2
+
is-callable: 1.2.7
+
+
functions-have-names@1.2.3: {}
-
/functions-have-names@1.2.3:
-
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
-
dev: true
+
fzf@0.5.2: {}
-
/get-intrinsic@1.2.2:
-
resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==}
+
get-intrinsic@1.3.0:
dependencies:
+
call-bind-apply-helpers: 1.0.2
+
es-define-property: 1.0.1
+
es-errors: 1.3.0
+
es-object-atoms: 1.1.1
function-bind: 1.1.2
-
has-proto: 1.0.1
-
has-symbols: 1.0.3
-
hasown: 2.0.0
-
dev: true
-
-
/get-stream@6.0.1:
-
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
-
engines: {node: '>=10'}
-
dev: true
+
get-proto: 1.0.1
+
gopd: 1.2.0
+
has-symbols: 1.1.0
+
hasown: 2.0.2
+
math-intrinsics: 1.1.0
-
/get-symbol-description@1.0.0:
-
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
-
engines: {node: '>= 0.4'}
+
get-proto@1.0.1:
dependencies:
-
call-bind: 1.0.5
-
get-intrinsic: 1.2.2
-
dev: true
+
dunder-proto: 1.0.1
+
es-object-atoms: 1.1.1
-
/glob-parent@5.1.2:
-
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
-
engines: {node: '>= 6'}
+
get-symbol-description@1.1.0:
dependencies:
-
is-glob: 4.0.3
-
dev: true
+
call-bound: 1.0.4
+
es-errors: 1.3.0
+
get-intrinsic: 1.3.0
-
/glob-parent@6.0.2:
-
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
-
engines: {node: '>=10.13.0'}
+
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
-
dev: true
-
/glob@7.2.3:
-
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+
glob-parent@6.0.2:
dependencies:
-
fs.realpath: 1.0.0
-
inflight: 1.0.6
-
inherits: 2.0.4
-
minimatch: 3.1.2
-
once: 1.4.0
-
path-is-absolute: 1.0.1
+
is-glob: 4.0.3
-
/globals@13.23.0:
-
resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==}
-
engines: {node: '>=8'}
-
dependencies:
-
type-fest: 0.20.2
-
dev: true
+
globals@14.0.0: {}
-
/globalthis@1.0.3:
-
resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
-
engines: {node: '>= 0.4'}
+
globalthis@1.0.4:
dependencies:
define-properties: 1.2.1
-
dev: true
+
gopd: 1.2.0
-
/globby@11.1.0:
-
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
-
engines: {node: '>=10'}
-
dependencies:
-
array-union: 2.1.0
-
dir-glob: 3.0.1
-
fast-glob: 3.3.2
-
ignore: 5.3.0
-
merge2: 1.4.1
-
slash: 3.0.0
-
dev: true
+
gopd@1.2.0: {}
-
/gopd@1.0.1:
-
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
-
dependencies:
-
get-intrinsic: 1.2.2
-
dev: true
+
graphemer@1.4.0: {}
-
/graphemer@1.4.0:
-
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
-
dev: true
+
has-bigints@1.1.0: {}
-
/has-bigints@1.0.2:
-
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
-
dev: true
+
has-flag@4.0.0: {}
-
/has-flag@4.0.0:
-
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
-
engines: {node: '>=8'}
-
dev: true
+
has-property-descriptors@1.0.2:
+
dependencies:
+
es-define-property: 1.0.1
-
/has-property-descriptors@1.0.1:
-
resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==}
+
has-proto@1.2.0:
dependencies:
-
get-intrinsic: 1.2.2
-
dev: true
+
dunder-proto: 1.0.1
-
/has-proto@1.0.1:
-
resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
-
engines: {node: '>= 0.4'}
-
dev: true
-
-
/has-symbols@1.0.3:
-
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
-
engines: {node: '>= 0.4'}
-
dev: true
+
has-symbols@1.1.0: {}
-
/has-tostringtag@1.0.0:
-
resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
-
engines: {node: '>= 0.4'}
+
has-tostringtag@1.0.2:
dependencies:
-
has-symbols: 1.0.3
-
dev: true
+
has-symbols: 1.1.0
-
/hasown@2.0.0:
-
resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
-
engines: {node: '>= 0.4'}
+
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
-
dev: true
-
/human-signals@2.1.0:
-
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
-
engines: {node: '>=10.17.0'}
-
dev: true
+
husky@8.0.3: {}
-
/human-signals@4.3.1:
-
resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
-
engines: {node: '>=14.18.0'}
-
dev: true
+
ieee754@1.2.1: {}
-
/ignore@5.3.0:
-
resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==}
-
engines: {node: '>= 4'}
-
dev: true
+
ignore@5.3.2: {}
-
/import-fresh@3.3.0:
-
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
-
engines: {node: '>=6'}
+
import-fresh@3.3.0:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
-
dev: true
-
/imurmurhash@0.1.4:
-
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
-
engines: {node: '>=0.8.19'}
-
dev: true
+
imurmurhash@0.1.4: {}
-
/inflight@1.0.6:
-
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+
internal-slot@1.1.0:
dependencies:
-
once: 1.4.0
-
wrappy: 1.0.2
+
es-errors: 1.3.0
+
hasown: 2.0.2
+
side-channel: 1.1.0
-
/inherits@2.0.4:
-
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
is-array-buffer@3.0.5:
+
dependencies:
+
call-bind: 1.0.8
+
call-bound: 1.0.4
+
get-intrinsic: 1.3.0
-
/internal-slot@1.0.6:
-
resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==}
-
engines: {node: '>= 0.4'}
+
is-async-function@2.1.1:
dependencies:
-
get-intrinsic: 1.2.2
-
hasown: 2.0.0
-
side-channel: 1.0.4
-
dev: true
+
async-function: 1.0.0
+
call-bound: 1.0.4
+
get-proto: 1.0.1
+
has-tostringtag: 1.0.2
+
safe-regex-test: 1.1.0
-
/is-array-buffer@3.0.2:
-
resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
+
is-bigint@1.1.0:
dependencies:
-
call-bind: 1.0.5
-
get-intrinsic: 1.2.2
-
is-typed-array: 1.1.12
-
dev: true
+
has-bigints: 1.1.0
-
/is-async-function@2.0.0:
-
resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==}
-
engines: {node: '>= 0.4'}
+
is-boolean-object@1.2.2:
dependencies:
-
has-tostringtag: 1.0.0
-
dev: true
+
call-bound: 1.0.4
+
has-tostringtag: 1.0.2
-
/is-bigint@1.0.4:
-
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
-
dependencies:
-
has-bigints: 1.0.2
-
dev: true
+
is-callable@1.2.7: {}
-
/is-boolean-object@1.1.2:
-
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
-
engines: {node: '>= 0.4'}
+
is-core-module@2.16.1:
dependencies:
-
call-bind: 1.0.5
-
has-tostringtag: 1.0.0
-
dev: true
-
-
/is-callable@1.2.7:
-
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
-
engines: {node: '>= 0.4'}
-
dev: true
+
hasown: 2.0.2
-
/is-core-module@2.13.1:
-
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
+
is-data-view@1.0.2:
dependencies:
-
hasown: 2.0.0
-
dev: true
+
call-bound: 1.0.4
+
get-intrinsic: 1.3.0
+
is-typed-array: 1.1.15
-
/is-date-object@1.0.5:
-
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
-
engines: {node: '>= 0.4'}
+
is-date-object@1.1.0:
dependencies:
-
has-tostringtag: 1.0.0
-
dev: true
+
call-bound: 1.0.4
+
has-tostringtag: 1.0.2
-
/is-docker@2.2.1:
-
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
-
engines: {node: '>=8'}
-
hasBin: true
-
dev: true
-
-
/is-docker@3.0.0:
-
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
hasBin: true
-
dev: true
+
is-extglob@2.1.1: {}
-
/is-extglob@2.1.1:
-
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
-
engines: {node: '>=0.10.0'}
-
dev: true
-
-
/is-finalizationregistry@1.0.2:
-
resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==}
+
is-finalizationregistry@1.1.1:
dependencies:
-
call-bind: 1.0.5
-
dev: true
+
call-bound: 1.0.4
-
/is-generator-function@1.0.10:
-
resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
-
engines: {node: '>= 0.4'}
+
is-generator-function@1.1.0:
dependencies:
-
has-tostringtag: 1.0.0
-
dev: true
+
call-bound: 1.0.4
+
get-proto: 1.0.1
+
has-tostringtag: 1.0.2
+
safe-regex-test: 1.1.0
-
/is-glob@4.0.3:
-
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
-
engines: {node: '>=0.10.0'}
+
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
-
dev: true
-
/is-inside-container@1.0.0:
-
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
-
engines: {node: '>=14.16'}
-
hasBin: true
-
dependencies:
-
is-docker: 3.0.0
-
dev: true
-
-
/is-map@2.0.2:
-
resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
-
dev: true
-
-
/is-negative-zero@2.0.2:
-
resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
-
engines: {node: '>= 0.4'}
-
dev: true
+
is-map@2.0.3: {}
-
/is-number-object@1.0.7:
-
resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
-
engines: {node: '>= 0.4'}
+
is-number-object@1.1.1:
dependencies:
-
has-tostringtag: 1.0.0
-
dev: true
-
-
/is-number@7.0.0:
-
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
-
engines: {node: '>=0.12.0'}
-
dev: true
+
call-bound: 1.0.4
+
has-tostringtag: 1.0.2
-
/is-path-inside@3.0.3:
-
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
-
engines: {node: '>=8'}
-
dev: true
+
is-number@7.0.0: {}
-
/is-regex@1.1.4:
-
resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
-
engines: {node: '>= 0.4'}
+
is-regex@1.2.1:
dependencies:
-
call-bind: 1.0.5
-
has-tostringtag: 1.0.0
-
dev: true
+
call-bound: 1.0.4
+
gopd: 1.2.0
+
has-tostringtag: 1.0.2
+
hasown: 2.0.2
-
/is-set@2.0.2:
-
resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==}
-
dev: true
+
is-set@2.0.3: {}
-
/is-shared-array-buffer@1.0.2:
-
resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
+
is-shared-array-buffer@1.0.4:
dependencies:
-
call-bind: 1.0.5
-
dev: true
+
call-bound: 1.0.4
-
/is-stream@2.0.1:
-
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
-
engines: {node: '>=8'}
-
dev: true
-
-
/is-stream@3.0.0:
-
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
dev: true
-
-
/is-string@1.0.7:
-
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
-
engines: {node: '>= 0.4'}
+
is-string@1.1.1:
dependencies:
-
has-tostringtag: 1.0.0
-
dev: true
+
call-bound: 1.0.4
+
has-tostringtag: 1.0.2
-
/is-symbol@1.0.4:
-
resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
-
engines: {node: '>= 0.4'}
+
is-symbol@1.1.1:
dependencies:
-
has-symbols: 1.0.3
-
dev: true
+
call-bound: 1.0.4
+
has-symbols: 1.1.0
+
safe-regex-test: 1.1.0
-
/is-typed-array@1.1.12:
-
resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==}
-
engines: {node: '>= 0.4'}
+
is-typed-array@1.1.15:
dependencies:
-
which-typed-array: 1.1.13
-
dev: true
+
which-typed-array: 1.1.19
-
/is-weakmap@2.0.1:
-
resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==}
-
dev: true
+
is-weakmap@2.0.2: {}
-
/is-weakref@1.0.2:
-
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
+
is-weakref@1.1.1:
dependencies:
-
call-bind: 1.0.5
-
dev: true
+
call-bound: 1.0.4
-
/is-weakset@2.0.2:
-
resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==}
+
is-weakset@2.0.4:
dependencies:
-
call-bind: 1.0.5
-
get-intrinsic: 1.2.2
-
dev: true
+
call-bound: 1.0.4
+
get-intrinsic: 1.3.0
-
/is-wsl@2.2.0:
-
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
-
engines: {node: '>=8'}
-
dependencies:
-
is-docker: 2.2.1
-
dev: true
+
isarray@2.0.5: {}
-
/isarray@2.0.5:
-
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
-
dev: true
-
-
/isexe@2.0.0:
-
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
-
dev: true
+
isexe@2.0.0: {}
-
/iterator.prototype@1.1.2:
-
resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==}
+
iterator.prototype@1.1.5:
dependencies:
-
define-properties: 1.2.1
-
get-intrinsic: 1.2.2
-
has-symbols: 1.0.3
-
reflect.getprototypeof: 1.0.4
-
set-function-name: 2.0.1
-
dev: true
+
define-data-property: 1.1.4
+
es-object-atoms: 1.1.1
+
get-intrinsic: 1.3.0
+
get-proto: 1.0.1
+
has-symbols: 1.1.0
+
set-function-name: 2.0.2
-
/js-tokens@4.0.0:
-
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
-
dev: true
+
jiti@2.4.2: {}
-
/js-yaml@4.1.0:
-
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
-
hasBin: true
+
js-tokens@4.0.0: {}
+
+
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
-
dev: true
-
/json-buffer@3.0.1:
-
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
-
dev: true
+
json-buffer@3.0.1: {}
-
/json-schema-traverse@0.4.1:
-
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
-
dev: true
+
json-schema-traverse@0.4.1: {}
-
/json-stable-stringify-without-jsonify@1.0.1:
-
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
-
dev: true
+
json-stable-stringify-without-jsonify@1.0.1: {}
-
/jsx-ast-utils@3.3.5:
-
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
-
engines: {node: '>=4.0'}
+
jsx-ast-utils@3.3.5:
dependencies:
-
array-includes: 3.1.7
-
array.prototype.flat: 1.3.2
-
object.assign: 4.1.5
-
object.values: 1.1.7
-
dev: true
+
array-includes: 3.1.8
+
array.prototype.flat: 1.3.3
+
object.assign: 4.1.7
+
object.values: 1.2.1
-
/keyv@4.5.4:
-
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
-
dev: true
-
/levn@0.4.1:
-
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
-
engines: {node: '>= 0.8.0'}
+
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
-
dev: true
-
/locate-path@6.0.0:
-
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
-
engines: {node: '>=10'}
+
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
-
dev: true
-
/lodash.merge@4.6.2:
-
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
-
dev: true
+
lodash.merge@4.6.2: {}
-
/loose-envify@1.4.0:
-
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
-
hasBin: true
+
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
-
dev: true
-
/lru-cache@6.0.0:
-
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
-
engines: {node: '>=10'}
-
dependencies:
-
yallist: 4.0.0
-
dev: true
+
math-intrinsics@1.1.0: {}
-
/merge-stream@2.0.0:
-
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
-
dev: true
+
merge2@1.4.1: {}
-
/merge2@1.4.1:
-
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
-
engines: {node: '>= 8'}
-
dev: true
+
meriyah@6.0.1: {}
-
/micromatch@4.0.5:
-
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
-
engines: {node: '>=8.6'}
+
microdiff@1.5.0: {}
+
+
micromatch@4.0.8:
dependencies:
-
braces: 3.0.2
+
braces: 3.0.3
picomatch: 2.3.1
-
dev: true
-
/mimic-fn@2.1.0:
-
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
-
engines: {node: '>=6'}
-
dev: true
+
mimic-function@5.0.1: {}
-
/mimic-fn@4.0.0:
-
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
-
engines: {node: '>=12'}
-
dev: true
-
-
/minimatch@3.1.2:
-
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
-
/ms@2.1.2:
-
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
-
dev: true
+
minimatch@9.0.5:
+
dependencies:
+
brace-expansion: 2.0.1
-
/natural-compare@1.4.0:
-
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
-
dev: true
+
ms@2.1.3: {}
-
/npm-run-path@4.0.1:
-
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
-
engines: {node: '>=8'}
-
dependencies:
-
path-key: 3.1.1
-
dev: true
+
nanotar@0.1.1: {}
-
/npm-run-path@5.1.0:
-
resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==}
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
dependencies:
-
path-key: 4.0.0
-
dev: true
+
natural-compare@1.4.0: {}
-
/object-assign@4.1.1:
-
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
-
engines: {node: '>=0.10.0'}
-
dev: true
+
node-fetch-native@1.6.6: {}
-
/object-inspect@1.13.1:
-
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
-
dev: true
+
object-assign@4.1.1: {}
-
/object-keys@1.1.1:
-
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
-
engines: {node: '>= 0.4'}
-
dev: true
+
object-inspect@1.13.4: {}
-
/object.assign@4.1.5:
-
resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
-
engines: {node: '>= 0.4'}
+
object-keys@1.1.1: {}
+
+
object.assign@4.1.7:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
+
call-bound: 1.0.4
define-properties: 1.2.1
-
has-symbols: 1.0.3
+
es-object-atoms: 1.1.1
+
has-symbols: 1.1.0
object-keys: 1.1.1
-
dev: true
-
/object.entries@1.1.7:
-
resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==}
-
engines: {node: '>= 0.4'}
+
object.entries@1.1.9:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
+
call-bound: 1.0.4
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
dev: true
+
es-object-atoms: 1.1.1
-
/object.fromentries@2.0.7:
-
resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==}
-
engines: {node: '>= 0.4'}
+
object.fromentries@2.0.8:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
dev: true
+
es-abstract: 1.23.9
+
es-object-atoms: 1.1.1
-
/object.hasown@1.1.3:
-
resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==}
+
object.values@1.2.1:
dependencies:
+
call-bind: 1.0.8
+
call-bound: 1.0.4
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
dev: true
-
-
/object.values@1.1.7:
-
resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==}
-
engines: {node: '>= 0.4'}
-
dependencies:
-
call-bind: 1.0.5
-
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
dev: true
+
es-object-atoms: 1.1.1
-
/once@1.4.0:
-
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
ofetch@1.4.1:
dependencies:
-
wrappy: 1.0.2
+
destr: 2.0.4
+
node-fetch-native: 1.6.6
+
ufo: 1.5.4
-
/onetime@5.1.2:
-
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
-
engines: {node: '>=6'}
+
onetime@7.0.0:
dependencies:
-
mimic-fn: 2.1.0
-
dev: true
+
mimic-function: 5.0.1
-
/onetime@6.0.0:
-
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
-
engines: {node: '>=12'}
-
dependencies:
-
mimic-fn: 4.0.0
-
dev: true
-
-
/open@9.1.0:
-
resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==}
-
engines: {node: '>=14.16'}
-
dependencies:
-
default-browser: 4.0.0
-
define-lazy-prop: 3.0.0
-
is-inside-container: 1.0.0
-
is-wsl: 2.2.0
-
dev: true
-
-
/optionator@0.9.3:
-
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
-
engines: {node: '>= 0.8.0'}
+
optionator@0.9.3:
dependencies:
'@aashutoshrathi/word-wrap': 1.2.6
deep-is: 0.1.4
···
levn: 0.4.1
prelude-ls: 1.2.1
type-check: 0.4.0
-
dev: true
+
+
own-keys@1.0.1:
+
dependencies:
+
get-intrinsic: 1.3.0
+
object-keys: 1.1.1
+
safe-push-apply: 1.0.0
-
/p-limit@3.1.0:
-
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
-
engines: {node: '>=10'}
+
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
-
dev: true
-
/p-locate@5.0.0:
-
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
-
engines: {node: '>=10'}
+
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
-
dev: true
-
/parent-module@1.0.1:
-
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
-
engines: {node: '>=6'}
+
package-manager-detector@1.1.0: {}
+
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
-
dev: true
-
/path-exists@4.0.0:
-
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
-
engines: {node: '>=8'}
-
dev: true
+
path-exists@4.0.0: {}
-
/path-is-absolute@1.0.1:
-
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
-
engines: {node: '>=0.10.0'}
+
path-key@3.1.1: {}
-
/path-key@3.1.1:
-
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
-
engines: {node: '>=8'}
-
dev: true
+
path-parse@1.0.7: {}
-
/path-key@4.0.0:
-
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
-
engines: {node: '>=12'}
-
dev: true
+
pathe@2.0.3: {}
-
/path-parse@1.0.7:
-
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
-
dev: true
+
picomatch@2.3.1: {}
-
/path-type@4.0.0:
-
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
-
engines: {node: '>=8'}
-
dev: true
+
picomatch@4.0.2: {}
-
/picocolors@1.0.0:
-
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
-
dev: true
+
pnpm-workspace-yaml@0.3.1:
+
dependencies:
+
yaml: 2.7.1
-
/picomatch@2.3.1:
-
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
-
engines: {node: '>=8.6'}
-
dev: true
+
possible-typed-array-names@1.1.0: {}
-
/prelude-ls@1.2.1:
-
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
-
engines: {node: '>= 0.8.0'}
-
dev: true
+
prelude-ls@1.2.1: {}
-
/prettier-linter-helpers@1.0.0:
-
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
-
engines: {node: '>=6.0.0'}
+
prettier-linter-helpers@1.0.0:
dependencies:
fast-diff: 1.3.0
-
dev: true
-
/prettier@3.1.0:
-
resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==}
-
engines: {node: '>=14'}
-
hasBin: true
-
dev: true
+
prettier@3.1.0: {}
+
+
process@0.11.10: {}
-
/prop-types@15.8.1:
-
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
-
dev: true
-
/punycode@2.3.1:
-
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
-
engines: {node: '>=6'}
-
dev: true
+
punycode@2.3.1: {}
-
/queue-microtask@1.2.3:
-
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
-
dev: true
+
quansync@0.2.10: {}
-
/react-is@16.13.1:
-
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
-
dev: true
+
queue-microtask@1.2.3: {}
-
/reflect.getprototypeof@1.0.4:
-
resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==}
-
engines: {node: '>= 0.4'}
+
react-is@16.13.1: {}
+
+
readable-stream@4.5.2:
+
dependencies:
+
abort-controller: 3.0.0
+
buffer: 6.0.3
+
events: 3.3.0
+
process: 0.11.10
+
string_decoder: 1.3.0
+
+
reflect.getprototypeof@1.0.10:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
get-intrinsic: 1.2.2
-
globalthis: 1.0.3
-
which-builtin-type: 1.1.3
-
dev: true
+
es-abstract: 1.23.9
+
es-errors: 1.3.0
+
es-object-atoms: 1.1.1
+
get-intrinsic: 1.3.0
+
get-proto: 1.0.1
+
which-builtin-type: 1.2.1
-
/regexp.prototype.flags@1.5.1:
-
resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==}
-
engines: {node: '>= 0.4'}
+
regexp.prototype.flags@1.5.4:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
define-properties: 1.2.1
-
set-function-name: 2.0.1
-
dev: true
+
es-errors: 1.3.0
+
get-proto: 1.0.1
+
gopd: 1.2.0
+
set-function-name: 2.0.2
-
/resolve-from@4.0.0:
-
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
-
engines: {node: '>=4'}
-
dev: true
+
resolve-from@4.0.0: {}
-
/resolve@2.0.0-next.5:
-
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
-
hasBin: true
+
resolve@2.0.0-next.5:
dependencies:
-
is-core-module: 2.13.1
+
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
-
dev: true
-
/reusify@1.0.4:
-
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
-
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
-
dev: true
-
-
/rimraf@3.0.2:
-
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
-
hasBin: true
+
restore-cursor@5.1.0:
dependencies:
-
glob: 7.2.3
-
dev: true
+
onetime: 7.0.0
+
signal-exit: 4.1.0
-
/run-applescript@5.0.0:
-
resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==}
-
engines: {node: '>=12'}
-
dependencies:
-
execa: 5.1.1
-
dev: true
+
reusify@1.0.4: {}
-
/run-parallel@1.2.0:
-
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
-
dev: true
-
/safe-array-concat@1.0.1:
-
resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==}
-
engines: {node: '>=0.4'}
+
safe-array-concat@1.1.3:
dependencies:
-
call-bind: 1.0.5
-
get-intrinsic: 1.2.2
-
has-symbols: 1.0.3
+
call-bind: 1.0.8
+
call-bound: 1.0.4
+
get-intrinsic: 1.3.0
+
has-symbols: 1.1.0
isarray: 2.0.5
-
dev: true
-
/safe-regex-test@1.0.0:
-
resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
+
safe-buffer@5.2.1: {}
+
+
safe-push-apply@1.0.0:
dependencies:
-
call-bind: 1.0.5
-
get-intrinsic: 1.2.2
-
is-regex: 1.1.4
-
dev: true
+
es-errors: 1.3.0
+
isarray: 2.0.5
-
/semver@6.3.1:
-
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
-
hasBin: true
-
dev: true
+
safe-regex-test@1.1.0:
+
dependencies:
+
call-bound: 1.0.4
+
es-errors: 1.3.0
+
is-regex: 1.2.1
+
+
semver@6.3.1: {}
+
+
semver@7.7.1: {}
-
/semver@7.5.4:
-
resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
-
engines: {node: '>=10'}
-
hasBin: true
+
set-function-length@1.2.2:
dependencies:
-
lru-cache: 6.0.0
-
dev: true
+
define-data-property: 1.1.4
+
es-errors: 1.3.0
+
function-bind: 1.1.2
+
get-intrinsic: 1.3.0
+
gopd: 1.2.0
+
has-property-descriptors: 1.0.2
-
/set-function-length@1.1.1:
-
resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==}
-
engines: {node: '>= 0.4'}
+
set-function-name@2.0.2:
dependencies:
-
define-data-property: 1.1.1
-
get-intrinsic: 1.2.2
-
gopd: 1.0.1
-
has-property-descriptors: 1.0.1
-
dev: true
+
define-data-property: 1.1.4
+
es-errors: 1.3.0
+
functions-have-names: 1.2.3
+
has-property-descriptors: 1.0.2
-
/set-function-name@2.0.1:
-
resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==}
-
engines: {node: '>= 0.4'}
+
set-proto@1.0.0:
dependencies:
-
define-data-property: 1.1.1
-
functions-have-names: 1.2.3
-
has-property-descriptors: 1.0.1
-
dev: true
+
dunder-proto: 1.0.1
+
es-errors: 1.3.0
+
es-object-atoms: 1.1.1
-
/shebang-command@2.0.0:
-
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
-
engines: {node: '>=8'}
+
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
-
dev: true
-
/shebang-regex@3.0.0:
-
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
-
engines: {node: '>=8'}
-
dev: true
+
shebang-regex@3.0.0: {}
-
/side-channel@1.0.4:
-
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
+
side-channel-list@1.0.0:
dependencies:
-
call-bind: 1.0.5
-
get-intrinsic: 1.2.2
-
object-inspect: 1.13.1
-
dev: true
+
es-errors: 1.3.0
+
object-inspect: 1.13.4
-
/signal-exit@3.0.7:
-
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
-
dev: true
+
side-channel-map@1.0.1:
+
dependencies:
+
call-bound: 1.0.4
+
es-errors: 1.3.0
+
get-intrinsic: 1.3.0
+
object-inspect: 1.13.4
-
/slash@3.0.0:
-
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
-
engines: {node: '>=8'}
-
dev: true
+
side-channel-weakmap@1.0.2:
+
dependencies:
+
call-bound: 1.0.4
+
es-errors: 1.3.0
+
get-intrinsic: 1.3.0
+
object-inspect: 1.13.4
+
side-channel-map: 1.0.1
-
/standalone-electron-types@1.0.0:
-
resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==}
+
side-channel@1.1.0:
+
dependencies:
+
es-errors: 1.3.0
+
object-inspect: 1.13.4
+
side-channel-list: 1.0.0
+
side-channel-map: 1.0.1
+
side-channel-weakmap: 1.0.2
+
+
signal-exit@4.1.0: {}
+
+
standalone-electron-types@1.0.0:
dependencies:
'@types/node': 18.17.17
-
dev: false
-
/string.prototype.matchall@4.0.10:
-
resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==}
+
string.prototype.matchall@4.0.12:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
+
call-bound: 1.0.4
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
get-intrinsic: 1.2.2
-
has-symbols: 1.0.3
-
internal-slot: 1.0.6
-
regexp.prototype.flags: 1.5.1
-
set-function-name: 2.0.1
-
side-channel: 1.0.4
-
dev: true
+
es-abstract: 1.23.9
+
es-errors: 1.3.0
+
es-object-atoms: 1.1.1
+
get-intrinsic: 1.3.0
+
gopd: 1.2.0
+
has-symbols: 1.1.0
+
internal-slot: 1.1.0
+
regexp.prototype.flags: 1.5.4
+
set-function-name: 2.0.2
+
side-channel: 1.1.0
-
/string.prototype.trim@1.2.8:
-
resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
-
engines: {node: '>= 0.4'}
+
string.prototype.repeat@1.0.0:
dependencies:
-
call-bind: 1.0.5
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
dev: true
+
es-abstract: 1.23.9
-
/string.prototype.trimend@1.0.7:
-
resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==}
+
string.prototype.trim@1.2.10:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
+
call-bound: 1.0.4
+
define-data-property: 1.1.4
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
dev: true
+
es-abstract: 1.23.9
+
es-object-atoms: 1.1.1
+
has-property-descriptors: 1.0.2
-
/string.prototype.trimstart@1.0.7:
-
resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==}
+
string.prototype.trimend@1.0.9:
dependencies:
-
call-bind: 1.0.5
+
call-bind: 1.0.8
+
call-bound: 1.0.4
define-properties: 1.2.1
-
es-abstract: 1.22.3
-
dev: true
+
es-object-atoms: 1.1.1
-
/strip-ansi@6.0.1:
-
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
-
engines: {node: '>=8'}
+
string.prototype.trimstart@1.0.8:
dependencies:
-
ansi-regex: 5.0.1
-
dev: true
+
call-bind: 1.0.8
+
define-properties: 1.2.1
+
es-object-atoms: 1.1.1
-
/strip-final-newline@2.0.0:
-
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
-
engines: {node: '>=6'}
-
dev: true
+
string_decoder@1.3.0:
+
dependencies:
+
safe-buffer: 5.2.1
-
/strip-final-newline@3.0.0:
-
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
-
engines: {node: '>=12'}
-
dev: true
-
-
/strip-json-comments@3.1.1:
-
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
-
engines: {node: '>=8'}
-
dev: true
+
strip-json-comments@3.1.1: {}
-
/supports-color@7.2.0:
-
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
-
engines: {node: '>=8'}
+
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
-
dev: true
-
/supports-preserve-symlinks-flag@1.0.0:
-
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
-
engines: {node: '>= 0.4'}
-
dev: true
+
supports-preserve-symlinks-flag@1.0.0: {}
+
+
synckit@0.11.1:
+
dependencies:
+
'@pkgr/core': 0.2.0
+
tslib: 2.8.1
-
/synckit@0.8.6:
-
resolution: {integrity: sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==}
-
engines: {node: ^14.18.0 || >=16.0.0}
+
taze@19.0.4:
dependencies:
-
'@pkgr/utils': 2.4.2
-
tslib: 2.6.2
-
dev: true
+
'@antfu/ni': 24.3.0
+
cac: 6.7.14
+
find-up-simple: 1.0.1
+
ofetch: 1.4.1
+
package-manager-detector: 1.1.0
+
pathe: 2.0.3
+
pnpm-workspace-yaml: 0.3.1
+
restore-cursor: 5.1.0
+
tinyexec: 1.0.1
+
tinyglobby: 0.2.12
+
unconfig: 7.3.1
+
yaml: 2.7.1
-
/text-table@0.2.0:
-
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
-
dev: true
+
tinyexec@1.0.1: {}
-
/titleize@3.0.0:
-
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
-
engines: {node: '>=12'}
-
dev: true
+
tinyglobby@0.2.12:
+
dependencies:
+
fdir: 6.4.3(picomatch@4.0.2)
+
picomatch: 4.0.2
-
/to-regex-range@5.0.1:
-
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
-
engines: {node: '>=8.0'}
+
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
-
dev: true
-
/ts-api-utils@1.0.3(typescript@5.3.2):
-
resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==}
-
engines: {node: '>=16.13.0'}
-
peerDependencies:
-
typescript: '>=4.2.0'
+
ts-api-utils@2.1.0(typescript@5.8.2):
dependencies:
-
typescript: 5.3.2
-
dev: true
+
typescript: 5.8.2
-
/tslib@2.6.2:
-
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
-
dev: true
+
tslib@2.8.1: {}
-
/type-check@0.4.0:
-
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
-
engines: {node: '>= 0.8.0'}
+
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
-
dev: true
-
/type-fest@0.20.2:
-
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
-
engines: {node: '>=10'}
-
dev: true
+
typed-array-buffer@1.0.3:
+
dependencies:
+
call-bound: 1.0.4
+
es-errors: 1.3.0
+
is-typed-array: 1.1.15
-
/typed-array-buffer@1.0.0:
-
resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==}
-
engines: {node: '>= 0.4'}
+
typed-array-byte-length@1.0.3:
dependencies:
-
call-bind: 1.0.5
-
get-intrinsic: 1.2.2
-
is-typed-array: 1.1.12
-
dev: true
+
call-bind: 1.0.8
+
for-each: 0.3.5
+
gopd: 1.2.0
+
has-proto: 1.2.0
+
is-typed-array: 1.1.15
-
/typed-array-byte-length@1.0.0:
-
resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==}
-
engines: {node: '>= 0.4'}
+
typed-array-byte-offset@1.0.4:
dependencies:
-
call-bind: 1.0.5
-
for-each: 0.3.3
-
has-proto: 1.0.1
-
is-typed-array: 1.1.12
-
dev: true
+
available-typed-arrays: 1.0.7
+
call-bind: 1.0.8
+
for-each: 0.3.5
+
gopd: 1.2.0
+
has-proto: 1.2.0
+
is-typed-array: 1.1.15
+
reflect.getprototypeof: 1.0.10
-
/typed-array-byte-offset@1.0.0:
-
resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==}
-
engines: {node: '>= 0.4'}
+
typed-array-length@1.0.7:
dependencies:
-
available-typed-arrays: 1.0.5
-
call-bind: 1.0.5
-
for-each: 0.3.3
-
has-proto: 1.0.1
-
is-typed-array: 1.1.12
-
dev: true
+
call-bind: 1.0.8
+
for-each: 0.3.5
+
gopd: 1.2.0
+
is-typed-array: 1.1.15
+
possible-typed-array-names: 1.1.0
+
reflect.getprototypeof: 1.0.10
-
/typed-array-length@1.0.4:
-
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
+
typescript-eslint@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2):
dependencies:
-
call-bind: 1.0.5
-
for-each: 0.3.3
-
is-typed-array: 1.1.12
-
dev: true
+
'@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)
+
'@typescript-eslint/parser': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
+
'@typescript-eslint/utils': 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
+
transitivePeerDependencies:
+
- supports-color
+
+
typescript@5.8.2: {}
+
+
ufo@1.5.4: {}
-
/typescript@5.3.2:
-
resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==}
-
engines: {node: '>=14.17'}
-
hasBin: true
-
dev: true
+
unbox-primitive@1.1.0:
+
dependencies:
+
call-bound: 1.0.4
+
has-bigints: 1.1.0
+
has-symbols: 1.1.0
+
which-boxed-primitive: 1.1.1
-
/unbox-primitive@1.0.2:
-
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
+
unconfig@7.3.1:
dependencies:
-
call-bind: 1.0.5
-
has-bigints: 1.0.2
-
has-symbols: 1.0.3
-
which-boxed-primitive: 1.0.2
-
dev: true
+
'@quansync/fs': 0.1.2
+
defu: 6.1.4
+
jiti: 2.4.2
+
quansync: 0.2.10
-
/untildify@4.0.0:
-
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
-
engines: {node: '>=8'}
-
dev: true
+
undici-types@6.20.0: {}
-
/uri-js@4.4.1:
-
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
undici-types@6.21.0: {}
+
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
-
dev: true
+
+
utilium@1.10.1:
+
dependencies:
+
eventemitter3: 5.0.1
+
optionalDependencies:
+
'@xterm/xterm': 5.5.0
-
/which-boxed-primitive@1.0.2:
-
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+
which-boxed-primitive@1.1.1:
dependencies:
-
is-bigint: 1.0.4
-
is-boolean-object: 1.1.2
-
is-number-object: 1.0.7
-
is-string: 1.0.7
-
is-symbol: 1.0.4
-
dev: true
+
is-bigint: 1.1.0
+
is-boolean-object: 1.2.2
+
is-number-object: 1.1.1
+
is-string: 1.1.1
+
is-symbol: 1.1.1
-
/which-builtin-type@1.1.3:
-
resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==}
-
engines: {node: '>= 0.4'}
+
which-builtin-type@1.2.1:
dependencies:
-
function.prototype.name: 1.1.6
-
has-tostringtag: 1.0.0
-
is-async-function: 2.0.0
-
is-date-object: 1.0.5
-
is-finalizationregistry: 1.0.2
-
is-generator-function: 1.0.10
-
is-regex: 1.1.4
-
is-weakref: 1.0.2
+
call-bound: 1.0.4
+
function.prototype.name: 1.1.8
+
has-tostringtag: 1.0.2
+
is-async-function: 2.1.1
+
is-date-object: 1.1.0
+
is-finalizationregistry: 1.1.1
+
is-generator-function: 1.1.0
+
is-regex: 1.2.1
+
is-weakref: 1.1.1
isarray: 2.0.5
-
which-boxed-primitive: 1.0.2
-
which-collection: 1.0.1
-
which-typed-array: 1.1.13
-
dev: true
+
which-boxed-primitive: 1.1.1
+
which-collection: 1.0.2
+
which-typed-array: 1.1.19
-
/which-collection@1.0.1:
-
resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==}
+
which-collection@1.0.2:
dependencies:
-
is-map: 2.0.2
-
is-set: 2.0.2
-
is-weakmap: 2.0.1
-
is-weakset: 2.0.2
-
dev: true
+
is-map: 2.0.3
+
is-set: 2.0.3
+
is-weakmap: 2.0.2
+
is-weakset: 2.0.4
-
/which-typed-array@1.1.13:
-
resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==}
-
engines: {node: '>= 0.4'}
+
which-typed-array@1.1.19:
dependencies:
-
available-typed-arrays: 1.0.5
-
call-bind: 1.0.5
-
for-each: 0.3.3
-
gopd: 1.0.1
-
has-tostringtag: 1.0.0
-
dev: true
+
available-typed-arrays: 1.0.7
+
call-bind: 1.0.8
+
call-bound: 1.0.4
+
for-each: 0.3.5
+
get-proto: 1.0.1
+
gopd: 1.2.0
+
has-tostringtag: 1.0.2
-
/which@2.0.2:
-
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
-
engines: {node: '>= 8'}
-
hasBin: true
+
which@2.0.2:
dependencies:
isexe: 2.0.0
-
dev: true
-
/wrappy@1.0.2:
-
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
yaml@2.7.1: {}
-
/yallist@4.0.0:
-
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
-
dev: true
+
yocto-queue@0.1.0: {}
-
/yocto-queue@0.1.0:
-
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
-
engines: {node: '>=10'}
-
dev: true
+
zustand@5.0.3(@types/react@18.3.20):
+
optionalDependencies:
+
'@types/react': 18.3.20
+31 -1
pnpm-workspace.yaml
···
packages:
-
- "packages/*"
+
- packages/*
+
+
catalogs:
+
dev:
+
esbuild: ^0.19.3
+
esbuild-copy-static-files: ^0.1.0
+
"@types/node": ^22.14.0
+
"@moonlight-mod/eslint-config": "github:moonlight-mod/eslint-config"
+
eslint: ^9.12.0
+
"@types/chrome": ^0.0.313
+
husky: ^8.0.3
+
prettier: ^3.1.0
+
typescript: ^5.3.3
+
taze: ^19.0.4
+
prod:
+
"@moonlight-mod/lunast": ^1.0.1
+
"@moonlight-mod/mappings": ^1.1.25
+
"@moonlight-mod/moonmap": ^1.0.5
+
microdiff: ^1.5.0
+
nanotar: ^0.1.1
+
"@zenfs/core": ^2.0.0
+
"@zenfs/dom": ^1.1.3
+
+
onlyBuiltDependencies:
+
- esbuild
+
+
engineStrict: true
+
strictSsl: true
+
strictDepBuilds: true
+
packageManagerStrict: true
+
registry: https://registry.npmjs.org/
+78
scripts/link.mjs
···
+
// Janky script to get around pnpm link issues
+
// Probably don't use this. Probably
+
/* eslint-disable no-console */
+
const fs = require("fs");
+
const path = require("path");
+
const child_process = require("child_process");
+
+
const cwd = process.cwd();
+
const onDisk = {
+
//"@moonlight-mod/lunast": "../lunast",
+
//"@moonlight-mod/moonmap": "../moonmap",
+
"@moonlight-mod/mappings": "../mappings"
+
};
+
+
function exec(cmd, dir) {
+
child_process.execSync(cmd, { cwd: dir, stdio: "inherit" });
+
}
+
+
function getDeps(packageJSON) {
+
const ret = {};
+
Object.assign(ret, packageJSON.dependencies || {});
+
Object.assign(ret, packageJSON.devDependencies || {});
+
Object.assign(ret, packageJSON.peerDependencies || {});
+
return ret;
+
}
+
+
function link(dir) {
+
const packageJSONPath = path.join(dir, "package.json");
+
if (!fs.existsSync(packageJSONPath)) return;
+
const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, "utf8"));
+
const deps = getDeps(packageJSON);
+
+
for (const [dep, relativePath] of Object.entries(onDisk)) {
+
const fullPath = path.join(cwd, relativePath);
+
if (deps[dep]) {
+
exec(`pnpm link ${fullPath}`, dir);
+
}
+
}
+
}
+
+
function undo(dir) {
+
exec("pnpm unlink", dir);
+
try {
+
if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) {
+
exec("git restore pnpm-lock.yaml", dir);
+
}
+
} catch {
+
// ignored
+
}
+
}
+
+
const shouldUndo = process.argv.includes("--undo");
+
const packages = fs.readdirSync("./packages");
+
+
for (const path of Object.values(onDisk)) {
+
console.log(path);
+
if (shouldUndo) {
+
undo(path);
+
} else {
+
link(path);
+
}
+
}
+
+
if (shouldUndo) {
+
console.log(cwd);
+
undo(cwd);
+
for (const pkg of packages) {
+
const dir = path.join(cwd, "packages", pkg);
+
console.log(dir);
+
undo(dir);
+
}
+
} else {
+
for (const pkg of packages) {
+
const dir = path.join(cwd, "packages", pkg);
+
console.log(dir);
+
link(dir);
+
}
+
}
+35
tsconfig.base.json
···
+
{
+
"$schema": "https://json.schemastore.org/tsconfig.json",
+
"display": "Base",
+
"_version": "1.0.0",
+
"compilerOptions": {
+
"incremental": true,
+
"target": "ES2022",
+
"jsx": "react",
+
"lib": ["ESNext", "ESNext.Disposable", "DOM", "DOM.Iterable"],
+
"module": "ES2020",
+
"moduleResolution": "Bundler",
+
"resolveJsonModule": true,
+
"allowArbitraryExtensions": false,
+
"allowImportingTsExtensions": true,
+
"allowJs": true,
+
"strict": true,
+
"strictNullChecks": true,
+
+
// disable unreachable code detection because it breaks with esbuild labels
+
"allowUnreachableCode": true,
+
"noFallthroughCasesInSwitch": true,
+
"noImplicitReturns": true,
+
"declaration": true,
+
"declarationMap": true,
+
"outDir": "dist",
+
"sourceMap": true,
+
"stripInternal": true,
+
"esModuleInterop": true,
+
"forceConsistentCasingInFileNames": true,
+
"noErrorTruncation": true,
+
"verbatimModuleSyntax": false,
+
// meriyah has a broken import lol
+
"skipLibCheck": true
+
}
+
}
+7 -14
tsconfig.json
···
{
+
"extends": ["./tsconfig.base.json"],
"compilerOptions": {
-
"target": "es2016",
-
"module": "es6",
-
"esModuleInterop": true,
-
"forceConsistentCasingInFileNames": true,
-
"strict": true,
-
"skipLibCheck": true,
-
"moduleResolution": "bundler",
"baseUrl": "./packages/",
-
"jsx": "react",
-
"noEmit": true,
-
-
// disable unreachable code detection because it breaks with esbuild labels
-
"allowUnreachableCode": true
+
"noEmit": true
},
-
"include": ["./packages/**/*", "./env.d.ts"],
-
"exclude": ["node_modules"]
+
"exclude": [
+
"**/node_modules/**",
+
"**/dist/**",
+
"**/build/**"
+
]
}