-75
.eslintrc.json
-75
.eslintrc.json
···
1
-
{
2
-
"root": true,
3
-
"extends": [
4
-
"eslint:recommended",
5
-
"plugin:@typescript-eslint/recommended",
6
-
"plugin:prettier/recommended",
7
-
"plugin:react/recommended"
8
-
],
9
-
"plugins": ["@typescript-eslint", "prettier", "react"],
10
-
"parser": "@typescript-eslint/parser",
11
-
"env": {
12
-
"browser": true,
13
-
"node": true
14
-
},
15
-
"parserOptions": {
16
-
"ecmaFeatures": {
17
-
"jsx": true
18
-
},
19
-
"ecmaVersion": "latest",
20
-
"sourceType": "module"
21
-
},
22
-
"rules": {
23
-
"indent": "off",
24
-
"eqeqeq": [
25
-
"error",
26
-
"always",
27
-
{
28
-
"null": "ignore"
29
-
}
30
-
],
31
-
"quotes": [
32
-
"error",
33
-
"double",
34
-
{ "avoidEscape": true, "allowTemplateLiterals": true }
35
-
],
36
-
"@typescript-eslint/no-unused-vars": [
37
-
"error",
38
-
{ "args": "none", "varsIgnorePattern": "^_" }
39
-
],
40
-
// Mostly so we don't forget to leave these in when committing
41
-
"no-console": "error",
42
-
"no-debugger": "error",
43
-
44
-
// Quite honestly we're interacting with so much unknown within Discord that
45
-
// this being enabled is a hinderance
46
-
"@typescript-eslint/no-explicit-any": "off",
47
-
48
-
"@typescript-eslint/no-var-requires": "off",
49
-
50
-
// https://canary.discord.com/channels/1154257010532032512/1154275441788583996/1181760413231230976
51
-
"no-unused-labels": "off",
52
-
53
-
// baseUrl being set to ./packages/ makes language server suggest "types/src" instead of "@moonlight-mod/types"
54
-
"no-restricted-imports": [
55
-
"error",
56
-
{
57
-
"patterns": [
58
-
{
59
-
"group": ["types/*"],
60
-
"message": "Use @moonlight-mod/types instead"
61
-
},
62
-
{
63
-
"group": ["core/*"],
64
-
"message": "Use @moonlight-mod/core instead"
65
-
}
66
-
]
67
-
}
68
-
]
69
-
},
70
-
"settings": {
71
-
"react": {
72
-
"version": "18.2"
73
-
}
74
-
}
75
-
}
···
+4
-8
.github/workflows/browser.yml
+4
-8
.github/workflows/browser.yml
···
10
name: Browser extension builds
11
runs-on: ubuntu-latest
12
steps:
13
-
- uses: actions/checkout@v3
14
-
15
-
- uses: pnpm/action-setup@v2
16
-
with:
17
-
version: 9
18
-
run_install: false
19
-
- uses: actions/setup-node@v3
20
with:
21
-
node-version: 18
22
cache: pnpm
23
24
- name: Install dependencies
+4
-8
.github/workflows/lint.yml
+4
-8
.github/workflows/lint.yml
+7
-11
.github/workflows/nightly.yml
+7
-11
.github/workflows/nightly.yml
···
15
name: Nightly builds on GitHub Pages
16
runs-on: ubuntu-latest
17
steps:
18
-
- uses: actions/checkout@v3
19
-
20
-
- uses: pnpm/action-setup@v2
21
-
with:
22
-
version: 9
23
-
run_install: false
24
-
- uses: actions/setup-node@v3
25
with:
26
-
node-version: 18
27
cache: pnpm
28
29
- name: Install dependencies
···
47
echo "$(date +%s)" >> ./dist/ref
48
49
- name: Setup GitHub Pages
50
-
uses: actions/configure-pages@v3
51
- name: Upload artifact
52
-
uses: actions/upload-pages-artifact@v1
53
with:
54
path: ./dist
55
- name: Deploy to GitHub Pages
56
-
uses: actions/deploy-pages@v2
···
15
name: Nightly builds on GitHub Pages
16
runs-on: ubuntu-latest
17
steps:
18
+
- uses: actions/checkout@v4
19
+
- uses: pnpm/action-setup@v4
20
+
- uses: actions/setup-node@v4
21
with:
22
+
node-version: 22
23
cache: pnpm
24
25
- name: Install dependencies
···
43
echo "$(date +%s)" >> ./dist/ref
44
45
- name: Setup GitHub Pages
46
+
uses: actions/configure-pages@v5
47
- name: Upload artifact
48
+
uses: actions/upload-pages-artifact@v3
49
with:
50
path: ./dist
51
- name: Deploy to GitHub Pages
52
+
uses: actions/deploy-pages@v4
+16
.github/workflows/nix.yml
+16
.github/workflows/nix.yml
···
···
1
+
name: Check Nix flake
2
+
on: [push, pull_request]
3
+
4
+
permissions:
5
+
checks: write
6
+
7
+
jobs:
8
+
nix:
9
+
name: Check Nix flake
10
+
runs-on: ubuntu-latest
11
+
steps:
12
+
- uses: actions/checkout@v4
13
+
- uses: DeterminateSystems/nix-installer-action@main
14
+
15
+
- name: Build default flake output
16
+
run: nix build
+4
-8
.github/workflows/release.yml
+4
-8
.github/workflows/release.yml
···
13
name: Release builds to GitHub Releases
14
runs-on: ubuntu-latest
15
steps:
16
-
- uses: actions/checkout@v3
17
-
18
-
- uses: pnpm/action-setup@v2
19
-
with:
20
-
version: 9
21
-
run_install: false
22
-
- uses: actions/setup-node@v3
23
with:
24
-
node-version: 18
25
cache: pnpm
26
27
- name: Install dependencies
+32
.github/workflows/types.yml
+32
.github/workflows/types.yml
···
···
1
+
name: Publish types on npm
2
+
on: workflow_dispatch
3
+
4
+
permissions:
5
+
contents: read
6
+
pages: write
7
+
id-token: write
8
+
9
+
jobs:
10
+
types:
11
+
name: Publish types on npm
12
+
runs-on: ubuntu-latest
13
+
steps:
14
+
- uses: actions/checkout@v4
15
+
- uses: pnpm/action-setup@v4
16
+
- uses: actions/setup-node@v4
17
+
with:
18
+
node-version: 22
19
+
cache: pnpm
20
+
registry-url: https://registry.npmjs.org
21
+
22
+
- name: Install dependencies
23
+
run: pnpm install --frozen-lockfile
24
+
- name: Build moonlight
25
+
env:
26
+
NODE_ENV: production
27
+
run: pnpm run build
28
+
29
+
- name: Publish types
30
+
run: pnpm publish --filter=./packages/types --access public --no-git-checks
31
+
env:
32
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+5
-1
.gitignore
+5
-1
.gitignore
+4
-4
.prettierrc
+4
-4
.prettierrc
-14
.vscode/tasks.json
-14
.vscode/tasks.json
+4
-1
CHANGELOG.md
+4
-1
CHANGELOG.md
+10
-3
README.md
+10
-3
README.md
···
5
<img src="./img/wordmark.png" alt="moonlight" />
6
</picture>
7
8
-
<a href="https://discord.gg/FdZBTFCP6F">Discord server</a>
9
\- <a href="https://github.com/moonlight-mod/moonlight">GitHub</a>
10
-
\- <a href="https://moonlight-mod.github.io/">Docs</a>
11
12
<hr />
13
</h3>
14
15
**moonlight** is yet another Discord client mod, focused on providing a decent user and developer experience.
16
17
moonlight is heavily inspired by hh3 (a private client mod) and the projects before it that it is inspired by, namely EndPwn. All core code is original or used with permission from their respective authors where not copyleft.
18
19
-
**_This is an experimental passion project._** Anything and everything is subject to change, but it is stable enough for developers to experiment with.
20
21
moonlight is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.html) (`LGPL-3.0-or-later`). See [the documentation](https://moonlight-mod.github.io/) for more information.
···
5
<img src="./img/wordmark.png" alt="moonlight" />
6
</picture>
7
8
+
<a href="https://moonlight-mod.github.io/using/install">Install</a>
9
+
\- <a href="https://moonlight-mod.github.io/ext-dev/getting-started">Docs</a>
10
+
\- <a href="https://discord.gg/FdZBTFCP6F">Discord server</a>
11
\- <a href="https://github.com/moonlight-mod/moonlight">GitHub</a>
12
13
<hr />
14
+
15
+
<picture>
16
+
<source media="(prefers-color-scheme: dark)" srcset="https://moonlight-mod.github.io/moonbase.png">
17
+
<source media="(prefers-color-scheme: light)" srcset="https://moonlight-mod.github.io/moonbase-light.png">
18
+
<img src="https://moonlight-mod.github.io/moonbase.png" alt="A screenshot of Moonbase, the moonlight UI" />
19
+
</picture>
20
</h3>
21
22
**moonlight** is yet another Discord client mod, focused on providing a decent user and developer experience.
23
24
moonlight is heavily inspired by hh3 (a private client mod) and the projects before it that it is inspired by, namely EndPwn. All core code is original or used with permission from their respective authors where not copyleft.
25
26
+
moonlight is a **_passion project_** - things may break from time to time, but we try our best to keep things working in a timely manner.
27
28
moonlight is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.html) (`LGPL-3.0-or-later`). See [the documentation](https://moonlight-mod.github.io/) for more information.
+34
-44
build.mjs
+34
-44
build.mjs
···
15
const watch = process.argv.includes("--watch");
16
const browser = process.argv.includes("--browser");
17
const mv2 = process.argv.includes("--mv2");
18
19
const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev";
20
const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev";
···
69
name: "build-log",
70
setup(build) {
71
build.onEnd((result) => {
72
-
console.log(
73
-
`[${timeFormatter.format(new Date())}] [${tag}] build finished`
74
-
);
75
});
76
}
77
});
···
104
MOONLIGHT_VERSION: `"${buildVersion}"`
105
};
106
107
-
for (const iterName of [
108
-
"injector",
109
-
"node-preload",
110
-
"web-preload",
111
-
"browser"
112
-
]) {
113
const snake = iterName.replace(/-/g, "_").toUpperCase();
114
define[`MOONLIGHT_${snake}`] = (name === iterName).toString();
115
}
···
121
if (name === "browser") {
122
plugins.push(
123
copyStaticFiles({
124
-
src: mv2
125
-
? "./packages/browser/manifestv2.json"
126
-
: "./packages/browser/manifest.json",
127
dest: `./dist/${browserDir}/manifest.json`
128
})
129
);
···
145
146
plugins.push(
147
copyStaticFiles({
148
-
src: mv2
149
-
? "./packages/browser/src/background-mv2.js"
150
-
: "./packages/browser/src/background.js",
151
dest: `./dist/${browserDir}/background.js`
152
})
153
);
···
174
dropLabels,
175
176
logLevel: "silent",
177
-
plugins
178
};
179
180
if (name === "browser") {
181
const coreExtensionsJson = {};
182
183
-
// eslint-disable-next-line no-inner-declarations
184
function readDir(dir) {
185
const files = fs.readdirSync(dir);
186
for (const file of files) {
···
189
if (fs.statSync(filePath).isDirectory()) {
190
readDir(filePath);
191
} else {
192
-
coreExtensionsJson[normalizedPath] = fs.readFileSync(
193
-
filePath,
194
-
"utf8"
195
-
);
196
}
197
}
198
}
···
200
readDir("./dist/core-extensions");
201
202
esbuildConfig.banner = {
203
-
js: `window._moonlight_coreExtensionsStr = ${JSON.stringify(
204
-
JSON.stringify(coreExtensionsJson)
205
-
)};`
206
};
207
}
208
···
214
}
215
}
216
217
-
async function buildExt(ext, side, copyManifest, fileExt) {
218
const outdir = path.join("./dist", "core-extensions", ext);
219
if (!fs.existsSync(outdir)) {
220
fs.mkdirSync(outdir, { recursive: true });
221
}
222
223
-
const entryPoints = [
224
-
`packages/core-extensions/src/${ext}/${side}.${fileExt}`
225
-
];
226
227
const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`;
228
if (fs.existsSync(wpModulesDir) && side === "index") {
229
const wpModules = fs.opendirSync(wpModulesDir);
230
for await (const wpModule of wpModules) {
231
if (wpModule.isFile()) {
232
-
entryPoints.push(
233
-
`packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`
234
-
);
235
} else {
236
for (const fileExt of ["ts", "tsx"]) {
237
const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`;
···
259
}
260
};
261
262
const esbuildConfig = {
263
entryPoints,
264
outdir,
···
278
},
279
logLevel: "silent",
280
plugins: [
281
-
...(copyManifest
282
? [
283
copyStaticFiles({
284
-
src: `./packages/core-extensions/src/${ext}/manifest.json`,
285
-
dest: `./dist/core-extensions/${ext}/manifest.json`
286
})
287
]
288
: []),
···
302
303
const promises = [];
304
305
-
if (browser) {
306
build("browser", "packages/browser/src/index.ts");
307
} else {
308
for (const [name, entry] of Object.entries(config)) {
···
311
312
const coreExtensions = fs.readdirSync("./packages/core-extensions/src");
313
for (const ext of coreExtensions) {
314
-
let copiedManifest = false;
315
-
316
for (const fileExt of ["ts", "tsx"]) {
317
for (const type of ["index", "node", "host"]) {
318
-
if (
319
-
fs.existsSync(
320
-
`./packages/core-extensions/src/${ext}/${type}.${fileExt}`
321
-
)
322
-
) {
323
-
promises.push(buildExt(ext, type, !copiedManifest, fileExt));
324
-
copiedManifest = true;
325
}
326
}
327
}
···
15
const watch = process.argv.includes("--watch");
16
const browser = process.argv.includes("--browser");
17
const mv2 = process.argv.includes("--mv2");
18
+
const clean = process.argv.includes("--clean");
19
20
const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev";
21
const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev";
···
70
name: "build-log",
71
setup(build) {
72
build.onEnd((result) => {
73
+
console.log(`[${timeFormatter.format(new Date())}] [${tag}] build finished`);
74
});
75
}
76
});
···
103
MOONLIGHT_VERSION: `"${buildVersion}"`
104
};
105
106
+
for (const iterName of ["injector", "node-preload", "web-preload", "browser"]) {
107
const snake = iterName.replace(/-/g, "_").toUpperCase();
108
define[`MOONLIGHT_${snake}`] = (name === iterName).toString();
109
}
···
115
if (name === "browser") {
116
plugins.push(
117
copyStaticFiles({
118
+
src: mv2 ? "./packages/browser/manifestv2.json" : "./packages/browser/manifest.json",
119
dest: `./dist/${browserDir}/manifest.json`
120
})
121
);
···
137
138
plugins.push(
139
copyStaticFiles({
140
+
src: mv2 ? "./packages/browser/src/background-mv2.js" : "./packages/browser/src/background.js",
141
dest: `./dist/${browserDir}/background.js`
142
})
143
);
···
164
dropLabels,
165
166
logLevel: "silent",
167
+
plugins,
168
+
169
+
// https://github.com/evanw/esbuild/issues/3944
170
+
footer:
171
+
name === "web-preload"
172
+
? {
173
+
js: `\n//# sourceURL=${name}.js`
174
+
}
175
+
: undefined
176
};
177
178
if (name === "browser") {
179
const coreExtensionsJson = {};
180
181
function readDir(dir) {
182
const files = fs.readdirSync(dir);
183
for (const file of files) {
···
186
if (fs.statSync(filePath).isDirectory()) {
187
readDir(filePath);
188
} else {
189
+
coreExtensionsJson[normalizedPath] = fs.readFileSync(filePath, "utf8");
190
}
191
}
192
}
···
194
readDir("./dist/core-extensions");
195
196
esbuildConfig.banner = {
197
+
js: `window._moonlight_coreExtensionsStr = ${JSON.stringify(JSON.stringify(coreExtensionsJson))};`
198
};
199
}
200
···
206
}
207
}
208
209
+
async function buildExt(ext, side, fileExt) {
210
const outdir = path.join("./dist", "core-extensions", ext);
211
if (!fs.existsSync(outdir)) {
212
fs.mkdirSync(outdir, { recursive: true });
213
}
214
215
+
const entryPoints = [`packages/core-extensions/src/${ext}/${side}.${fileExt}`];
216
217
const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`;
218
if (fs.existsSync(wpModulesDir) && side === "index") {
219
const wpModules = fs.opendirSync(wpModulesDir);
220
for await (const wpModule of wpModules) {
221
if (wpModule.isFile()) {
222
+
entryPoints.push(`packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`);
223
} else {
224
for (const fileExt of ["ts", "tsx"]) {
225
const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`;
···
247
}
248
};
249
250
+
const styleInput = `packages/core-extensions/src/${ext}/style.css`;
251
+
const styleOutput = `dist/core-extensions/${ext}/style.css`;
252
+
253
const esbuildConfig = {
254
entryPoints,
255
outdir,
···
269
},
270
logLevel: "silent",
271
plugins: [
272
+
copyStaticFiles({
273
+
src: `./packages/core-extensions/src/${ext}/manifest.json`,
274
+
dest: `./dist/core-extensions/${ext}/manifest.json`
275
+
}),
276
+
...(fs.existsSync(styleInput)
277
? [
278
copyStaticFiles({
279
+
src: styleInput,
280
+
dest: styleOutput
281
})
282
]
283
: []),
···
297
298
const promises = [];
299
300
+
if (clean) {
301
+
fs.rmSync("./dist", { recursive: true, force: true });
302
+
} else if (browser) {
303
build("browser", "packages/browser/src/index.ts");
304
} else {
305
for (const [name, entry] of Object.entries(config)) {
···
308
309
const coreExtensions = fs.readdirSync("./packages/core-extensions/src");
310
for (const ext of coreExtensions) {
311
for (const fileExt of ["ts", "tsx"]) {
312
for (const type of ["index", "node", "host"]) {
313
+
if (fs.existsSync(`./packages/core-extensions/src/${ext}/${type}.${fileExt}`)) {
314
+
promises.push(buildExt(ext, type, fileExt));
315
}
316
}
317
}
+25
eslint.config.mjs
+25
eslint.config.mjs
···
···
1
+
import config from "@moonlight-mod/eslint-config";
2
+
3
+
export default [
4
+
...config,
5
+
{
6
+
rules: {
7
+
// baseUrl being set to ./packages/ makes language server suggest "types/src" instead of "@moonlight-mod/types"
8
+
"no-restricted-imports": [
9
+
"error",
10
+
{
11
+
patterns: [
12
+
{
13
+
group: ["types/*"],
14
+
message: "Use @moonlight-mod/types instead"
15
+
},
16
+
{
17
+
group: ["core/*"],
18
+
message: "Use @moonlight-mod/core instead"
19
+
}
20
+
]
21
+
}
22
+
]
23
+
}
24
+
}
25
+
];
+4
-73
flake.lock
+4
-73
flake.lock
···
18
"type": "github"
19
}
20
},
21
-
"flake-utils_2": {
22
-
"inputs": {
23
-
"systems": "systems_2"
24
-
},
25
-
"locked": {
26
-
"lastModified": 1701680307,
27
-
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
28
-
"owner": "numtide",
29
-
"repo": "flake-utils",
30
-
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
31
-
"type": "github"
32
-
},
33
-
"original": {
34
-
"owner": "numtide",
35
-
"repo": "flake-utils",
36
-
"type": "github"
37
-
}
38
-
},
39
"nixpkgs": {
40
"locked": {
41
-
"lastModified": 1728067476,
42
-
"narHash": "sha256-/uJcVXuBt+VFCPQIX+4YnYrHaubJSx4HoNsJVNRgANM=",
43
"owner": "NixOS",
44
"repo": "nixpkgs",
45
-
"rev": "6e6b3dd395c3b1eb9be9f2d096383a8d05add030",
46
"type": "github"
47
},
48
"original": {
49
"owner": "NixOS",
50
-
"ref": "nixos-24.05",
51
-
"repo": "nixpkgs",
52
-
"type": "github"
53
-
}
54
-
},
55
-
"nixpkgs_2": {
56
-
"locked": {
57
-
"lastModified": 1727802920,
58
-
"narHash": "sha256-HP89HZOT0ReIbI7IJZJQoJgxvB2Tn28V6XS3MNKnfLs=",
59
-
"owner": "nixos",
60
-
"repo": "nixpkgs",
61
-
"rev": "27e30d177e57d912d614c88c622dcfdb2e6e6515",
62
-
"type": "github"
63
-
},
64
-
"original": {
65
-
"owner": "nixos",
66
"ref": "nixos-unstable",
67
"repo": "nixpkgs",
68
"type": "github"
69
}
70
},
71
-
"pnpm2nix": {
72
-
"inputs": {
73
-
"flake-utils": "flake-utils_2",
74
-
"nixpkgs": "nixpkgs_2"
75
-
},
76
-
"locked": {
77
-
"lastModified": 1728137762,
78
-
"narHash": "sha256-iEFvPR3BopGyI5KjQ1DK+gEZ1dKDugq838tKdet2moQ=",
79
-
"owner": "NotNite",
80
-
"repo": "pnpm2nix-nzbr",
81
-
"rev": "b7a60d3c7d106b601665e3f05dba6cdc6f59f959",
82
-
"type": "github"
83
-
},
84
-
"original": {
85
-
"owner": "NotNite",
86
-
"repo": "pnpm2nix-nzbr",
87
-
"type": "github"
88
-
}
89
-
},
90
"root": {
91
"inputs": {
92
"flake-utils": "flake-utils",
93
-
"nixpkgs": "nixpkgs",
94
-
"pnpm2nix": "pnpm2nix"
95
}
96
},
97
"systems": {
98
-
"locked": {
99
-
"lastModified": 1681028828,
100
-
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
101
-
"owner": "nix-systems",
102
-
"repo": "default",
103
-
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
104
-
"type": "github"
105
-
},
106
-
"original": {
107
-
"owner": "nix-systems",
108
-
"repo": "default",
109
-
"type": "github"
110
-
}
111
-
},
112
-
"systems_2": {
113
"locked": {
114
"lastModified": 1681028828,
115
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
···
18
"type": "github"
19
}
20
},
21
"nixpkgs": {
22
"locked": {
23
+
"lastModified": 1744232761,
24
+
"narHash": "sha256-gbl9hE39nQRpZaLjhWKmEu5ejtQsgI5TWYrIVVJn30U=",
25
"owner": "NixOS",
26
"repo": "nixpkgs",
27
+
"rev": "f675531bc7e6657c10a18b565cfebd8aa9e24c14",
28
"type": "github"
29
},
30
"original": {
31
"owner": "NixOS",
32
"ref": "nixos-unstable",
33
"repo": "nixpkgs",
34
"type": "github"
35
}
36
},
37
"root": {
38
"inputs": {
39
"flake-utils": "flake-utils",
40
+
"nixpkgs": "nixpkgs"
41
}
42
},
43
"systems": {
44
"locked": {
45
"lastModified": 1681028828,
46
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+3
-4
flake.nix
+3
-4
flake.nix
···
2
description = "Yet another Discord mod";
3
4
inputs = {
5
-
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
6
flake-utils.url = "github:numtide/flake-utils";
7
-
pnpm2nix.url = "github:NotNite/pnpm2nix-nzbr";
8
};
9
10
-
outputs = { self, nixpkgs, flake-utils, pnpm2nix }:
11
-
let overlay = import ./nix/overlay.nix { inherit pnpm2nix; };
12
in flake-utils.lib.eachDefaultSystem (system:
13
let
14
pkgs = import nixpkgs {
···
2
description = "Yet another Discord mod";
3
4
inputs = {
5
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6
flake-utils.url = "github:numtide/flake-utils";
7
};
8
9
+
outputs = { self, nixpkgs, flake-utils }:
10
+
let overlay = import ./nix/overlay.nix { };
11
in flake-utils.lib.eachDefaultSystem (system:
12
let
13
pkgs = import nixpkgs {
+40
-17
nix/default.nix
+40
-17
nix/default.nix
···
1
-
{ pkgs, mkPnpmPackage }:
2
3
-
mkPnpmPackage rec {
4
-
workspace = ./..;
5
src = ./..;
6
7
-
# Work around a bug with how it expects dist
8
-
components = [
9
-
"packages/core"
10
-
"packages/core-extensions"
11
-
"packages/injector"
12
-
"packages/node-preload"
13
-
"packages/types"
14
-
"packages/web-preload"
15
];
16
-
distDirs = [ "dist" ];
17
18
-
copyNodeModules = true;
19
-
buildPhase = "pnpm run build";
20
-
installPhase = "cp -r dist $out";
21
22
-
meta = with pkgs.lib; {
23
description = "Yet another Discord mod";
24
homepage = "https://moonlight-mod.github.io/";
25
license = licenses.lgpl3;
26
maintainers = with maintainers; [ notnite ];
27
};
28
-
}
···
1
+
{
2
+
lib,
3
+
stdenv,
4
+
nodejs_22,
5
+
pnpm_10,
6
+
}:
7
+
8
+
stdenv.mkDerivation (finalAttrs: {
9
+
pname = "moonlight";
10
+
version = (builtins.fromJSON (builtins.readFile ./../package.json)).version;
11
12
src = ./..;
13
14
+
nativeBuildInputs = [
15
+
nodejs_22
16
+
pnpm_10.configHook
17
];
18
+
19
+
pnpmDeps = pnpm_10.fetchDeps {
20
+
inherit (finalAttrs) pname version src;
21
+
hash = "sha256-I+zRCUqJabpGJRFBGW0NrM9xzyzeCjioF54zlCpynBU=";
22
+
};
23
24
+
env = {
25
+
NODE_ENV = "production";
26
+
MOONLIGHT_VERSION = "v${finalAttrs.version}";
27
+
};
28
+
29
+
buildPhase = ''
30
+
runHook preBuild
31
32
+
pnpm run build
33
+
34
+
runHook postBuild
35
+
'';
36
+
37
+
installPhase = ''
38
+
runHook preInstall
39
+
40
+
cp -r dist $out
41
+
42
+
runHook postInstall
43
+
'';
44
+
45
+
meta = with lib; {
46
description = "Yet another Discord mod";
47
homepage = "https://moonlight-mod.github.io/";
48
license = licenses.lgpl3;
49
maintainers = with maintainers; [ notnite ];
50
};
51
+
})
+3
-6
nix/overlay.nix
+3
-6
nix/overlay.nix
···
1
-
{ pnpm2nix }:
2
3
let
4
nameTable = {
···
29
'';
30
31
packageJson = ''
32
-
{"name":"discord","main":"./injector.js","private":true}
33
'';
34
35
in old.installPhase + "\n" + ''
···
49
'';
50
});
51
in final: prev: rec {
52
-
moonlight-mod = final.callPackage ./default.nix {
53
-
pkgs = final;
54
-
mkPnpmPackage = pnpm2nix.packages.${final.system}.mkPnpmPackage;
55
-
};
56
discord = mkOverride prev moonlight-mod "discord";
57
discord-ptb = mkOverride prev moonlight-mod "discord-ptb";
58
discord-canary = mkOverride prev moonlight-mod "discord-canary";
···
1
+
{ ... }:
2
3
let
4
nameTable = {
···
29
'';
30
31
packageJson = ''
32
+
{"name":"${name}","main":"./injector.js","private":true}
33
'';
34
35
in old.installPhase + "\n" + ''
···
49
'';
50
});
51
in final: prev: rec {
52
+
moonlight-mod = final.callPackage ./default.nix { };
53
discord = mkOverride prev moonlight-mod "discord";
54
discord-ptb = mkOverride prev moonlight-mod "discord-ptb";
55
discord-canary = mkOverride prev moonlight-mod "discord-canary";
+24
-16
package.json
+24
-16
package.json
···
1
{
2
"name": "moonlight",
3
-
"version": "1.1.0",
4
"description": "Yet another Discord mod",
5
-
"homepage": "https://moonlight-mod.github.io/",
6
"license": "LGPL-3.0-or-later",
7
"repository": {
8
"type": "git",
9
"url": "git+https://github.com/moonlight-mod/moonlight.git"
···
11
"bugs": {
12
"url": "https://github.com/moonlight-mod/moonlight/issues"
13
},
14
"scripts": {
15
"build": "node build.mjs",
16
"dev": "node build.mjs --watch",
17
"browser": "node build.mjs --browser",
18
"browser-mv2": "node build.mjs --browser --mv2",
19
"lint": "eslint packages",
20
-
"lint:fix": "eslint packages",
21
-
"lint:report": "eslint --output-file eslint_report.json --format json packages",
22
"typecheck": "tsc --noEmit",
23
"check": "pnpm run lint && pnpm run typecheck",
24
-
"prepare": "husky install"
25
},
26
"devDependencies": {
27
-
"@typescript-eslint/eslint-plugin": "^6.13.2",
28
-
"@typescript-eslint/parser": "^6.13.2",
29
-
"esbuild": "^0.19.3",
30
-
"esbuild-copy-static-files": "^0.1.0",
31
-
"eslint": "^8.55.0",
32
-
"eslint-config-prettier": "^9.1.0",
33
-
"eslint-plugin-prettier": "^5.0.1",
34
-
"eslint-plugin-react": "^7.33.2",
35
-
"husky": "^8.0.3",
36
-
"prettier": "^3.1.0",
37
-
"typescript": "^5.3.2"
38
}
39
}
···
1
{
2
"name": "moonlight",
3
+
"version": "1.3.14",
4
+
"packageManager": "pnpm@10.7.1",
5
"description": "Yet another Discord mod",
6
"license": "LGPL-3.0-or-later",
7
+
"homepage": "https://moonlight-mod.github.io/",
8
"repository": {
9
"type": "git",
10
"url": "git+https://github.com/moonlight-mod/moonlight.git"
···
12
"bugs": {
13
"url": "https://github.com/moonlight-mod/moonlight/issues"
14
},
15
+
"engineStrict": true,
16
+
"engines": {
17
+
"node": ">=22",
18
+
"pnpm": ">=10",
19
+
"npm": "pnpm",
20
+
"yarn": "pnpm"
21
+
},
22
"scripts": {
23
"build": "node build.mjs",
24
"dev": "node build.mjs --watch",
25
+
"clean": "node build.mjs --clean",
26
"browser": "node build.mjs --browser",
27
"browser-mv2": "node build.mjs --browser --mv2",
28
"lint": "eslint packages",
29
+
"lint:fix": "pnpm lint --fix",
30
+
"lint:report": "pnpm lint --output-file eslint_report.json --format json",
31
"typecheck": "tsc --noEmit",
32
"check": "pnpm run lint && pnpm run typecheck",
33
+
"prepare": "husky install",
34
+
"updates": "pnpm taze -r"
35
},
36
"devDependencies": {
37
+
"@moonlight-mod/eslint-config": "catalog:dev",
38
+
"@types/node": "catalog:dev",
39
+
"esbuild": "catalog:dev",
40
+
"esbuild-copy-static-files": "catalog:dev",
41
+
"eslint": "catalog:dev",
42
+
"husky": "catalog:dev",
43
+
"prettier": "catalog:dev",
44
+
"taze": "catalog:dev",
45
+
"typescript": "catalog:dev"
46
}
47
}
+2
-1
packages/browser/blockLoading.json
+2
-1
packages/browser/blockLoading.json
+7
-10
packages/browser/manifest.json
+7
-10
packages/browser/manifest.json
···
1
{
2
"manifest_version": 3,
3
"name": "moonlight",
4
"description": "Yet another Discord mod",
5
-
"version": "1.1.0",
6
-
"permissions": [
7
-
"declarativeNetRequestWithHostAccess",
8
-
"webRequest",
9
-
"scripting",
10
-
"webNavigation"
11
-
],
12
"host_permissions": [
13
"https://moonlight-mod.github.io/*",
14
"https://api.github.com/*",
15
-
"https://*.discord.com/*"
16
],
17
"content_scripts": [
18
{
19
"js": ["index.js"],
20
-
"matches": ["https://*.discord.com/*"],
21
"run_at": "document_start",
22
"world": "MAIN"
23
}
···
43
"web_accessible_resources": [
44
{
45
"resources": ["index.js"],
46
-
"matches": ["https://*.discord.com/*"]
47
}
48
]
49
}
···
1
{
2
+
"$schema": "https://json.schemastore.org/chrome-manifest",
3
"manifest_version": 3,
4
"name": "moonlight",
5
"description": "Yet another Discord mod",
6
+
"version": "1.3.14",
7
+
"permissions": ["declarativeNetRequestWithHostAccess", "webRequest", "scripting", "webNavigation"],
8
"host_permissions": [
9
"https://moonlight-mod.github.io/*",
10
"https://api.github.com/*",
11
+
"https://*.discord.com/*",
12
+
"https://*.discordapp.com/*"
13
],
14
"content_scripts": [
15
{
16
"js": ["index.js"],
17
+
"matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"],
18
"run_at": "document_start",
19
"world": "MAIN"
20
}
···
40
"web_accessible_resources": [
41
{
42
"resources": ["index.js"],
43
+
"matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"]
44
}
45
]
46
}
+6
-5
packages/browser/manifestv2.json
+6
-5
packages/browser/manifestv2.json
···
1
{
2
"manifest_version": 2,
3
"name": "moonlight",
4
"description": "Yet another Discord mod",
5
-
"version": "1.1.0",
6
"permissions": [
7
"webRequest",
8
"webRequestBlocking",
9
"scripting",
10
"webNavigation",
11
-
"https://*.discord.com/assets/*.js",
12
"https://moonlight-mod.github.io/*",
13
-
"https://api.github.com/*",
14
-
"https://*.discord.com/*"
15
],
16
"background": {
17
"scripts": ["background.js"]
···
19
"content_scripts": [
20
{
21
"js": ["index.js"],
22
-
"matches": ["https://*.discord.com/*"],
23
"run_at": "document_start",
24
"world": "MAIN"
25
}
···
1
{
2
+
"$schema": "https://json.schemastore.org/chrome-manifest",
3
"manifest_version": 2,
4
"name": "moonlight",
5
"description": "Yet another Discord mod",
6
+
"version": "1.3.14",
7
"permissions": [
8
"webRequest",
9
"webRequestBlocking",
10
"scripting",
11
"webNavigation",
12
+
"https://*.discord.com/*",
13
+
"https://*.discordapp.com/*",
14
"https://moonlight-mod.github.io/*",
15
+
"https://api.github.com/*"
16
],
17
"background": {
18
"scripts": ["background.js"]
···
20
"content_scripts": [
21
{
22
"js": ["index.js"],
23
+
"matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"],
24
"run_at": "document_start",
25
"world": "MAIN"
26
}
+1
-1
packages/browser/modifyResponseHeaders.json
+1
-1
packages/browser/modifyResponseHeaders.json
+12
-2
packages/browser/package.json
+12
-2
packages/browser/package.json
···
1
{
2
"name": "@moonlight-mod/browser",
3
"private": true,
4
+
"engines": {
5
+
"node": ">=22",
6
+
"pnpm": ">=10",
7
+
"npm": "pnpm",
8
+
"yarn": "pnpm"
9
+
},
10
"dependencies": {
11
"@moonlight-mod/core": "workspace:*",
12
"@moonlight-mod/types": "workspace:*",
13
"@moonlight-mod/web-preload": "workspace:*",
14
+
"@zenfs/core": "catalog:prod",
15
+
"@zenfs/dom": "catalog:prod"
16
+
},
17
+
"engineStrict": true,
18
+
"devDependencies": {
19
+
"@types/chrome": "catalog:dev"
20
}
21
}
+55
-70
packages/browser/src/background-mv2.js
+55
-70
packages/browser/src/background-mv2.js
···
1
/* eslint-disable no-console */
2
/* eslint-disable no-undef */
3
4
-
const starterUrls = ["web.", "sentry."];
5
-
let blockLoading = true;
6
-
let doing = false;
7
-
let collectedUrls = new Set();
8
9
-
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
10
-
const url = new URL(details.url);
11
-
if (!blockLoading && url.hostname.endsWith("discord.com")) {
12
-
console.log("Blocking", details.url);
13
-
blockLoading = true;
14
-
collectedUrls.clear();
15
-
}
16
-
});
17
18
-
async function doTheThing(urls, tabId) {
19
-
console.log("Doing", urls, tabId);
20
21
-
blockLoading = false;
22
23
-
try {
24
-
await chrome.scripting.executeScript({
25
-
target: { tabId },
26
-
world: "MAIN",
27
-
args: [urls],
28
-
func: async (urls) => {
29
-
try {
30
-
await window._moonlightBrowserInit();
31
-
} catch (e) {
32
-
console.log(e);
33
-
}
34
35
-
const scripts = [...document.querySelectorAll("script")].filter(
36
-
(script) => script.src && urls.some((url) => url.includes(script.src))
37
-
);
38
39
-
// backwards
40
-
urls.reverse();
41
-
for (const url of urls) {
42
-
const script = scripts.find((script) => url.includes(script.src));
43
-
console.log("adding new script", script);
44
45
-
const newScript = document.createElement("script");
46
-
for (const { name, value } of script.attributes) {
47
-
newScript.setAttribute(name, value);
48
}
49
-
50
-
script.remove();
51
-
document.documentElement.appendChild(newScript);
52
-
}
53
-
}
54
-
});
55
-
} catch (e) {
56
-
console.log(e);
57
-
}
58
-
59
-
doing = false;
60
-
collectedUrls.clear();
61
-
}
62
-
63
-
chrome.webRequest.onBeforeRequest.addListener(
64
-
async (details) => {
65
-
if (starterUrls.some((url) => details.url.includes(url))) {
66
-
console.log("Adding", details.url);
67
-
collectedUrls.add(details.url);
68
}
69
70
-
if (collectedUrls.size === starterUrls.length) {
71
-
if (doing) return;
72
-
if (!blockLoading) return;
73
-
doing = true;
74
-
const urls = [...collectedUrls];
75
-
const tabId = details.tabId;
76
-
77
-
// yes this is a load-bearing sleep
78
-
setTimeout(() => doTheThing(urls, tabId), 0);
79
-
}
80
-
81
-
if (blockLoading) return { cancel: true };
82
},
83
{
84
-
urls: ["https://*.discord.com/assets/*.js"]
85
},
86
["blocking"]
87
);
···
94
)
95
};
96
},
97
-
{ urls: ["https://*.discord.com/*"] },
98
["blocking", "responseHeaders"]
99
);
···
1
/* eslint-disable no-console */
2
/* eslint-disable no-undef */
3
4
+
const scriptUrls = ["web.", "sentry."];
5
+
let blockedScripts = new Set();
6
7
+
chrome.webRequest.onBeforeRequest.addListener(
8
+
async (details) => {
9
+
if (details.tabId === -1) return;
10
11
+
const url = new URL(details.url);
12
+
const hasUrl = scriptUrls.some((scriptUrl) => {
13
+
return (
14
+
details.url.includes(scriptUrl) &&
15
+
!url.searchParams.has("inj") &&
16
+
(url.host.endsWith("discord.com") || url.host.endsWith("discordapp.com"))
17
+
);
18
+
});
19
+
if (hasUrl) blockedScripts.add(details.url);
20
21
+
if (blockedScripts.size === scriptUrls.length) {
22
+
const blockedScriptsCopy = Array.from(blockedScripts);
23
+
blockedScripts.clear();
24
25
+
setTimeout(async () => {
26
+
console.log("Starting moonlight");
27
+
await chrome.scripting.executeScript({
28
+
target: { tabId: details.tabId },
29
+
world: "MAIN",
30
+
args: [blockedScriptsCopy],
31
+
func: async (blockedScripts) => {
32
+
console.log("Initializing moonlight");
33
+
try {
34
+
await window._moonlightBrowserInit();
35
+
} catch (e) {
36
+
console.error(e);
37
+
}
38
39
+
console.log("Readding scripts");
40
+
try {
41
+
const scripts = [...document.querySelectorAll("script")].filter(
42
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
43
+
);
44
45
+
blockedScripts.reverse();
46
+
for (const url of blockedScripts) {
47
+
if (url.includes("/sentry.")) continue;
48
49
+
const script = scripts.find((script) => url.includes(script.src));
50
+
const newScript = document.createElement("script");
51
+
for (const attr of script.attributes) {
52
+
if (attr.name === "src") attr.value += "?inj";
53
+
newScript.setAttribute(attr.name, attr.value);
54
+
}
55
+
script.remove();
56
+
document.documentElement.appendChild(newScript);
57
+
}
58
+
} catch (e) {
59
+
console.error(e);
60
+
}
61
}
62
+
});
63
+
}, 0);
64
}
65
66
+
if (hasUrl) return { cancel: true };
67
},
68
{
69
+
urls: ["https://*.discord.com/assets/*.js", "https://*.discordapp.com/assets/*.js"]
70
},
71
["blocking"]
72
);
···
79
)
80
};
81
},
82
+
{ urls: ["https://*.discord.com/*", "https://*.discordapp.com/*"] },
83
["blocking", "responseHeaders"]
84
);
+37
-40
packages/browser/src/background.js
+37
-40
packages/browser/src/background.js
···
1
/* eslint-disable no-console */
2
/* eslint-disable no-undef */
3
4
-
const starterUrls = ["web.", "sentry."];
5
-
let blockLoading = true;
6
-
let doing = false;
7
-
let collectedUrls = new Set();
8
9
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
10
const url = new URL(details.url);
11
-
if (!blockLoading && url.hostname.endsWith("discord.com")) {
12
await chrome.declarativeNetRequest.updateEnabledRulesets({
13
enableRulesetIds: ["modifyResponseHeaders", "blockLoading"]
14
});
15
-
blockLoading = true;
16
-
collectedUrls.clear();
17
}
18
});
19
20
chrome.webRequest.onBeforeRequest.addListener(
21
async (details) => {
22
if (details.tabId === -1) return;
23
-
if (starterUrls.some((url) => details.url.includes(url))) {
24
-
console.log("Adding", details.url);
25
-
collectedUrls.add(details.url);
26
-
}
27
28
-
if (collectedUrls.size === starterUrls.length) {
29
-
if (doing) return;
30
-
if (!blockLoading) return;
31
-
doing = true;
32
-
const urls = [...collectedUrls];
33
-
console.log("Doing", urls);
34
35
console.log("Running moonlight script");
36
try {
···
40
files: ["index.js"]
41
});
42
} catch (e) {
43
-
console.log(e);
44
}
45
46
console.log("Initializing moonlight");
···
52
try {
53
await window._moonlightBrowserInit();
54
} catch (e) {
55
-
console.log(e);
56
}
57
}
58
});
···
60
console.log(e);
61
}
62
63
-
console.log("Updating rulesets");
64
try {
65
-
blockLoading = false;
66
await chrome.declarativeNetRequest.updateEnabledRulesets({
67
disableRulesetIds: ["blockLoading"],
68
enableRulesetIds: ["modifyResponseHeaders"]
69
});
70
} catch (e) {
71
-
console.log(e);
72
}
73
74
console.log("Readding scripts");
···
76
await chrome.scripting.executeScript({
77
target: { tabId: details.tabId },
78
world: "MAIN",
79
-
args: [urls],
80
-
func: async (urls) => {
81
const scripts = [...document.querySelectorAll("script")].filter(
82
-
(script) =>
83
-
script.src && urls.some((url) => url.includes(script.src))
84
);
85
86
-
// backwards
87
-
urls.reverse();
88
-
for (const url of urls) {
89
-
const script = scripts.find((script) => url.includes(script.src));
90
-
console.log("adding new script", script);
91
92
const newScript = document.createElement("script");
93
-
for (const { name, value } of script.attributes) {
94
-
newScript.setAttribute(name, value);
95
}
96
-
97
script.remove();
98
document.documentElement.appendChild(newScript);
99
}
100
}
101
});
102
} catch (e) {
103
-
console.log(e);
104
}
105
-
106
-
console.log("Done");
107
-
doing = false;
108
-
collectedUrls.clear();
109
}
110
},
111
{
112
-
urls: ["*://*.discord.com/assets/*.js"]
113
}
114
);
···
1
/* eslint-disable no-console */
2
/* eslint-disable no-undef */
3
4
+
const scriptUrls = ["web.", "sentry."];
5
+
let blockedScripts = new Set();
6
7
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
8
const url = new URL(details.url);
9
+
if (
10
+
!url.searchParams.has("inj") &&
11
+
(url.hostname.endsWith("discord.com") || url.hostname.endsWith("discordapp.com"))
12
+
) {
13
+
console.log("Enabling block ruleset");
14
await chrome.declarativeNetRequest.updateEnabledRulesets({
15
enableRulesetIds: ["modifyResponseHeaders", "blockLoading"]
16
});
17
}
18
});
19
20
chrome.webRequest.onBeforeRequest.addListener(
21
async (details) => {
22
if (details.tabId === -1) return;
23
+
24
+
const url = new URL(details.url);
25
+
const hasUrl = scriptUrls.some((scriptUrl) => {
26
+
return (
27
+
details.url.includes(scriptUrl) &&
28
+
!url.searchParams.has("inj") &&
29
+
(url.hostname.endsWith("discord.com") || url.hostname.endsWith("discordapp.com"))
30
+
);
31
+
});
32
33
+
if (hasUrl) blockedScripts.add(details.url);
34
+
35
+
if (blockedScripts.size === scriptUrls.length) {
36
+
const blockedScriptsCopy = Array.from(blockedScripts);
37
+
blockedScripts.clear();
38
39
console.log("Running moonlight script");
40
try {
···
44
files: ["index.js"]
45
});
46
} catch (e) {
47
+
console.error(e);
48
}
49
50
console.log("Initializing moonlight");
···
56
try {
57
await window._moonlightBrowserInit();
58
} catch (e) {
59
+
console.error(e);
60
}
61
}
62
});
···
64
console.log(e);
65
}
66
67
+
console.log("Disabling block ruleset");
68
try {
69
await chrome.declarativeNetRequest.updateEnabledRulesets({
70
disableRulesetIds: ["blockLoading"],
71
enableRulesetIds: ["modifyResponseHeaders"]
72
});
73
} catch (e) {
74
+
console.error(e);
75
}
76
77
console.log("Readding scripts");
···
79
await chrome.scripting.executeScript({
80
target: { tabId: details.tabId },
81
world: "MAIN",
82
+
args: [blockedScriptsCopy],
83
+
func: async (blockedScripts) => {
84
const scripts = [...document.querySelectorAll("script")].filter(
85
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
86
);
87
88
+
blockedScripts.reverse();
89
+
for (const url of blockedScripts) {
90
+
if (url.includes("/sentry.")) continue;
91
92
+
const script = scripts.find((script) => url.includes(script.src));
93
const newScript = document.createElement("script");
94
+
for (const attr of script.attributes) {
95
+
if (attr.name === "src") attr.value += "?inj";
96
+
newScript.setAttribute(attr.name, attr.value);
97
}
98
script.remove();
99
document.documentElement.appendChild(newScript);
100
}
101
}
102
});
103
} catch (e) {
104
+
console.error(e);
105
}
106
}
107
},
108
{
109
+
urls: ["*://*.discord.com/assets/*.js", "*://*.discordapp.com/assets/*.js"]
110
}
111
);
+93
-81
packages/browser/src/index.ts
+93
-81
packages/browser/src/index.ts
···
4
import { getExtensions } from "@moonlight-mod/core/extension";
5
import { loadExtensions } from "@moonlight-mod/core/extension/loader";
6
import { MoonlightBranch, MoonlightNode } from "@moonlight-mod/types";
7
import { IndexedDB } from "@zenfs/dom";
8
-
import { configure } from "@zenfs/core";
9
import * as fs from "@zenfs/core/promises";
10
11
function getParts(path: string) {
12
if (path.startsWith("/")) path = path.substring(1);
···
14
}
15
16
window._moonlightBrowserInit = async () => {
17
// Set up a virtual filesystem with IndexedDB
18
-
await configure({
19
-
mounts: {
20
-
"/": {
21
-
backend: IndexedDB,
22
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
23
-
// @ts-ignore tsc tweaking
24
-
storeName: "moonlight-fs"
25
-
}
26
-
}
27
});
28
29
-
window.moonlightFS = {
30
-
async readFile(path) {
31
-
return new Uint8Array(await fs.readFile(path));
32
-
},
33
-
async readFileString(path) {
34
-
const file = await this.readFile(path);
35
-
return new TextDecoder().decode(file);
36
-
},
37
-
async writeFile(path, data) {
38
-
await fs.writeFile(path, data);
39
-
},
40
-
async writeFileString(path, data) {
41
-
const file = new TextEncoder().encode(data);
42
-
await this.writeFile(path, file);
43
-
},
44
-
async unlink(path) {
45
-
await fs.unlink(path);
46
-
},
47
48
-
async readdir(path) {
49
-
return await fs.readdir(path);
50
-
},
51
-
async mkdir(path) {
52
-
const parts = getParts(path);
53
-
for (let i = 0; i < parts.length; i++) {
54
-
const path = this.join(...parts.slice(0, i + 1));
55
-
if (!(await this.exists(path))) await fs.mkdir(path);
56
-
}
57
-
},
58
59
-
async rmdir(path) {
60
-
const entries = await this.readdir(path);
61
62
-
for (const entry of entries) {
63
-
const fullPath = this.join(path, entry);
64
-
const isFile = await this.isFile(fullPath);
65
-
if (isFile) {
66
-
await this.unlink(fullPath);
67
-
} else {
68
-
await this.rmdir(fullPath);
69
}
70
-
}
71
72
-
await fs.rmdir(path);
73
-
},
74
75
-
async exists(path) {
76
-
return await fs.exists(path);
77
-
},
78
-
async isFile(path) {
79
-
return (await fs.stat(path)).isFile();
80
-
},
81
82
-
join(...parts) {
83
-
let str = parts.join("/");
84
-
if (!str.startsWith("/")) str = "/" + str;
85
-
return str;
86
},
87
-
dirname(path) {
88
-
const parts = getParts(path);
89
-
return "/" + parts.slice(0, parts.length - 1).join("/");
90
-
}
91
};
92
93
// Actual loading begins here
94
-
const config = await readConfig();
95
initLogger(config);
96
97
const extensions = await getExtensions();
98
const processedExtensions = await loadExtensions(extensions);
99
100
-
function getConfig(ext: string) {
101
-
const val = config.extensions[ext];
102
-
if (val == null || typeof val === "boolean") return undefined;
103
-
return val.config;
104
-
}
105
-
106
const moonlightNode: MoonlightNode = {
107
-
config,
108
extensions,
109
processedExtensions,
110
nativesCache: {},
111
isBrowser: true,
112
113
version: MOONLIGHT_VERSION,
114
branch: MOONLIGHT_BRANCH as MoonlightBranch,
115
116
-
getConfig,
117
-
getConfigOption: <T>(ext: string, name: string) => {
118
-
const config = getConfig(ext);
119
-
if (config == null) return undefined;
120
-
const option = config[name];
121
-
if (option == null) return undefined;
122
-
return option as T;
123
},
124
getNatives: () => {},
125
getLogger: (id: string) => {
126
return new Logger(id);
···
133
return `/extensions/${ext}`;
134
},
135
136
-
writeConfig
137
};
138
139
Object.assign(window, {
···
141
});
142
143
// This is set by web-preload for us
144
-
await window._moonlightBrowserLoad();
145
};
···
4
import { getExtensions } from "@moonlight-mod/core/extension";
5
import { loadExtensions } from "@moonlight-mod/core/extension/loader";
6
import { MoonlightBranch, MoonlightNode } from "@moonlight-mod/types";
7
+
import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
8
import { IndexedDB } from "@zenfs/dom";
9
+
import { configureSingle } from "@zenfs/core";
10
import * as fs from "@zenfs/core/promises";
11
+
import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event";
12
+
import { createEventEmitter } from "@moonlight-mod/core/util/event";
13
14
function getParts(path: string) {
15
if (path.startsWith("/")) path = path.substring(1);
···
17
}
18
19
window._moonlightBrowserInit = async () => {
20
+
delete window._moonlightBrowserInit;
21
+
22
// Set up a virtual filesystem with IndexedDB
23
+
await configureSingle({
24
+
backend: IndexedDB,
25
+
storeName: "moonlight-fs"
26
});
27
28
+
window.moonlightNodeSandboxed = {
29
+
fs: {
30
+
async readFile(path) {
31
+
return new Uint8Array(await fs.readFile(path));
32
+
},
33
+
async readFileString(path) {
34
+
const file = await this.readFile(path);
35
+
return new TextDecoder().decode(file);
36
+
},
37
+
async writeFile(path, data) {
38
+
await fs.writeFile(path, data);
39
+
},
40
+
async writeFileString(path, data) {
41
+
const file = new TextEncoder().encode(data);
42
+
await this.writeFile(path, file);
43
+
},
44
+
async unlink(path) {
45
+
await fs.unlink(path);
46
+
},
47
48
+
async readdir(path) {
49
+
return await fs.readdir(path);
50
+
},
51
+
async mkdir(path) {
52
+
const parts = getParts(path);
53
+
for (let i = 0; i < parts.length; i++) {
54
+
const path = this.join(...parts.slice(0, i + 1));
55
+
if (!(await this.exists(path))) await fs.mkdir(path);
56
+
}
57
+
},
58
59
+
async rmdir(path) {
60
+
const entries = await this.readdir(path);
61
62
+
for (const entry of entries) {
63
+
const fullPath = this.join(path, entry);
64
+
const isFile = await this.isFile(fullPath);
65
+
if (isFile) {
66
+
await this.unlink(fullPath);
67
+
} else {
68
+
await this.rmdir(fullPath);
69
+
}
70
}
71
72
+
await fs.rmdir(path);
73
+
},
74
75
+
async exists(path) {
76
+
return await fs.exists(path);
77
+
},
78
+
async isFile(path) {
79
+
return (await fs.stat(path)).isFile();
80
+
},
81
+
async isDir(path) {
82
+
return (await fs.stat(path)).isDirectory();
83
+
},
84
85
+
join(...parts) {
86
+
let str = parts.join("/");
87
+
if (!str.startsWith("/")) str = "/" + str;
88
+
return str;
89
+
},
90
+
dirname(path) {
91
+
const parts = getParts(path);
92
+
return "/" + parts.slice(0, parts.length - 1).join("/");
93
+
}
94
},
95
+
// TODO
96
+
addCors(url) {},
97
+
addBlocked(url) {}
98
};
99
100
// Actual loading begins here
101
+
let config = await readConfig();
102
initLogger(config);
103
104
const extensions = await getExtensions();
105
const processedExtensions = await loadExtensions(extensions);
106
107
const moonlightNode: MoonlightNode = {
108
+
get config() {
109
+
return config;
110
+
},
111
extensions,
112
processedExtensions,
113
nativesCache: {},
114
isBrowser: true,
115
+
events: createEventEmitter<NodeEventType, NodeEventPayloads>(),
116
117
version: MOONLIGHT_VERSION,
118
branch: MOONLIGHT_BRANCH as MoonlightBranch,
119
120
+
getConfig(ext) {
121
+
return getConfig(ext, config);
122
+
},
123
+
getConfigOption(ext, name) {
124
+
const manifest = getManifest(extensions, ext);
125
+
return getConfigOption(ext, name, config, manifest?.settings);
126
+
},
127
+
async setConfigOption(ext, name, value) {
128
+
setConfigOption(config, ext, name, value);
129
+
await this.writeConfig(config);
130
},
131
+
132
getNatives: () => {},
133
getLogger: (id: string) => {
134
return new Logger(id);
···
141
return `/extensions/${ext}`;
142
},
143
144
+
async writeConfig(newConfig) {
145
+
await writeConfig(newConfig);
146
+
config = newConfig;
147
+
this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig);
148
+
}
149
};
150
151
Object.assign(window, {
···
153
});
154
155
// This is set by web-preload for us
156
+
await window._moonlightWebLoad!();
157
};
+1
packages/browser/tsconfig.json
+1
packages/browser/tsconfig.json
+7
packages/core/package.json
+7
packages/core/package.json
+4
-8
packages/core/src/config.ts
+4
-8
packages/core/src/config.ts
···
6
const logger = new Logger("core/config");
7
8
const defaultConfig: Config = {
9
extensions: {
10
moonbase: true,
11
disableSentry: true,
···
18
export async function writeConfig(config: Config) {
19
try {
20
const configPath = await getConfigPath();
21
-
await moonlightFS.writeFileString(
22
-
configPath,
23
-
JSON.stringify(config, null, 2)
24
-
);
25
} catch (e) {
26
logger.error("Failed to write config", e);
27
}
···
33
}
34
35
const configPath = await getConfigPath();
36
-
if (!(await moonlightFS.exists(configPath))) {
37
await writeConfig(defaultConfig);
38
return defaultConfig;
39
} else {
40
try {
41
-
let config: Config = JSON.parse(
42
-
await moonlightFS.readFileString(configPath)
43
-
);
44
// Assign the default values if they don't exist (newly added)
45
config = { ...defaultConfig, ...config };
46
await writeConfig(config);
···
6
const logger = new Logger("core/config");
7
8
const defaultConfig: Config = {
9
+
// If you're updating this, update `builtinExtensions` in constants as well
10
extensions: {
11
moonbase: true,
12
disableSentry: true,
···
19
export async function writeConfig(config: Config) {
20
try {
21
const configPath = await getConfigPath();
22
+
await moonlightNodeSandboxed.fs.writeFileString(configPath, JSON.stringify(config, null, 2));
23
} catch (e) {
24
logger.error("Failed to write config", e);
25
}
···
31
}
32
33
const configPath = await getConfigPath();
34
+
if (!(await moonlightNodeSandboxed.fs.exists(configPath))) {
35
await writeConfig(defaultConfig);
36
return defaultConfig;
37
} else {
38
try {
39
+
let config: Config = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(configPath));
40
// Assign the default values if they don't exist (newly added)
41
config = { ...defaultConfig, ...config };
42
await writeConfig(config);
+17
packages/core/src/cors.ts
+17
packages/core/src/cors.ts
···
···
1
+
const cors: string[] = [];
2
+
const blocked: string[] = [];
3
+
4
+
export function registerCors(url: string) {
5
+
cors.push(url);
6
+
}
7
+
8
+
export function registerBlocked(url: string) {
9
+
blocked.push(url);
10
+
}
11
+
12
+
export function getDynamicCors() {
13
+
return {
14
+
cors,
15
+
blocked
16
+
};
17
+
}
+62
-59
packages/core/src/extension/loader.ts
+62
-59
packages/core/src/extension/loader.ts
···
13
import calculateDependencies from "../util/dependency";
14
import { createEventEmitter } from "../util/event";
15
import { registerStyles } from "../styles";
16
-
import { EventPayloads, EventType } from "@moonlight-mod/types/core/event";
17
18
const logger = new Logger("core/extension/loader");
19
20
-
function loadExtWeb(ext: DetectedExtension) {
21
if (ext.scripts.web != null) {
22
-
const source = ext.scripts.web;
23
-
const fn = new Function("require", "module", "exports", source);
24
25
-
const module = { id: ext.id, exports: {} };
26
-
fn.apply(window, [
27
-
() => {
28
-
logger.warn("Attempted to require() from web");
29
-
},
30
-
module,
31
-
module.exports
32
-
]);
33
34
-
const exports: ExtensionWebExports = module.exports;
35
if (exports.patches != null) {
36
let idx = 0;
37
for (const patch of exports.patches) {
38
if (Array.isArray(patch.replace)) {
39
-
for (const replacement of patch.replace) {
40
-
const newPatch = Object.assign({}, patch, {
41
-
replace: replacement
42
-
});
43
-
44
-
registerPatch({ ...newPatch, ext: ext.id, id: idx });
45
-
idx++;
46
-
}
47
} else {
48
-
registerPatch({ ...patch, ext: ext.id, id: idx });
49
-
idx++;
50
}
51
}
52
}
53
54
if (exports.webpackModules != null) {
55
for (const [name, wp] of Object.entries(exports.webpackModules)) {
56
if (wp.run == null && ext.scripts.webpackModules?.[name] != null) {
57
-
const func = new Function(
58
-
"module",
59
-
"exports",
60
-
"require",
61
-
ext.scripts.webpackModules[name]!
62
-
) as WebpackModuleFunc;
63
registerWebpackModule({
64
...wp,
65
ext: ext.id,
···
73
}
74
75
if (exports.styles != null) {
76
-
registerStyles(
77
-
exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`)
78
-
);
79
}
80
}
81
}
82
83
async function loadExt(ext: DetectedExtension) {
84
webTarget: {
85
-
loadExtWeb(ext);
86
}
87
88
nodePreload: {
···
113
InvalidEnvironment
114
}
115
116
-
export function checkExtensionCompat(
117
-
manifest: ExtensionManifest
118
-
): ExtensionCompat {
119
let environment;
120
webTarget: {
121
environment = ExtensionEnvironment.Web;
···
124
environment = ExtensionEnvironment.Desktop;
125
}
126
127
-
if (manifest.apiLevel !== constants.apiLevel)
128
-
return ExtensionCompat.InvalidApiLevel;
129
-
if (
130
-
(manifest.environment ?? "both") !== "both" &&
131
-
manifest.environment !== environment
132
-
)
133
return ExtensionCompat.InvalidEnvironment;
134
return ExtensionCompat.Compatible;
135
}
···
148
extensions fires an event on completion, which allows us to await the loading
149
of another extension, resolving dependencies & load order effectively.
150
*/
151
-
export async function loadExtensions(
152
-
exts: DetectedExtension[]
153
-
): Promise<ProcessedExtensions> {
154
-
exts = exts.filter(
155
-
(ext) => checkExtensionCompat(ext.manifest) === ExtensionCompat.Compatible
156
-
);
157
158
const config = await readConfig();
159
const items = exts
···
193
};
194
}
195
196
-
export async function loadProcessedExtensions({
197
-
extensions,
198
-
dependencyGraph
199
-
}: ProcessedExtensions) {
200
-
const eventEmitter = createEventEmitter<EventType, EventPayloads>();
201
const finished: Set<string> = new Set();
202
203
logger.trace(
···
219
}
220
221
function done() {
222
-
eventEmitter.removeEventListener(EventType.ExtensionLoad, cb);
223
r();
224
}
225
226
-
eventEmitter.addEventListener(EventType.ExtensionLoad, cb);
227
if (finished.has(dep)) done();
228
})
229
);
230
231
if (waitPromises.length > 0) {
232
-
logger.debug(
233
-
`Waiting on ${waitPromises.length} dependencies for "${ext.id}"`
234
-
);
235
await Promise.all(waitPromises);
236
}
237
···
239
await loadExt(ext);
240
241
finished.add(ext.id);
242
-
eventEmitter.dispatchEvent(EventType.ExtensionLoad, ext.id);
243
logger.debug(`Loaded "${ext.id}"`);
244
}
245
···
13
import calculateDependencies from "../util/dependency";
14
import { createEventEmitter } from "../util/event";
15
import { registerStyles } from "../styles";
16
+
import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event";
17
18
const logger = new Logger("core/extension/loader");
19
20
+
function evalIIFE(id: string, source: string): ExtensionWebExports {
21
+
const fn = new Function("require", "module", "exports", source);
22
+
23
+
const module = { id, exports: {} };
24
+
fn.apply(window, [
25
+
() => {
26
+
logger.warn("Attempted to require() from web");
27
+
},
28
+
module,
29
+
module.exports
30
+
]);
31
+
32
+
return module.exports;
33
+
}
34
+
35
+
async function evalEsm(source: string): Promise<ExtensionWebExports> {
36
+
// Data URLs (`data:`) don't seem to work under the CSP, but object URLs do
37
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
38
+
39
+
const module = await import(url);
40
+
41
+
URL.revokeObjectURL(url);
42
+
43
+
return module;
44
+
}
45
+
46
+
async function loadExtWeb(ext: DetectedExtension) {
47
if (ext.scripts.web != null) {
48
+
const source = ext.scripts.web + `\n//# sourceURL=${ext.id}/web.js`;
49
50
+
let exports: ExtensionWebExports;
51
52
+
try {
53
+
exports = evalIIFE(ext.id, source);
54
+
} catch {
55
+
logger.trace(`Failed to load IIFE for extension ${ext.id}, trying ESM loading`);
56
+
exports = await evalEsm(source);
57
+
}
58
+
59
if (exports.patches != null) {
60
let idx = 0;
61
for (const patch of exports.patches) {
62
if (Array.isArray(patch.replace)) {
63
+
registerPatch({ ...patch, ext: ext.id, id: idx });
64
} else {
65
+
registerPatch({ ...patch, replace: [patch.replace], ext: ext.id, id: idx });
66
}
67
+
idx++;
68
}
69
}
70
71
if (exports.webpackModules != null) {
72
for (const [name, wp] of Object.entries(exports.webpackModules)) {
73
if (wp.run == null && ext.scripts.webpackModules?.[name] != null) {
74
+
const source = ext.scripts.webpackModules[name]! + `\n//# sourceURL=${ext.id}/webpackModules/${name}.js`;
75
+
const func = new Function("module", "exports", "require", source) as WebpackModuleFunc;
76
registerWebpackModule({
77
...wp,
78
ext: ext.id,
···
86
}
87
88
if (exports.styles != null) {
89
+
registerStyles(exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`));
90
+
}
91
+
if (ext.scripts.style != null) {
92
+
registerStyles([`/* ${ext.id}#style.css */ ${ext.scripts.style}`]);
93
}
94
}
95
}
96
97
async function loadExt(ext: DetectedExtension) {
98
webTarget: {
99
+
try {
100
+
await loadExtWeb(ext);
101
+
} catch (e) {
102
+
logger.error(`Failed to load extension "${ext.id}"`, e);
103
+
}
104
}
105
106
nodePreload: {
···
131
InvalidEnvironment
132
}
133
134
+
export function checkExtensionCompat(manifest: ExtensionManifest): ExtensionCompat {
135
let environment;
136
webTarget: {
137
environment = ExtensionEnvironment.Web;
···
140
environment = ExtensionEnvironment.Desktop;
141
}
142
143
+
if (manifest.apiLevel !== constants.apiLevel) return ExtensionCompat.InvalidApiLevel;
144
+
if ((manifest.environment ?? "both") !== "both" && manifest.environment !== environment)
145
return ExtensionCompat.InvalidEnvironment;
146
return ExtensionCompat.Compatible;
147
}
···
160
extensions fires an event on completion, which allows us to await the loading
161
of another extension, resolving dependencies & load order effectively.
162
*/
163
+
export async function loadExtensions(exts: DetectedExtension[]): Promise<ProcessedExtensions> {
164
+
exts = exts.filter((ext) => checkExtensionCompat(ext.manifest) === ExtensionCompat.Compatible);
165
166
const config = await readConfig();
167
const items = exts
···
201
};
202
}
203
204
+
export async function loadProcessedExtensions({ extensions, dependencyGraph }: ProcessedExtensions) {
205
+
const eventEmitter = createEventEmitter<WebEventType, WebEventPayloads>();
206
const finished: Set<string> = new Set();
207
208
logger.trace(
···
224
}
225
226
function done() {
227
+
eventEmitter.removeEventListener(WebEventType.ExtensionLoad, cb);
228
r();
229
}
230
231
+
eventEmitter.addEventListener(WebEventType.ExtensionLoad, cb);
232
if (finished.has(dep)) done();
233
})
234
);
235
236
if (waitPromises.length > 0) {
237
+
logger.debug(`Waiting on ${waitPromises.length} dependencies for "${ext.id}"`);
238
await Promise.all(waitPromises);
239
}
240
···
242
await loadExt(ext);
243
244
finished.add(ext.id);
245
+
eventEmitter.dispatchEvent(WebEventType.ExtensionLoad, ext.id);
246
logger.debug(`Loaded "${ext.id}"`);
247
}
248
+55
-78
packages/core/src/extension.ts
+55
-78
packages/core/src/extension.ts
···
1
-
import {
2
-
ExtensionManifest,
3
-
DetectedExtension,
4
-
ExtensionLoadSource,
5
-
constants
6
-
} from "@moonlight-mod/types";
7
import { readConfig } from "./config";
8
import { getCoreExtensionsPath, getExtensionsPath } from "./util/data";
9
import Logger from "./util/logger";
···
13
async function findManifests(dir: string): Promise<string[]> {
14
const ret = [];
15
16
-
if (await moonlightFS.exists(dir)) {
17
-
for (const file of await moonlightFS.readdir(dir)) {
18
-
const path = moonlightFS.join(dir, file);
19
if (file === "manifest.json") {
20
ret.push(path);
21
}
22
23
-
if (!(await moonlightFS.isFile(path))) {
24
ret.push(...(await findManifests(path)));
25
}
26
}
···
31
32
async function loadDetectedExtensions(
33
dir: string,
34
-
type: ExtensionLoadSource
35
): Promise<DetectedExtension[]> {
36
const ret: DetectedExtension[] = [];
37
38
const manifests = await findManifests(dir);
39
for (const manifestPath of manifests) {
40
try {
41
-
if (!(await moonlightFS.exists(manifestPath))) continue;
42
-
const dir = moonlightFS.dirname(manifestPath);
43
44
-
const manifest: ExtensionManifest = JSON.parse(
45
-
await moonlightFS.readFileString(manifestPath)
46
-
);
47
48
-
const webPath = moonlightFS.join(dir, "index.js");
49
-
const nodePath = moonlightFS.join(dir, "node.js");
50
-
const hostPath = moonlightFS.join(dir, "host.js");
51
52
// if none exist (empty manifest) don't give a shit
53
if (
54
-
!moonlightFS.exists(webPath) &&
55
-
!moonlightFS.exists(nodePath) &&
56
-
!moonlightFS.exists(hostPath)
57
) {
58
continue;
59
}
60
61
-
const web = (await moonlightFS.exists(webPath))
62
-
? await moonlightFS.readFileString(webPath)
63
: undefined;
64
65
let url: string | undefined = undefined;
66
-
const urlPath = moonlightFS.join(dir, constants.repoUrlFile);
67
-
if (
68
-
type === ExtensionLoadSource.Normal &&
69
-
(await moonlightFS.exists(urlPath))
70
-
) {
71
-
url = await moonlightFS.readFileString(urlPath);
72
}
73
74
const wpModules: Record<string, string> = {};
75
-
const wpModulesPath = moonlightFS.join(dir, "webpackModules");
76
-
if (await moonlightFS.exists(wpModulesPath)) {
77
-
const wpModulesFile = await moonlightFS.readdir(wpModulesPath);
78
79
for (const wpModuleFile of wpModulesFile) {
80
if (wpModuleFile.endsWith(".js")) {
81
-
wpModules[wpModuleFile.replace(".js", "")] =
82
-
await moonlightFS.readFileString(
83
-
moonlightFS.join(wpModulesPath, wpModuleFile)
84
-
);
85
}
86
}
87
}
88
89
ret.push({
90
id: manifest.id,
···
97
web,
98
webPath: web != null ? webPath : undefined,
99
webpackModules: wpModules,
100
-
nodePath: (await moonlightFS.exists(nodePath)) ? nodePath : undefined,
101
-
hostPath: (await moonlightFS.exists(hostPath)) ? hostPath : undefined
102
}
103
});
104
-
} catch (e) {
105
-
logger.error(e, "Failed to load extension");
106
}
107
}
108
···
112
async function getExtensionsNative(): Promise<DetectedExtension[]> {
113
const config = await readConfig();
114
const res = [];
115
116
-
res.push(
117
-
...(await loadDetectedExtensions(
118
-
getCoreExtensionsPath(),
119
-
ExtensionLoadSource.Core
120
-
))
121
-
);
122
-
123
-
res.push(
124
-
...(await loadDetectedExtensions(
125
-
await getExtensionsPath(),
126
-
ExtensionLoadSource.Normal
127
-
))
128
-
);
129
130
for (const devSearchPath of config.devSearchPaths ?? []) {
131
-
res.push(
132
-
...(await loadDetectedExtensions(
133
-
devSearchPath,
134
-
ExtensionLoadSource.Developer
135
-
))
136
-
);
137
}
138
139
return res;
140
}
141
142
async function getExtensionsBrowser(): Promise<DetectedExtension[]> {
143
const ret: DetectedExtension[] = [];
144
145
-
const coreExtensionsFs: Record<string, string> = JSON.parse(
146
-
// @ts-expect-error shut up
147
-
_moonlight_coreExtensionsStr
148
-
);
149
-
const coreExtensions = Array.from(
150
-
new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0]))
151
-
);
152
153
for (const ext of coreExtensions) {
154
if (!coreExtensionsFs[`${ext}/index.js`]) continue;
···
159
const wpModulesPath = `${ext}/webpackModules`;
160
for (const wpModuleFile of Object.keys(coreExtensionsFs)) {
161
if (wpModuleFile.startsWith(wpModulesPath)) {
162
-
wpModules[
163
-
wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "")
164
-
] = coreExtensionsFs[wpModuleFile];
165
}
166
}
167
···
173
},
174
scripts: {
175
web,
176
-
webpackModules: wpModules
177
}
178
});
179
}
180
181
-
if (await moonlightFS.exists("/extensions")) {
182
-
ret.push(
183
-
...(await loadDetectedExtensions(
184
-
"/extensions",
185
-
ExtensionLoadSource.Normal
186
-
))
187
-
);
188
}
189
190
return ret;
···
1
+
import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, constants } from "@moonlight-mod/types";
2
import { readConfig } from "./config";
3
import { getCoreExtensionsPath, getExtensionsPath } from "./util/data";
4
import Logger from "./util/logger";
···
8
async function findManifests(dir: string): Promise<string[]> {
9
const ret = [];
10
11
+
if (await moonlightNodeSandboxed.fs.exists(dir)) {
12
+
for (const file of await moonlightNodeSandboxed.fs.readdir(dir)) {
13
+
const path = moonlightNodeSandboxed.fs.join(dir, file);
14
if (file === "manifest.json") {
15
ret.push(path);
16
}
17
18
+
if (!(await moonlightNodeSandboxed.fs.isFile(path))) {
19
ret.push(...(await findManifests(path)));
20
}
21
}
···
26
27
async function loadDetectedExtensions(
28
dir: string,
29
+
type: ExtensionLoadSource,
30
+
seen: Set<string>
31
): Promise<DetectedExtension[]> {
32
const ret: DetectedExtension[] = [];
33
34
const manifests = await findManifests(dir);
35
for (const manifestPath of manifests) {
36
try {
37
+
if (!(await moonlightNodeSandboxed.fs.exists(manifestPath))) continue;
38
+
const dir = moonlightNodeSandboxed.fs.dirname(manifestPath);
39
40
+
const manifest: ExtensionManifest = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(manifestPath));
41
+
if (seen.has(manifest.id)) {
42
+
logger.warn(`Duplicate extension found, skipping: ${manifest.id}`);
43
+
continue;
44
+
}
45
+
seen.add(manifest.id);
46
47
+
const webPath = moonlightNodeSandboxed.fs.join(dir, "index.js");
48
+
const nodePath = moonlightNodeSandboxed.fs.join(dir, "node.js");
49
+
const hostPath = moonlightNodeSandboxed.fs.join(dir, "host.js");
50
51
// if none exist (empty manifest) don't give a shit
52
if (
53
+
!moonlightNodeSandboxed.fs.exists(webPath) &&
54
+
!moonlightNodeSandboxed.fs.exists(nodePath) &&
55
+
!moonlightNodeSandboxed.fs.exists(hostPath)
56
) {
57
continue;
58
}
59
60
+
const web = (await moonlightNodeSandboxed.fs.exists(webPath))
61
+
? await moonlightNodeSandboxed.fs.readFileString(webPath)
62
: undefined;
63
64
let url: string | undefined = undefined;
65
+
const urlPath = moonlightNodeSandboxed.fs.join(dir, constants.repoUrlFile);
66
+
if (type === ExtensionLoadSource.Normal && (await moonlightNodeSandboxed.fs.exists(urlPath))) {
67
+
url = await moonlightNodeSandboxed.fs.readFileString(urlPath);
68
}
69
70
const wpModules: Record<string, string> = {};
71
+
const wpModulesPath = moonlightNodeSandboxed.fs.join(dir, "webpackModules");
72
+
if (await moonlightNodeSandboxed.fs.exists(wpModulesPath)) {
73
+
const wpModulesFile = await moonlightNodeSandboxed.fs.readdir(wpModulesPath);
74
75
for (const wpModuleFile of wpModulesFile) {
76
if (wpModuleFile.endsWith(".js")) {
77
+
wpModules[wpModuleFile.replace(".js", "")] = await moonlightNodeSandboxed.fs.readFileString(
78
+
moonlightNodeSandboxed.fs.join(wpModulesPath, wpModuleFile)
79
+
);
80
}
81
}
82
}
83
+
84
+
const stylePath = moonlightNodeSandboxed.fs.join(dir, "style.css");
85
86
ret.push({
87
id: manifest.id,
···
94
web,
95
webPath: web != null ? webPath : undefined,
96
webpackModules: wpModules,
97
+
nodePath: (await moonlightNodeSandboxed.fs.exists(nodePath)) ? nodePath : undefined,
98
+
hostPath: (await moonlightNodeSandboxed.fs.exists(hostPath)) ? hostPath : undefined,
99
+
style: (await moonlightNodeSandboxed.fs.exists(stylePath))
100
+
? await moonlightNodeSandboxed.fs.readFileString(stylePath)
101
+
: undefined
102
}
103
});
104
+
} catch (err) {
105
+
logger.error(`Failed to load extension from "${manifestPath}":`, err);
106
}
107
}
108
···
112
async function getExtensionsNative(): Promise<DetectedExtension[]> {
113
const config = await readConfig();
114
const res = [];
115
+
const seen = new Set<string>();
116
117
+
res.push(...(await loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core, seen)));
118
119
for (const devSearchPath of config.devSearchPaths ?? []) {
120
+
res.push(...(await loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer, seen)));
121
}
122
123
+
res.push(...(await loadDetectedExtensions(await getExtensionsPath(), ExtensionLoadSource.Normal, seen)));
124
+
125
return res;
126
}
127
128
async function getExtensionsBrowser(): Promise<DetectedExtension[]> {
129
const ret: DetectedExtension[] = [];
130
+
const seen = new Set<string>();
131
132
+
const coreExtensionsFs: Record<string, string> = JSON.parse(_moonlight_coreExtensionsStr);
133
+
const coreExtensions = Array.from(new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0])));
134
135
for (const ext of coreExtensions) {
136
if (!coreExtensionsFs[`${ext}/index.js`]) continue;
···
141
const wpModulesPath = `${ext}/webpackModules`;
142
for (const wpModuleFile of Object.keys(coreExtensionsFs)) {
143
if (wpModuleFile.startsWith(wpModulesPath)) {
144
+
wpModules[wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "")] = coreExtensionsFs[wpModuleFile];
145
}
146
}
147
···
153
},
154
scripts: {
155
web,
156
+
webpackModules: wpModules,
157
+
style: coreExtensionsFs[`${ext}/style.css`]
158
}
159
});
160
+
seen.add(manifest.id);
161
}
162
163
+
if (await moonlightNodeSandboxed.fs.exists("/extensions")) {
164
+
ret.push(...(await loadDetectedExtensions("/extensions", ExtensionLoadSource.Normal, seen)));
165
}
166
167
return ret;
+3
packages/core/src/fs.ts
+3
packages/core/src/fs.ts
+138
-122
packages/core/src/patch.ts
+138
-122
packages/core/src/patch.ts
···
11
} from "@moonlight-mod/types";
12
import Logger from "./util/logger";
13
import calculateDependencies, { Dependency } from "./util/dependency";
14
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
15
-
import { EventType } from "@moonlight-mod/types/core/event";
16
17
const logger = new Logger("core/patch");
18
···
21
let webpackModules: Set<IdentifiedWebpackModule> = new Set();
22
let webpackRequire: WebpackRequireType | null = null;
23
24
-
const moduleLoadSubscriptions: Map<string, ((moduleId: string) => void)[]> =
25
-
new Map();
26
27
export function registerPatch(patch: IdentifiedPatch) {
28
patches.push(patch);
29
moonlight.unpatched.add(patch);
30
}
···
36
}
37
}
38
39
-
export function onModuleLoad(
40
-
module: string | string[],
41
-
callback: (moduleId: string) => void
42
-
): void {
43
let moduleIds = module;
44
45
if (typeof module === "string") {
···
67
const moduleCache: Record<string, string> = {};
68
const patched: Record<string, Array<string>> = {};
69
70
-
function patchModules(entry: WebpackJsonpEntry[1]) {
71
-
function patchModule(id: string, patchId: string, replaced: string) {
72
-
// Store what extensions patched what modules for easier debugging
73
-
patched[id] = patched[id] || [];
74
-
patched[id].push(patchId);
75
76
-
// Webpack module arguments are minified, so we replace them with consistent names
77
-
// We have to wrap it so things don't break, though
78
-
const patchedStr = patched[id].sort().join(", ");
79
80
-
const wrapped =
81
-
`(${replaced}).apply(this, arguments)\n` +
82
-
`// Patched by moonlight: ${patchedStr}\n` +
83
-
`//# sourceURL=Webpack-Module-${id}`;
84
85
-
try {
86
-
const func = new Function(
87
-
"module",
88
-
"exports",
89
-
"require",
90
-
wrapped
91
-
) as WebpackModuleFunc;
92
-
entry[id] = func;
93
-
entry[id].__moonlight = true;
94
-
return true;
95
-
} catch (e) {
96
-
logger.warn("Error constructing function for patch", patchId, e);
97
-
patched[id].pop();
98
-
return false;
99
-
}
100
}
101
102
// Populate the module cache
103
for (const [id, func] of Object.entries(entry)) {
104
if (!Object.hasOwn(moduleCache, id) && func.__moonlight !== true) {
···
109
110
for (const [id, func] of Object.entries(entry)) {
111
if (func.__moonlight === true) continue;
112
-
let moduleString = moduleCache[id];
113
114
for (let i = 0; i < patches.length; i++) {
115
const patch = patches[i];
116
if (patch.prerequisite != null && !patch.prerequisite()) {
117
continue;
118
}
119
···
122
patch.find.lastIndex = 0;
123
}
124
125
-
// indexOf is faster than includes by 0.25% lmao
126
-
const match =
127
-
typeof patch.find === "string"
128
-
? moduleString.indexOf(patch.find) !== -1
129
-
: patch.find.test(moduleString);
130
131
// Global regexes apply to all modules
132
-
const shouldRemove =
133
-
typeof patch.find === "string" ? true : !patch.find.global;
134
135
if (match) {
136
-
moonlight.unpatched.delete(patch);
137
138
-
// We ensured all arrays get turned into normal PatchReplace objects on register
139
-
const replace = patch.replace as PatchReplace;
140
-
141
-
if (
142
-
replace.type === undefined ||
143
-
replace.type === PatchReplaceType.Normal
144
-
) {
145
-
// Add support for \i to match rspack's minified names
146
-
if (typeof replace.match !== "string") {
147
-
replace.match = new RegExp(
148
-
replace.match.source.replace(/\\i/g, "[A-Za-z_$][\\w$]*"),
149
-
replace.match.flags
150
-
);
151
-
}
152
-
// tsc fails to detect the overloads for this, so I'll just do this
153
-
// Verbose, but it works
154
-
let replaced;
155
-
if (typeof replace.replacement === "string") {
156
-
replaced = moduleString.replace(replace.match, replace.replacement);
157
-
} else {
158
-
replaced = moduleString.replace(replace.match, replace.replacement);
159
-
}
160
161
-
if (replaced === moduleString) {
162
-
logger.warn("Patch replacement failed", id, patch);
163
-
continue;
164
-
}
165
166
-
if (patchModule(id, `${patch.ext}#${patch.id}`, replaced)) {
167
-
moduleString = replaced;
168
}
169
-
} else if (replace.type === PatchReplaceType.Module) {
170
-
// Directly replace the module with a new one
171
-
const newModule = replace.replacement(moduleString);
172
-
entry[id] = newModule;
173
-
entry[id].__moonlight = true;
174
-
moduleString =
175
-
newModule.toString().replace(/\n/g, "") +
176
-
`//# sourceURL=Webpack-Module-${id}`;
177
}
178
179
-
if (shouldRemove) {
180
-
patches.splice(i--, 1);
181
}
182
}
183
}
184
185
-
moduleCache[id] = moduleString;
186
187
try {
188
const parsed = moonlight.lunast.parseScript(id, moduleString);
189
if (parsed != null) {
190
for (const [parsedId, parsedScript] of Object.entries(parsed)) {
191
-
if (patchModule(parsedId, "lunast", parsedScript)) {
192
moduleCache[parsedId] = parsedScript;
193
}
194
}
···
198
}
199
200
if (moonlightNode.config.patchAll === true) {
201
-
if (
202
-
(typeof id !== "string" || !id.includes("_")) &&
203
-
!entry[id].__moonlight
204
-
) {
205
-
const wrapped =
206
-
`(${moduleCache[id]}).apply(this, arguments)\n` +
207
-
`//# sourceURL=Webpack-Module-${id}`;
208
-
entry[id] = new Function(
209
-
"module",
210
-
"exports",
211
-
"require",
212
-
wrapped
213
-
) as WebpackModuleFunc;
214
entry[id].__moonlight = true;
215
}
216
}
···
246
function handleModuleDependencies() {
247
const modules = Array.from(webpackModules.values());
248
249
-
const dependencies: Dependency<string, IdentifiedWebpackModule>[] =
250
-
modules.map((wp) => {
251
-
return {
252
-
id: depToString(wp),
253
-
data: wp
254
-
};
255
-
});
256
257
const [sorted, _] = calculateDependencies(dependencies, {
258
fetchDep: (id) => {
···
263
const deps = item.data?.dependencies ?? [];
264
return (
265
deps.filter(
266
-
(dep) =>
267
-
!(dep instanceof RegExp || typeof dep === "string") &&
268
-
dep.ext != null
269
) as ExplicitExtensionDependency[]
270
).map(depToString);
271
}
···
297
if (dep.test(modStr)) deps.delete(dep);
298
} else if (
299
dep.ext != null
300
-
? injectedWpModules.find(
301
-
(x) => x.ext === dep.ext && x.id === dep.id
302
-
)
303
: injectedWpModules.find((x) => x.id === dep.id)
304
) {
305
deps.delete(dep);
306
}
307
}
308
309
if (deps.size !== 0) {
310
-
wpModule.dependencies = Array.from(deps);
311
continue;
312
}
313
-
314
-
wpModule.dependencies = Array.from(deps);
315
}
316
}
317
···
324
if (wpModule.run) {
325
modules[id] = wpModule.run;
326
wpModule.run.__moonlight = true;
327
}
328
-
if (wpModule.entrypoint) entrypoints.push(id);
329
}
330
if (!webpackModules.size) break;
331
}
332
333
-
for (const [name, func] of Object.entries(
334
-
moonlight.moonmap.getWebpackModules("window.moonlight.moonmap")
335
-
)) {
336
injectedWpModules.push({ id: name, run: func });
337
modules[name] = func;
338
inject = true;
···
349
window.webpackChunkdiscord_app.push([
350
[--chunkId],
351
modules,
352
-
(require: typeof WebpackRequire) => entrypoints.map(require)
353
]);
354
}
355
}
···
399
const realPush = jsonp.push;
400
if (jsonp.push.__moonlight !== true) {
401
jsonp.push = (items) => {
402
-
moonlight.events.dispatchEvent(EventType.ChunkLoad, {
403
chunkId: items[0],
404
modules: items[1],
405
require: items[2]
···
447
set(modules: any) {
448
const { stack } = new Error();
449
if (stack!.includes("/assets/") && !Array.isArray(modules)) {
450
-
moonlight.events.dispatchEvent(EventType.ChunkLoad, {
451
modules: modules
452
});
453
patchModules(modules);
454
455
-
if (!window.webpackChunkdiscord_app)
456
-
window.webpackChunkdiscord_app = [];
457
injectModules(modules);
458
}
459
···
11
} from "@moonlight-mod/types";
12
import Logger from "./util/logger";
13
import calculateDependencies, { Dependency } from "./util/dependency";
14
+
import { WebEventType } from "@moonlight-mod/types/core/event";
15
+
import { processFind, processReplace, testFind } from "./util/patch";
16
17
const logger = new Logger("core/patch");
18
···
21
let webpackModules: Set<IdentifiedWebpackModule> = new Set();
22
let webpackRequire: WebpackRequireType | null = null;
23
24
+
const moduleLoadSubscriptions: Map<string, ((moduleId: string) => void)[]> = new Map();
25
26
export function registerPatch(patch: IdentifiedPatch) {
27
+
patch.find = processFind(patch.find);
28
+
processReplace(patch.replace);
29
+
30
patches.push(patch);
31
moonlight.unpatched.add(patch);
32
}
···
38
}
39
}
40
41
+
export function onModuleLoad(module: string | string[], callback: (moduleId: string) => void): void {
42
let moduleIds = module;
43
44
if (typeof module === "string") {
···
66
const moduleCache: Record<string, string> = {};
67
const patched: Record<string, Array<string>> = {};
68
69
+
function createSourceURL(id: string) {
70
+
const remapped = Object.entries(moonlight.moonmap.modules).find((m) => m[1] === id)?.[0];
71
+
72
+
if (remapped) {
73
+
return `// Webpack Module: ${id}\n//# sourceURL=${remapped}`;
74
+
}
75
+
76
+
return `//# sourceURL=Webpack-Module/${id.slice(0, 3)}/${id}`;
77
+
}
78
+
79
+
function patchModule(id: string, patchId: string, replaced: string, entry: WebpackJsonpEntry[1]) {
80
+
// Store what extensions patched what modules for easier debugging
81
+
patched[id] = patched[id] ?? [];
82
+
patched[id].push(patchId);
83
84
+
// Webpack module arguments are minified, so we replace them with consistent names
85
+
// We have to wrap it so things don't break, though
86
+
const patchedStr = patched[id].sort().join(", ");
87
88
+
const wrapped =
89
+
`(${replaced}).apply(this, arguments)\n` + `// Patched by moonlight: ${patchedStr}\n` + createSourceURL(id);
90
91
+
try {
92
+
const func = new Function("module", "exports", "require", wrapped) as WebpackModuleFunc;
93
+
entry[id] = func;
94
+
entry[id].__moonlight = true;
95
+
return true;
96
+
} catch (e) {
97
+
logger.warn("Error constructing function for patch", patchId, e);
98
+
patched[id].pop();
99
+
return false;
100
}
101
+
}
102
103
+
function patchModules(entry: WebpackJsonpEntry[1]) {
104
// Populate the module cache
105
for (const [id, func] of Object.entries(entry)) {
106
if (!Object.hasOwn(moduleCache, id) && func.__moonlight !== true) {
···
111
112
for (const [id, func] of Object.entries(entry)) {
113
if (func.__moonlight === true) continue;
114
+
115
+
// Clone the module string so finds don't get messed up by other extensions
116
+
const origModuleString = moduleCache[id];
117
+
let moduleString = origModuleString;
118
+
const patchedStr = [];
119
+
const mappedName = Object.entries(moonlight.moonmap.modules).find((m) => m[1] === id)?.[0];
120
+
let modified = false;
121
+
let swappedModule = false;
122
+
123
+
const exts = new Set<string>();
124
125
for (let i = 0; i < patches.length; i++) {
126
const patch = patches[i];
127
if (patch.prerequisite != null && !patch.prerequisite()) {
128
+
moonlight.unpatched.delete(patch);
129
continue;
130
}
131
···
134
patch.find.lastIndex = 0;
135
}
136
137
+
const match = testFind(origModuleString, patch.find) || patch.find === mappedName;
138
139
// Global regexes apply to all modules
140
+
const shouldRemove = typeof patch.find === "string" ? true : !patch.find.global;
141
142
+
let replaced = moduleString;
143
+
let hardFailed = false;
144
if (match) {
145
+
// We ensured normal PatchReplace objects get turned into arrays on register
146
+
const replaces = patch.replace as PatchReplace[];
147
148
+
let isPatched = true;
149
+
for (let i = 0; i < replaces.length; i++) {
150
+
const replace = replaces[i];
151
+
let patchId = `${patch.ext}#${patch.id}`;
152
+
if (replaces.length > 1) patchId += `#${i}`;
153
+
patchedStr.push(patchId);
154
155
+
if (replace.type === undefined || replace.type === PatchReplaceType.Normal) {
156
+
// tsc fails to detect the overloads for this, so I'll just do this
157
+
// Verbose, but it works
158
+
if (typeof replace.replacement === "string") {
159
+
replaced = replaced.replace(replace.match, replace.replacement);
160
+
} else {
161
+
replaced = replaced.replace(replace.match, replace.replacement);
162
+
}
163
164
+
if (replaced === moduleString) {
165
+
logger.warn("Patch replacement failed", id, patchId, patch);
166
+
isPatched = false;
167
+
if (patch.hardFail) {
168
+
hardFailed = true;
169
+
break;
170
+
} else {
171
+
continue;
172
+
}
173
+
}
174
+
} else if (replace.type === PatchReplaceType.Module) {
175
+
// Directly replace the module with a new one
176
+
const newModule = replace.replacement(replaced);
177
+
entry[id] = newModule;
178
+
entry[id].__moonlight = true;
179
+
replaced = newModule.toString().replace(/\n/g, "");
180
+
swappedModule = true;
181
}
182
}
183
184
+
if (!hardFailed) {
185
+
moduleString = replaced;
186
+
modified = true;
187
+
exts.add(patch.ext);
188
}
189
+
190
+
if (isPatched) moonlight.unpatched.delete(patch);
191
+
if (shouldRemove) patches.splice(i--, 1);
192
}
193
}
194
195
+
if (modified) {
196
+
let shouldCache = true;
197
+
if (!swappedModule) shouldCache = patchModule(id, patchedStr.join(", "), moduleString, entry);
198
+
if (shouldCache) moduleCache[id] = moduleString;
199
+
moonlight.patched.set(id, exts);
200
+
}
201
202
try {
203
const parsed = moonlight.lunast.parseScript(id, moduleString);
204
if (parsed != null) {
205
for (const [parsedId, parsedScript] of Object.entries(parsed)) {
206
+
if (patchModule(parsedId, "lunast", parsedScript, entry)) {
207
moduleCache[parsedId] = parsedScript;
208
}
209
}
···
213
}
214
215
if (moonlightNode.config.patchAll === true) {
216
+
if ((typeof id !== "string" || !id.includes("_")) && !entry[id].__moonlight) {
217
+
const wrapped = `(${moduleCache[id]}).apply(this, arguments)\n` + createSourceURL(id);
218
+
entry[id] = new Function("module", "exports", "require", wrapped) as WebpackModuleFunc;
219
entry[id].__moonlight = true;
220
}
221
}
···
251
function handleModuleDependencies() {
252
const modules = Array.from(webpackModules.values());
253
254
+
const dependencies: Dependency<string, IdentifiedWebpackModule>[] = modules.map((wp) => {
255
+
return {
256
+
id: depToString(wp),
257
+
data: wp
258
+
};
259
+
});
260
261
const [sorted, _] = calculateDependencies(dependencies, {
262
fetchDep: (id) => {
···
267
const deps = item.data?.dependencies ?? [];
268
return (
269
deps.filter(
270
+
(dep) => !(dep instanceof RegExp || typeof dep === "string") && dep.ext != null
271
) as ExplicitExtensionDependency[]
272
).map(depToString);
273
}
···
299
if (dep.test(modStr)) deps.delete(dep);
300
} else if (
301
dep.ext != null
302
+
? injectedWpModules.find((x) => x.ext === dep.ext && x.id === dep.id)
303
: injectedWpModules.find((x) => x.id === dep.id)
304
) {
305
deps.delete(dep);
306
}
307
}
308
309
+
wpModule.dependencies = Array.from(deps);
310
if (deps.size !== 0) {
311
continue;
312
}
313
}
314
}
315
···
322
if (wpModule.run) {
323
modules[id] = wpModule.run;
324
wpModule.run.__moonlight = true;
325
+
// @ts-expect-error hacks
326
+
wpModule.run.call = function (self, module, exports, require) {
327
+
try {
328
+
wpModule.run!.apply(self, [module, exports, require]);
329
+
} catch (err) {
330
+
logger.error(`Failed to run module "${id}":`, err);
331
+
}
332
+
};
333
+
if (wpModule.entrypoint) entrypoints.push(id);
334
}
335
}
336
if (!webpackModules.size) break;
337
}
338
339
+
for (const [name, func] of Object.entries(moonlight.moonmap.getWebpackModules("window.moonlight.moonmap"))) {
340
+
// @ts-expect-error probably should fix the type on this idk
341
+
func.__moonlight = true;
342
injectedWpModules.push({ id: name, run: func });
343
modules[name] = func;
344
inject = true;
···
355
window.webpackChunkdiscord_app.push([
356
[--chunkId],
357
modules,
358
+
(require: WebpackRequireType) =>
359
+
entrypoints.map((id) => {
360
+
try {
361
+
if (require.m[id] == null) {
362
+
logger.error(`Failing to load entrypoint module "${id}" because it's not found in Webpack.`);
363
+
} else {
364
+
require(id);
365
+
}
366
+
} catch (err) {
367
+
logger.error(`Failed to load entrypoint module "${id}":`, err);
368
+
}
369
+
})
370
]);
371
}
372
}
···
416
const realPush = jsonp.push;
417
if (jsonp.push.__moonlight !== true) {
418
jsonp.push = (items) => {
419
+
moonlight.events.dispatchEvent(WebEventType.ChunkLoad, {
420
chunkId: items[0],
421
modules: items[1],
422
require: items[2]
···
464
set(modules: any) {
465
const { stack } = new Error();
466
if (stack!.includes("/assets/") && !Array.isArray(modules)) {
467
+
moonlight.events.dispatchEvent(WebEventType.ChunkLoad, {
468
modules: modules
469
});
470
patchModules(modules);
471
472
+
if (!window.webpackChunkdiscord_app) window.webpackChunkdiscord_app = [];
473
injectModules(modules);
474
}
475
+2
-12
packages/core/src/persist.ts
+2
-12
packages/core/src/persist.ts
···
1
import { join, dirname } from "node:path";
2
-
import {
3
-
mkdirSync,
4
-
renameSync,
5
-
existsSync,
6
-
copyFileSync,
7
-
readdirSync
8
-
} from "node:fs";
9
import Logger from "./util/logger";
10
11
const logger = new Logger("core/persist");
···
31
if (event === "host-updated") {
32
const versions = this.queryCurrentVersionsSync();
33
34
-
const newRootDir = join(
35
-
this.rootPath,
36
-
"app-" +
37
-
versions.current_host.map((v: number) => v.toString()).join(".")
38
-
);
39
logger.info(`Persisting moonlight - new root dir: ${newRootDir}`);
40
41
const newResources = join(newRootDir, "resources");
···
1
import { join, dirname } from "node:path";
2
+
import { mkdirSync, renameSync, existsSync, copyFileSync, readdirSync } from "node:fs";
3
import Logger from "./util/logger";
4
5
const logger = new Logger("core/persist");
···
25
if (event === "host-updated") {
26
const versions = this.queryCurrentVersionsSync();
27
28
+
const newRootDir = join(this.rootPath, "app-" + versions.current_host.map((v: number) => v.toString()).join("."));
29
logger.info(`Persisting moonlight - new root dir: ${newRootDir}`);
30
31
const newResources = join(newRootDir, "resources");
+1
-4
packages/core/src/util/binary.ts
+1
-4
packages/core/src/util/binary.ts
+39
packages/core/src/util/config.ts
+39
packages/core/src/util/config.ts
···
···
1
+
import type { Config, DetectedExtension, ExtensionManifest } from "@moonlight-mod/types";
2
+
3
+
export function getManifest(extensions: DetectedExtension[], ext: string) {
4
+
return extensions.find((x) => x.id === ext)?.manifest;
5
+
}
6
+
7
+
export function getConfig(ext: string, config: Config) {
8
+
const val = config.extensions[ext];
9
+
if (val == null || typeof val === "boolean") return undefined;
10
+
return val.config;
11
+
}
12
+
13
+
export function getConfigOption<T>(
14
+
ext: string,
15
+
key: string,
16
+
config: Config,
17
+
settings?: ExtensionManifest["settings"]
18
+
): T | undefined {
19
+
const defaultValue: T | undefined = structuredClone(settings?.[key]?.default);
20
+
const cfg = getConfig(ext, config);
21
+
if (cfg == null || typeof cfg === "boolean") return defaultValue;
22
+
return cfg?.[key] ?? defaultValue;
23
+
}
24
+
25
+
export function setConfigOption<T>(config: Config, ext: string, key: string, value: T) {
26
+
const oldConfig = config.extensions[ext];
27
+
const newConfig =
28
+
typeof oldConfig === "boolean"
29
+
? {
30
+
enabled: oldConfig,
31
+
config: { [key]: value }
32
+
}
33
+
: {
34
+
...oldConfig,
35
+
config: { ...(oldConfig?.config ?? {}), [key]: value }
36
+
};
37
+
38
+
config.extensions[ext] = newConfig;
39
+
}
+10
-15
packages/core/src/util/data.ts
+10
-15
packages/core/src/util/data.ts
···
16
appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData);
17
}
18
19
-
const dir = moonlightFS.join(appData, "moonlight-mod");
20
-
if (!(await moonlightFS.exists(dir))) await moonlightFS.mkdir(dir);
21
22
return dir;
23
}
···
36
37
let configPath = "";
38
39
-
const buildInfoPath = moonlightFS.join(
40
-
process.resourcesPath,
41
-
"build_info.json"
42
-
);
43
-
if (!(await moonlightFS.exists(buildInfoPath))) {
44
-
configPath = moonlightFS.join(dir, "desktop.json");
45
} else {
46
-
const buildInfo: BuildInfo = JSON.parse(
47
-
await moonlightFS.readFileString(buildInfoPath)
48
-
);
49
-
configPath = moonlightFS.join(dir, buildInfo.releaseChannel + ".json");
50
}
51
52
return configPath;
···
55
async function getPathFromMoonlight(...names: string[]) {
56
const dir = await getMoonlightDir();
57
58
-
const target = moonlightFS.join(dir, ...names);
59
-
if (!(await moonlightFS.exists(target))) await moonlightFS.mkdir(target);
60
61
return target;
62
}
···
66
}
67
68
export function getCoreExtensionsPath(): string {
69
-
return moonlightFS.join(__dirname, constants.coreExtensionsDir);
70
}
···
16
appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData);
17
}
18
19
+
const dir = moonlightNodeSandboxed.fs.join(appData, "moonlight-mod");
20
+
if (!(await moonlightNodeSandboxed.fs.exists(dir))) await moonlightNodeSandboxed.fs.mkdir(dir);
21
22
return dir;
23
}
···
36
37
let configPath = "";
38
39
+
const buildInfoPath = moonlightNodeSandboxed.fs.join(process.resourcesPath, "build_info.json");
40
+
if (!(await moonlightNodeSandboxed.fs.exists(buildInfoPath))) {
41
+
configPath = moonlightNodeSandboxed.fs.join(dir, "desktop.json");
42
} else {
43
+
const buildInfo: BuildInfo = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(buildInfoPath));
44
+
configPath = moonlightNodeSandboxed.fs.join(dir, buildInfo.releaseChannel + ".json");
45
}
46
47
return configPath;
···
50
async function getPathFromMoonlight(...names: string[]) {
51
const dir = await getMoonlightDir();
52
53
+
const target = moonlightNodeSandboxed.fs.join(dir, ...names);
54
+
if (!(await moonlightNodeSandboxed.fs.exists(target))) await moonlightNodeSandboxed.fs.mkdir(target);
55
56
return target;
57
}
···
61
}
62
63
export function getCoreExtensionsPath(): string {
64
+
return moonlightNodeSandboxed.fs.join(__dirname, constants.coreExtensionsDir);
65
}
+4
-14
packages/core/src/util/dependency.ts
+4
-14
packages/core/src/util/dependency.ts
···
35
const fullDeps: Set<T> = new Set();
36
let failed = false;
37
38
-
// eslint-disable-next-line no-inner-declarations
39
function resolveDeps(id: T, root: boolean) {
40
if (id === item.id && !root) {
41
logger.warn(`Circular dependency detected: "${item.id}"`);
···
113
logger.trace("Enabled stage", itemsOrig);
114
const implicitlyEnabled: T[] = [];
115
116
-
// eslint-disable-next-line no-inner-declarations
117
function validateDeps(dep: Dependency<T, D>) {
118
if (getEnabled!(dep)) {
119
const deps = dependencyGraphOrig.get(dep.id)!;
···
122
validateDeps({ id, data });
123
}
124
} else {
125
-
const dependsOnMe = Array.from(dependencyGraphOrig.entries()).filter(
126
-
([, v]) => v?.has(dep.id)
127
-
);
128
129
if (dependsOnMe.length > 0) {
130
logger.debug("Implicitly enabling dependency", dep.id);
···
134
}
135
136
for (const dep of itemsOrig) validateDeps(dep);
137
-
itemsOrig = itemsOrig.filter(
138
-
(x) => getEnabled(x) || implicitlyEnabled.includes(x.id)
139
-
);
140
}
141
142
if (getIncompatible != null) {
···
176
dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id)));
177
}
178
179
-
while (
180
-
Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0
181
-
) {
182
-
const noDependents = items.filter(
183
-
(e) => dependencyGraph.get(e.id)?.size === 0
184
-
);
185
186
if (noDependents.length === 0) {
187
logger.warn("Stuck dependency graph detected", dependencyGraph);
···
35
const fullDeps: Set<T> = new Set();
36
let failed = false;
37
38
function resolveDeps(id: T, root: boolean) {
39
if (id === item.id && !root) {
40
logger.warn(`Circular dependency detected: "${item.id}"`);
···
112
logger.trace("Enabled stage", itemsOrig);
113
const implicitlyEnabled: T[] = [];
114
115
function validateDeps(dep: Dependency<T, D>) {
116
if (getEnabled!(dep)) {
117
const deps = dependencyGraphOrig.get(dep.id)!;
···
120
validateDeps({ id, data });
121
}
122
} else {
123
+
const dependsOnMe = Array.from(dependencyGraphOrig.entries()).filter(([, v]) => v?.has(dep.id));
124
125
if (dependsOnMe.length > 0) {
126
logger.debug("Implicitly enabling dependency", dep.id);
···
130
}
131
132
for (const dep of itemsOrig) validateDeps(dep);
133
+
itemsOrig = itemsOrig.filter((x) => getEnabled(x) || implicitlyEnabled.includes(x.id));
134
}
135
136
if (getIncompatible != null) {
···
170
dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id)));
171
}
172
173
+
while (Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0) {
174
+
const noDependents = items.filter((e) => dependencyGraph.get(e.id)?.size === 0);
175
176
if (noDependents.length === 0) {
177
logger.warn("Stuck dependency graph detected", dependencyGraph);
+7
-27
packages/core/src/util/event.ts
+7
-27
packages/core/src/util/event.ts
···
9
const listeners = new Map<(data: EventData) => void, (e: Event) => void>();
10
11
return {
12
-
dispatchEvent: <Id extends keyof EventData>(
13
-
id: Id,
14
-
data: EventData[Id]
15
-
) => {
16
-
eventEmitter.dispatchEvent(
17
-
new CustomEvent(id as string, { detail: data })
18
-
);
19
},
20
21
-
addEventListener: <Id extends keyof EventData>(
22
-
id: Id,
23
-
cb: (data: EventData[Id]) => void
24
-
) => {
25
const untyped = cb as (data: EventData) => void;
26
if (listeners.has(untyped)) return;
27
···
34
eventEmitter.addEventListener(id as string, listener);
35
},
36
37
-
removeEventListener: <Id extends keyof EventData>(
38
-
id: Id,
39
-
cb: (data: EventData[Id]) => void
40
-
) => {
41
const untyped = cb as (data: EventData) => void;
42
const listener = listeners.get(untyped);
43
if (listener == null) return;
···
53
const listeners = new Map<(data: EventData) => void, (e: Event) => void>();
54
55
return {
56
-
dispatchEvent: <Id extends keyof EventData>(
57
-
id: Id,
58
-
data: EventData[Id]
59
-
) => {
60
eventEmitter.emit(id as string, data);
61
},
62
63
-
addEventListener: <Id extends keyof EventData>(
64
-
id: Id,
65
-
cb: (data: EventData[Id]) => void
66
-
) => {
67
const untyped = cb as (data: EventData) => void;
68
if (listeners.has(untyped)) return;
69
···
76
eventEmitter.on(id as string, listener);
77
},
78
79
-
removeEventListener: <Id extends keyof EventData>(
80
-
id: Id,
81
-
cb: (data: EventData[Id]) => void
82
-
) => {
83
const untyped = cb as (data: EventData) => void;
84
const listener = listeners.get(untyped);
85
if (listener == null) return;
···
9
const listeners = new Map<(data: EventData) => void, (e: Event) => void>();
10
11
return {
12
+
dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => {
13
+
eventEmitter.dispatchEvent(new CustomEvent(id as string, { detail: data }));
14
},
15
16
+
addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
17
const untyped = cb as (data: EventData) => void;
18
if (listeners.has(untyped)) return;
19
···
26
eventEmitter.addEventListener(id as string, listener);
27
},
28
29
+
removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
30
const untyped = cb as (data: EventData) => void;
31
const listener = listeners.get(untyped);
32
if (listener == null) return;
···
42
const listeners = new Map<(data: EventData) => void, (e: Event) => void>();
43
44
return {
45
+
dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => {
46
eventEmitter.emit(id as string, data);
47
},
48
49
+
addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
50
const untyped = cb as (data: EventData) => void;
51
if (listeners.has(untyped)) return;
52
···
59
eventEmitter.on(id as string, listener);
60
},
61
62
+
removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
63
const untyped = cb as (data: EventData) => void;
64
const listener = listeners.get(untyped);
65
if (listener == null) return;
+3
-5
packages/core/src/util/import.ts
+3
-5
packages/core/src/util/import.ts
···
9
cemented if import is passed a string literal.
10
*/
11
12
-
const canRequire = ["path", "fs"] as const;
13
-
type CanRequire = (typeof canRequire)[number];
14
15
type ImportTypes = {
16
path: typeof import("path");
17
fs: typeof import("fs");
18
};
19
20
-
export default function requireImport<T extends CanRequire>(
21
-
type: T
22
-
): Awaited<ImportTypes[T]> {
23
return require(type);
24
}
···
9
cemented if import is passed a string literal.
10
*/
11
12
+
const _canRequire = ["path", "fs"] as const;
13
+
type CanRequire = (typeof _canRequire)[number];
14
15
type ImportTypes = {
16
path: typeof import("path");
17
fs: typeof import("fs");
18
};
19
20
+
export default function requireImport<T extends CanRequire>(type: T): Awaited<ImportTypes[T]> {
21
return require(type);
22
}
+2
-8
packages/core/src/util/logger.ts
+2
-8
packages/core/src/util/logger.ts
···
50
if (maxLevel > level) return;
51
52
if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) {
53
-
args = [
54
-
`%c[${logLevel}]`,
55
-
`background-color: ${colors[level]}; color: #FFFFFF;`,
56
-
`[${this.name}]`,
57
-
...obj
58
-
];
59
} else {
60
args = [`[${logLevel}]`, `[${this.name}]`, ...obj];
61
}
···
87
88
export function initLogger(config: Config) {
89
if (config.loggerLevel != null) {
90
-
const enumValue =
91
-
LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel];
92
if (enumValue != null) {
93
maxLevel = enumValue;
94
}
···
50
if (maxLevel > level) return;
51
52
if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) {
53
+
args = [`%c[${logLevel}]`, `background-color: ${colors[level]}; color: #FFFFFF;`, `[${this.name}]`, ...obj];
54
} else {
55
args = [`[${logLevel}]`, `[${this.name}]`, ...obj];
56
}
···
82
83
export function initLogger(config: Config) {
84
if (config.loggerLevel != null) {
85
+
const enumValue = LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel];
86
if (enumValue != null) {
87
maxLevel = enumValue;
88
}
+30
packages/core/src/util/patch.ts
+30
packages/core/src/util/patch.ts
···
···
1
+
import { PatchReplace, PatchReplaceType } from "@moonlight-mod/types";
2
+
3
+
type SingleFind = string | RegExp;
4
+
type Find = SingleFind | SingleFind[];
5
+
6
+
export function processFind<T extends Find>(find: T): T {
7
+
if (Array.isArray(find)) {
8
+
return find.map(processFind) as T;
9
+
} else if (find instanceof RegExp) {
10
+
// Add support for \i to match rspack's minified names
11
+
return new RegExp(find.source.replace(/\\i/g, "[A-Za-z_$][\\w$]*"), find.flags) as T;
12
+
} else {
13
+
return find;
14
+
}
15
+
}
16
+
17
+
export function processReplace(replace: PatchReplace | PatchReplace[]) {
18
+
if (Array.isArray(replace)) {
19
+
replace.forEach(processReplace);
20
+
} else {
21
+
if (replace.type === undefined || replace.type === PatchReplaceType.Normal) {
22
+
replace.match = processFind(replace.match);
23
+
}
24
+
}
25
+
}
26
+
27
+
export function testFind(src: string, find: SingleFind) {
28
+
// indexOf is faster than includes by 0.25% lmao
29
+
return typeof find === "string" ? src.indexOf(find) !== -1 : find.test(src);
30
+
}
+4
-1
packages/core/tsconfig.json
+4
-1
packages/core/tsconfig.json
+9
-1
packages/core-extensions/package.json
+9
-1
packages/core-extensions/package.json
···
1
{
2
"name": "@moonlight-mod/core-extensions",
3
"private": true,
4
+
"engineStrict": true,
5
+
"engines": {
6
+
"node": ">=22",
7
+
"pnpm": ">=10",
8
+
"npm": "pnpm",
9
+
"yarn": "pnpm"
10
+
},
11
"dependencies": {
12
"@moonlight-mod/core": "workspace:*",
13
"@moonlight-mod/types": "workspace:*",
14
+
"microdiff": "catalog:prod",
15
+
"nanotar": "catalog:prod"
16
}
17
}
+19
packages/core-extensions/src/appPanels/index.ts
+19
packages/core-extensions/src/appPanels/index.ts
···
···
1
+
import type { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
2
+
3
+
export const patches: Patch[] = [
4
+
{
5
+
find: 'setProperty("--custom-app-panels-height"',
6
+
replace: [
7
+
{
8
+
match: /\(0,.\.jsx\)\((.\..),{section:/,
9
+
replacement: (prev, el) => `...require("appPanels_appPanels").default.getPanels(${el}),${prev}`
10
+
}
11
+
]
12
+
}
13
+
];
14
+
15
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
16
+
appPanels: {
17
+
dependencies: [{ id: "react" }]
18
+
}
19
+
};
+11
packages/core-extensions/src/appPanels/manifest.json
+11
packages/core-extensions/src/appPanels/manifest.json
···
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "appPanels",
4
+
"apiLevel": 2,
5
+
"meta": {
6
+
"name": "App Panels",
7
+
"tagline": "An API for adding panels around the user/voice controls",
8
+
"authors": ["NotNite"],
9
+
"tags": ["library"]
10
+
}
11
+
}
+23
packages/core-extensions/src/appPanels/webpackModules/appPanels.ts
+23
packages/core-extensions/src/appPanels/webpackModules/appPanels.ts
···
···
1
+
import type { AppPanels as AppPanelsType } from "@moonlight-mod/types/coreExtensions/appPanels";
2
+
import React from "@moonlight-mod/wp/react";
3
+
4
+
const panels: Record<string, React.FC<any>> = {};
5
+
6
+
export const AppPanels: AppPanelsType = {
7
+
addPanel(section, element) {
8
+
panels[section] = element;
9
+
},
10
+
getPanels(panel) {
11
+
return Object.entries(panels).map(([section, element]) =>
12
+
React.createElement(
13
+
panel,
14
+
{
15
+
section
16
+
},
17
+
React.createElement(element)
18
+
)
19
+
);
20
+
}
21
+
};
22
+
23
+
export default AppPanels;
+85
packages/core-extensions/src/commands/index.ts
+85
packages/core-extensions/src/commands/index.ts
···
···
1
+
import { Patch, ExtensionWebpackModule } from "@moonlight-mod/types";
2
+
import { APPLICATION_ID } from "@moonlight-mod/types/coreExtensions/commands";
3
+
4
+
export const patches: Patch[] = [
5
+
{
6
+
find: ".fI5MTU)", // COMMAND_SECTION_BUILT_IN_NAME
7
+
replace: [
8
+
// inject commands
9
+
{
10
+
match: /return (\i)\.filter/,
11
+
replacement: (orig, commands) =>
12
+
`return [...${commands},...require("commands_commands").default._getCommands()].filter`
13
+
},
14
+
15
+
// section
16
+
{
17
+
match: /(?<=\i={)(?=\[\i\.\i\.BUILT_IN]:{id:\i\.\i\.BUILT_IN,type:(\i.\i\.BUILT_IN))/,
18
+
replacement: (_, type) =>
19
+
`"${APPLICATION_ID}":{id:"${APPLICATION_ID}",type:${type},get name(){return "moonlight"}},`
20
+
}
21
+
]
22
+
},
23
+
24
+
// index our section
25
+
{
26
+
find: '"ApplicationCommandIndexStore"',
27
+
replace: {
28
+
match: /(?<=let \i=(\i)\((\i\.\i)\[\i\.\i\.BUILT_IN\],(\i),!0,!0,(\i)\);)null!=(\i)&&(\i)\.push\(\i\)/,
29
+
replacement: (_, createSection, sections, deny, props, section, commands) =>
30
+
`null!=${section}&&(${section}.data=${section}.data.filter(c=>c.applicationId=="-1"));
31
+
null!=${section}&&${commands}.push(${section});
32
+
const moonlightCommands=${createSection}(${sections}["${APPLICATION_ID}"],${deny},!0,!0,${props});
33
+
null!=moonlightCommands&&(moonlightCommands.data=moonlightCommands.data.filter(c=>c.applicationId=="${APPLICATION_ID}"));
34
+
null!=moonlightCommands&&${commands}.push(moonlightCommands)`
35
+
}
36
+
},
37
+
38
+
// grab legacy commands (needed for adding actions that act like sed/plus reacting)
39
+
{
40
+
find: "={tts:{action:",
41
+
replace: {
42
+
match: /Object\.setPrototypeOf\((\i),null\)/,
43
+
replacement: (_, legacyCommands) => `require("commands_commands")._getLegacyCommands(${legacyCommands})`
44
+
}
45
+
},
46
+
47
+
// add icon
48
+
{
49
+
find: ",hasSpaceTerminator:",
50
+
replace: {
51
+
match: /(\i)\.type===/,
52
+
replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}`
53
+
}
54
+
},
55
+
{
56
+
find: ".icon,bot:null==",
57
+
replace: {
58
+
match: /(\.useMemo\(\(\)=>{(var \i;)?)((return |if\()(\i)\.type)/,
59
+
replacement: (_, before, beforeVar, after, afterIf, section) => `${before}
60
+
if (${section}.id==="${APPLICATION_ID}") return "https://moonlight-mod.github.io/favicon.png";
61
+
${after}`
62
+
}
63
+
},
64
+
// fix icon sizing because they expect built in to be 24 and others to be 32
65
+
{
66
+
find: ".builtInSeparator}):null]",
67
+
replace: {
68
+
match: /(\i)\.type===\i\.\i\.BUILT_IN/,
69
+
replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}`
70
+
}
71
+
},
72
+
73
+
// tell it this app id is authorized
74
+
{
75
+
find: /let{customInstallUrl:\i,installParams:\i,integrationTypesConfig:\i}/,
76
+
replace: {
77
+
match: /\|\|(\i)===\i\.\i\.BUILT_IN/,
78
+
replacement: (orig, id) => `${orig}||${id}==="${APPLICATION_ID}"`
79
+
}
80
+
}
81
+
];
82
+
83
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
84
+
commands: {}
85
+
};
+11
packages/core-extensions/src/commands/manifest.json
+11
packages/core-extensions/src/commands/manifest.json
+71
packages/core-extensions/src/commands/webpackModules/commands.ts
+71
packages/core-extensions/src/commands/webpackModules/commands.ts
···
···
1
+
import {
2
+
APPLICATION_ID,
3
+
Commands,
4
+
LegacyCommand,
5
+
RegisteredCommand
6
+
} from "@moonlight-mod/types/coreExtensions/commands";
7
+
8
+
type LegacyCommands = Record<string, LegacyCommand>;
9
+
let legacyCommands: LegacyCommands | undefined;
10
+
let queuedLegacyCommands: Record<string, LegacyCommand> | null = {};
11
+
12
+
const registeredCommands: RegisteredCommand[] = [];
13
+
14
+
export function _getLegacyCommands(commands: LegacyCommands) {
15
+
legacyCommands = commands;
16
+
if (queuedLegacyCommands != null) {
17
+
for (const [key, value] of Object.entries(queuedLegacyCommands)) {
18
+
legacyCommands[key] = value;
19
+
}
20
+
queuedLegacyCommands = null;
21
+
}
22
+
}
23
+
24
+
export const commands: Commands = {
25
+
registerCommand(command) {
26
+
const registered: RegisteredCommand = {
27
+
...command,
28
+
untranslatedName: command.id,
29
+
displayName: command.id,
30
+
applicationId: APPLICATION_ID,
31
+
untranslatedDescription: command.description,
32
+
displayDescription: command.description,
33
+
options: command.options?.map((o) => ({
34
+
...o,
35
+
displayName: o.name,
36
+
displayDescription: o.description
37
+
}))
38
+
};
39
+
registeredCommands.push(registered);
40
+
},
41
+
42
+
registerLegacyCommand(id, command) {
43
+
if (command.match) {
44
+
if (command.match instanceof RegExp) {
45
+
command.match = this.anyScopeRegex(command.match);
46
+
} else if (command.match.regex && typeof command.match !== "function") {
47
+
command.match = this.anyScopeRegex(command.match.regex);
48
+
}
49
+
}
50
+
51
+
if (!legacyCommands) {
52
+
queuedLegacyCommands![id] = command;
53
+
} else {
54
+
legacyCommands[id] = command;
55
+
}
56
+
},
57
+
58
+
anyScopeRegex(regex) {
59
+
const out = function (str: string) {
60
+
return regex.exec(str);
61
+
};
62
+
out.regex = regex;
63
+
return out;
64
+
},
65
+
66
+
_getCommands() {
67
+
return [...registeredCommands];
68
+
}
69
+
};
70
+
71
+
export default commands;
+7
-5
packages/core-extensions/src/common/index.ts
+7
-5
packages/core-extensions/src/common/index.ts
···
2
3
export const webpackModules: ExtensionWebExports["webpackModules"] = {
4
stores: {
5
+
dependencies: [{ id: "discord/packages/flux" }]
6
+
},
7
+
ErrorBoundary: {
8
+
dependencies: [{ id: "react" }]
9
+
},
10
+
icons: {
11
+
dependencies: [{ id: "react" }, { id: "discord/components/common/index" }]
12
}
13
};
+2
-1
packages/core-extensions/src/common/manifest.json
+2
-1
packages/core-extensions/src/common/manifest.json
+27
packages/core-extensions/src/common/style.css
+27
packages/core-extensions/src/common/style.css
···
···
1
+
.moonlight-error-boundary {
2
+
margin: 0 0 15px;
3
+
padding: 10px;
4
+
border-radius: 5px;
5
+
font-size: 1rem;
6
+
font-weight: 300;
7
+
line-height: 22px;
8
+
color: var(--text-normal, white);
9
+
background: hsl(var(--red-400-hsl) / 0.1);
10
+
border: 2px solid hsl(var(--red-400-hsl) / 0.5);
11
+
12
+
.theme-light & {
13
+
color: var(--text-normal, black) !important;
14
+
}
15
+
16
+
& > h3 {
17
+
margin-bottom: 0.25rem;
18
+
}
19
+
20
+
& > .hljs {
21
+
background: var(--background-secondary);
22
+
border: 1px solid var(--background-tertiary);
23
+
white-space: pre-wrap;
24
+
font-family: var(--font-code);
25
+
user-select: text;
26
+
}
27
+
}
+47
packages/core-extensions/src/common/webpackModules/ErrorBoundary.tsx
+47
packages/core-extensions/src/common/webpackModules/ErrorBoundary.tsx
···
···
1
+
import React from "@moonlight-mod/wp/react";
2
+
import { ErrorBoundaryProps, ErrorBoundaryState } from "@moonlight-mod/types/coreExtensions/common";
3
+
4
+
const logger = moonlight.getLogger("ErrorBoundary");
5
+
6
+
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
7
+
constructor(props: ErrorBoundaryProps) {
8
+
super(props);
9
+
this.state = {
10
+
errored: false,
11
+
error: undefined,
12
+
componentStack: undefined
13
+
};
14
+
}
15
+
16
+
static getDerivedStateFromError(error: Error) {
17
+
return {
18
+
errored: true,
19
+
error
20
+
};
21
+
}
22
+
23
+
componentDidCatch(error: Error, { componentStack }: { componentStack: string }) {
24
+
logger.error(`${error}\n\nComponent stack:\n${componentStack}`);
25
+
this.setState({ error, componentStack });
26
+
}
27
+
28
+
render() {
29
+
const { noop, fallback: FallbackComponent, children, message } = this.props;
30
+
const { errored, error, componentStack } = this.state;
31
+
32
+
if (FallbackComponent) return <FallbackComponent children={children} {...this.state} />;
33
+
34
+
if (errored) {
35
+
return noop ? null : (
36
+
<div className={`moonlight-error-boundary`}>
37
+
<h3>{message ?? "An error occurred rendering this component:"}</h3>
38
+
<code className="hljs">{`${error}\n\nComponent stack:\n${componentStack}`}</code>
39
+
</div>
40
+
);
41
+
}
42
+
43
+
return children;
44
+
}
45
+
}
46
+
47
+
export default ErrorBoundary;
+31
packages/core-extensions/src/common/webpackModules/icons.ts
+31
packages/core-extensions/src/common/webpackModules/icons.ts
···
···
1
+
import { Icons, IconSize } from "@moonlight-mod/types/coreExtensions/common";
2
+
import { tokens } from "@moonlight-mod/wp/discord/components/common/index";
3
+
4
+
// This is defined in a Webpack module but we copy it here to be less breakage-prone
5
+
const sizes: Partial<Record<IconSize, number>> = {
6
+
xxs: 12,
7
+
xs: 16,
8
+
sm: 18,
9
+
md: 24,
10
+
lg: 32,
11
+
refresh_sm: 20
12
+
};
13
+
14
+
export const icons: Icons = {
15
+
parseProps(props) {
16
+
// NOTE: var() fallback is non-standard behavior, just for safety reasons
17
+
const color = props?.color ?? tokens?.colors?.["INTERACTIVE_NORMAL"] ?? "var(--interactive-normal)";
18
+
19
+
const size = sizes[props?.size ?? "md"];
20
+
21
+
return {
22
+
// note: this default size is also non-standard behavior, just for safety
23
+
width: size ?? props?.width ?? sizes.md!,
24
+
height: size ?? props?.width ?? sizes.md!,
25
+
26
+
fill: typeof color === "string" ? color : color.css,
27
+
className: props?.colorClass ?? ""
28
+
};
29
+
}
30
+
};
31
+
export default icons;
+83
packages/core-extensions/src/componentEditor/index.ts
+83
packages/core-extensions/src/componentEditor/index.ts
···
···
1
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
2
+
3
+
export const patches: Patch[] = [
4
+
// dm list
5
+
{
6
+
find: ".interactiveSystemDM]:",
7
+
replace: [
8
+
{
9
+
match: /decorators:(\i\.isSystemDM\(\)\?\(0,\i\.jsx\)\(.+?verified:!0}\):null)/,
10
+
replacement: (_, decorators) =>
11
+
`decorators:require("componentEditor_dmList").default._patchDecorators([${decorators}],arguments[0])`
12
+
},
13
+
{
14
+
match: /(?<=selected:\i,)children:\[/,
15
+
replacement: 'children:require("componentEditor_dmList").default._patchItems(['
16
+
},
17
+
{
18
+
match: /(?<=(onMouseDown|nameplate):\i}\))]/,
19
+
replacement: "],arguments[0])"
20
+
}
21
+
],
22
+
hardFail: true
23
+
},
24
+
25
+
// member list
26
+
{
27
+
find: ".lostPermission",
28
+
replace: [
29
+
{
30
+
match: /(?<=\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[\i\(\),.+?\i\(\)])/,
31
+
replacement: (_, decorators) =>
32
+
`children:require("componentEditor_memberList").default._patchDecorators(${decorators},arguments[0])`
33
+
},
34
+
{
35
+
match: /name:null==\i\?\(0,\i\.jsx\)\("span"/,
36
+
replacement: (orig: string) =>
37
+
`children:require("componentEditor_memberList").default._patchItems([],arguments[0]),${orig}`
38
+
}
39
+
]
40
+
},
41
+
42
+
// messages
43
+
{
44
+
find: '},"new-member")),',
45
+
replace: [
46
+
{
47
+
match: /(?<=\.BADGES](=|:))(\i)(;|})/,
48
+
replacement: (_, leading, badges, trailing) =>
49
+
`require("componentEditor_messages").default._patchUsernameBadges(${badges},arguments[0])${trailing}`
50
+
},
51
+
{
52
+
match: /(?<=className:\i,)badges:(\i)/,
53
+
replacement: (_, badges) =>
54
+
`badges:require("componentEditor_messages").default._patchBadges(${badges},arguments[0])`
55
+
},
56
+
{
57
+
match: /(?<=username:\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[.+?])}\),usernameSpanId:/,
58
+
replacement: (_, elements) =>
59
+
`children:require("componentEditor_messages").default._patchUsername(${elements},arguments[0])}),usernameSpanId:`
60
+
}
61
+
]
62
+
},
63
+
{
64
+
find: '.provider&&"Discord"===',
65
+
replace: {
66
+
match: /(?<=\.container\),)children:(\[.+?this\.renderSuppressConfirmModal\(\),.+?\])}\)/,
67
+
replacement: (_, elements) =>
68
+
`children:require("componentEditor_messages").default._patchAccessories(${elements},this.props)})`
69
+
}
70
+
}
71
+
];
72
+
73
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
74
+
dmList: {
75
+
dependencies: [{ id: "react" }]
76
+
},
77
+
memberList: {
78
+
dependencies: [{ id: "react" }]
79
+
},
80
+
messages: {
81
+
dependencies: [{ id: "react" }]
82
+
}
83
+
};
+11
packages/core-extensions/src/componentEditor/manifest.json
+11
packages/core-extensions/src/componentEditor/manifest.json
···
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "componentEditor",
4
+
"apiLevel": 2,
5
+
"meta": {
6
+
"name": "Component Editor",
7
+
"tagline": "A library to add to commonly patched components",
8
+
"authors": ["Cynosphere"],
9
+
"tags": ["library"]
10
+
}
11
+
}
+61
packages/core-extensions/src/componentEditor/webpackModules/dmList.tsx
+61
packages/core-extensions/src/componentEditor/webpackModules/dmList.tsx
···
···
1
+
import {
2
+
DMList,
3
+
DMListItem,
4
+
DMListDecorator,
5
+
DMListAnchorIndicies,
6
+
DMListDecoratorAnchorIndicies
7
+
} from "@moonlight-mod/types/coreExtensions/componentEditor";
8
+
import React from "@moonlight-mod/wp/react";
9
+
10
+
const items: Record<string, DMListItem> = {};
11
+
const decorators: Record<string, DMListDecorator> = {};
12
+
13
+
function addEntries(
14
+
elements: React.ReactNode[],
15
+
entries: Record<string, DMListItem | DMListDecorator>,
16
+
indicies: Partial<Record<keyof typeof DMListAnchorIndicies | keyof typeof DMListDecoratorAnchorIndicies, number>>,
17
+
props: any
18
+
) {
19
+
const originalElements = [...elements];
20
+
for (const [id, entry] of Object.entries(entries)) {
21
+
const component = <entry.component {...props} key={id} />;
22
+
23
+
if (entry.anchor === undefined) {
24
+
if (entry.before) {
25
+
elements.splice(0, 0, component);
26
+
} else {
27
+
elements.push(component);
28
+
}
29
+
} else {
30
+
const index = elements.indexOf(originalElements[indicies[entry.anchor]!]);
31
+
elements.splice(index! + (entry.before ? 0 : 1), 0, component);
32
+
}
33
+
}
34
+
}
35
+
36
+
export const dmList: DMList = {
37
+
addItem(id, component, anchor, before = false) {
38
+
items[id] = {
39
+
component,
40
+
anchor,
41
+
before
42
+
};
43
+
},
44
+
addDecorator(id, component, anchor, before = false) {
45
+
decorators[id] = {
46
+
component,
47
+
anchor,
48
+
before
49
+
};
50
+
},
51
+
_patchItems(elements, props) {
52
+
addEntries(elements, items, DMListAnchorIndicies, props);
53
+
return elements;
54
+
},
55
+
_patchDecorators(elements, props) {
56
+
addEntries(elements, decorators, DMListDecoratorAnchorIndicies, props);
57
+
return elements;
58
+
}
59
+
};
60
+
61
+
export default dmList;
+50
packages/core-extensions/src/componentEditor/webpackModules/memberList.tsx
+50
packages/core-extensions/src/componentEditor/webpackModules/memberList.tsx
···
···
1
+
import {
2
+
MemberList,
3
+
MemberListDecorator,
4
+
MemberListDecoratorAnchorIndicies
5
+
} from "@moonlight-mod/types/coreExtensions/componentEditor";
6
+
import React from "@moonlight-mod/wp/react";
7
+
8
+
const items: Record<string, React.FC<any>> = {};
9
+
const decorators: Record<string, MemberListDecorator> = {};
10
+
11
+
export const memberList: MemberList = {
12
+
addItem(id, component) {
13
+
items[id] = component;
14
+
},
15
+
addDecorator(id, component, anchor, before = false) {
16
+
decorators[id] = {
17
+
component,
18
+
anchor,
19
+
before
20
+
};
21
+
},
22
+
_patchItems(elements, props) {
23
+
for (const [id, Component] of Object.entries(items)) {
24
+
elements.push(<Component {...props} key={id} />);
25
+
}
26
+
27
+
return elements;
28
+
},
29
+
_patchDecorators(elements, props) {
30
+
const originalElements = [...elements];
31
+
for (const [id, entry] of Object.entries(decorators)) {
32
+
const component = <entry.component {...props} key={id} />;
33
+
34
+
if (entry.anchor === undefined) {
35
+
if (entry.before) {
36
+
elements.splice(0, 0, component);
37
+
} else {
38
+
elements.push(component);
39
+
}
40
+
} else {
41
+
const index = elements.indexOf(originalElements[MemberListDecoratorAnchorIndicies[entry.anchor]!]);
42
+
elements.splice(index! + (entry.before ? 0 : 1), 0, component);
43
+
}
44
+
}
45
+
46
+
return elements;
47
+
}
48
+
};
49
+
50
+
export default memberList;
+97
packages/core-extensions/src/componentEditor/webpackModules/messages.tsx
+97
packages/core-extensions/src/componentEditor/webpackModules/messages.tsx
···
···
1
+
import {
2
+
MessageBadge,
3
+
MessageBadgeIndicies,
4
+
Messages,
5
+
MessageUsername,
6
+
MessageUsernameBadge,
7
+
MessageUsernameBadgeIndicies,
8
+
MessageUsernameIndicies
9
+
} from "@moonlight-mod/types/coreExtensions/componentEditor";
10
+
import React from "@moonlight-mod/wp/react";
11
+
12
+
const username: Record<string, MessageUsername> = {};
13
+
const usernameBadges: Record<string, MessageUsernameBadge> = {};
14
+
const badges: Record<string, MessageBadge> = {};
15
+
const accessories: Record<string, React.FC<any>> = {};
16
+
17
+
function addEntries(
18
+
elements: React.ReactNode[],
19
+
entries: Record<string, MessageUsername | MessageUsernameBadge | MessageBadge>,
20
+
indicies: Partial<
21
+
Record<
22
+
| keyof typeof MessageUsernameIndicies
23
+
| keyof typeof MessageUsernameBadgeIndicies
24
+
| keyof typeof MessageBadgeIndicies,
25
+
number
26
+
>
27
+
>,
28
+
props: any
29
+
) {
30
+
const originalElements = [...elements];
31
+
for (const [id, entry] of Object.entries(entries)) {
32
+
const component = <entry.component {...props} key={id} />;
33
+
34
+
if (entry.anchor === undefined) {
35
+
if (entry.before) {
36
+
elements.splice(0, 0, component);
37
+
} else {
38
+
elements.push(component);
39
+
}
40
+
} else {
41
+
const index = elements.indexOf(originalElements[indicies[entry.anchor]!]);
42
+
elements.splice(index! + (entry.before ? 0 : 1), 0, component);
43
+
}
44
+
}
45
+
}
46
+
47
+
function addComponents(elements: React.ReactNode[], components: Record<string, React.FC<any>>, props: any) {
48
+
for (const [id, Component] of Object.entries(components)) {
49
+
const component = <Component {...props} key={id} />;
50
+
elements.push(component);
51
+
}
52
+
}
53
+
54
+
export const messages: Messages = {
55
+
addToUsername(id, component, anchor, before = false) {
56
+
username[id] = {
57
+
component,
58
+
anchor,
59
+
before
60
+
};
61
+
},
62
+
addUsernameBadge(id, component, anchor, before = false) {
63
+
usernameBadges[id] = {
64
+
component,
65
+
anchor,
66
+
before
67
+
};
68
+
},
69
+
addBadge(id, component, anchor, before = false) {
70
+
badges[id] = {
71
+
component,
72
+
anchor,
73
+
before
74
+
};
75
+
},
76
+
addAccessory(id, component) {
77
+
accessories[id] = component;
78
+
},
79
+
_patchUsername(elements, props) {
80
+
addEntries(elements, username, MessageUsernameIndicies, props);
81
+
return elements;
82
+
},
83
+
_patchUsernameBadges(elements, props) {
84
+
addEntries(elements, usernameBadges, MessageUsernameBadgeIndicies, props);
85
+
return elements;
86
+
},
87
+
_patchBadges(elements, props) {
88
+
addEntries(elements, badges, MessageBadgeIndicies, props);
89
+
return elements;
90
+
},
91
+
_patchAccessories(elements, props) {
92
+
addComponents(elements, accessories, props);
93
+
return elements;
94
+
}
95
+
};
96
+
97
+
export default messages;
+5
-14
packages/core-extensions/src/contextMenu/index.tsx
+5
-14
packages/core-extensions/src/contextMenu/index.tsx
···
5
find: "Menu API only allows Items and groups of Items as children.",
6
replace: [
7
{
8
-
match:
9
-
/(?<=let{navId[^}]+?}=(.),(.)=function .\(.\){.+(?=,.=function))/,
10
-
replacement: (_, props, items) =>
11
-
`,__contextMenu=!${props}.__contextMenu_evilMenu&&require("contextMenu_contextMenu")._patchMenu(${props}, ${items})`
12
}
13
]
14
},
···
17
replace: [
18
{
19
match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/,
20
-
replacement: (render) =>
21
-
`require("contextMenu_contextMenu")._saveProps(this,${render})`
22
}
23
]
24
}
···
26
27
export const webpackModules: Record<string, ExtensionWebpackModule> = {
28
contextMenu: {
29
-
dependencies: [
30
-
{ ext: "spacepack", id: "spacepack" },
31
-
"Menu API only allows Items and groups of Items as children."
32
-
]
33
},
34
evilMenu: {
35
-
dependencies: [
36
-
{ ext: "spacepack", id: "spacepack" },
37
-
"Menu API only allows Items and groups of Items as children."
38
-
]
39
}
40
};
···
5
find: "Menu API only allows Items and groups of Items as children.",
6
replace: [
7
{
8
+
match: /(?<=let{navId[^}]+?}=(.),.=).+?(?=,)/,
9
+
replacement: (items, props) => `require("contextMenu_contextMenu")._patchMenu(${props},${items})`
10
}
11
]
12
},
···
15
replace: [
16
{
17
match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/,
18
+
replacement: (render) => `require("contextMenu_contextMenu")._saveProps(this,${render})`
19
}
20
]
21
}
···
23
24
export const webpackModules: Record<string, ExtensionWebpackModule> = {
25
contextMenu: {
26
+
dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."]
27
},
28
evilMenu: {
29
+
dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."]
30
}
31
};
+1
packages/core-extensions/src/contextMenu/manifest.json
+1
packages/core-extensions/src/contextMenu/manifest.json
+32
-31
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
+32
-31
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
···
1
-
import {
2
-
InternalItem,
3
-
MenuElement,
4
-
MenuProps
5
-
} from "@moonlight-mod/types/coreExtensions/contextMenu";
6
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
7
import parser from "@moonlight-mod/wp/contextMenu_evilMenu";
8
9
type Patch = {
10
navId: string;
11
-
item: (
12
-
props: any
13
-
) =>
14
-
| React.ReactComponentElement<MenuElement>
15
-
| React.ReactComponentElement<MenuElement>[];
16
-
anchorId: string;
17
before: boolean;
18
};
19
20
-
export function addItem<T>(
21
-
navId: string,
22
-
item: (
23
-
props: T
24
-
) =>
25
-
| React.ReactComponentElement<MenuElement>
26
-
| React.ReactComponentElement<MenuElement>[],
27
-
anchorId: string,
28
-
before = false
29
-
) {
30
-
patches.push({ navId, item, anchorId, before });
31
}
32
33
-
export const patches: Patch[] = [];
34
-
export function _patchMenu(props: MenuProps, items: InternalItem[]) {
35
const matches = patches.filter((p) => p.navId === props.navId);
36
-
if (!matches.length) return;
37
38
for (const patch of matches) {
39
-
const idx = items.findIndex((i) => i.key === patch.anchorId);
40
if (idx === -1) continue;
41
-
items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps)));
42
}
43
}
44
45
let menuProps: any;
46
-
export function _saveProps(self: any, el: any) {
47
menuProps = el.props;
48
49
const original = self.props.closeContextMenu;
···
55
return el;
56
}
57
58
// Unmangle Menu elements
59
const code =
60
spacepack.require.m[
61
-
spacepack.findByCode(
62
-
"Menu API only allows Items and groups of Items as children."
63
-
)[0].id
64
].toString();
65
66
let MangledMenu;
···
1
+
import { InternalItem, Menu, MenuElement } from "@moonlight-mod/types/coreExtensions/contextMenu";
2
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
3
import parser from "@moonlight-mod/wp/contextMenu_evilMenu";
4
5
+
// NOTE: We originally had item as a function that returned this, but it didn't
6
+
// quite know how to work out the type and thought it was a JSX element (it
7
+
// *technically* was). This has less type safety, but a @ts-expect-error has
8
+
// zero, so it's better than nothing.
9
+
type ReturnType = MenuElement | MenuElement[];
10
+
11
type Patch = {
12
navId: string;
13
+
item: React.FC<any>;
14
+
anchor: string | RegExp;
15
before: boolean;
16
};
17
18
+
function addItem<T = any>(navId: string, item: React.FC<T>, anchor: string | RegExp, before = false) {
19
+
if (anchor instanceof RegExp && anchor.flags.includes("g"))
20
+
throw new Error("anchor regular expression should not be global");
21
+
patches.push({ navId, item, anchor, before });
22
}
23
24
+
const patches: Patch[] = [];
25
+
function _patchMenu(props: React.ComponentProps<Menu>, items: InternalItem[]) {
26
const matches = patches.filter((p) => p.navId === props.navId);
27
+
if (!matches.length) return items;
28
29
for (const patch of matches) {
30
+
const idx = items.findIndex((i) =>
31
+
typeof patch.anchor === "string" ? i.key === patch.anchor : patch.anchor.test(i.key!)
32
+
);
33
if (idx === -1) continue;
34
+
items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps) as ReturnType));
35
}
36
+
37
+
return items;
38
}
39
40
let menuProps: any;
41
+
function _saveProps(self: any, el: any) {
42
menuProps = el.props;
43
44
const original = self.props.closeContextMenu;
···
50
return el;
51
}
52
53
+
module.exports = {
54
+
patches,
55
+
addItem,
56
+
_patchMenu,
57
+
_saveProps
58
+
};
59
+
60
// Unmangle Menu elements
61
+
// spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString();
62
const code =
63
spacepack.require.m[
64
+
spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id
65
].toString();
66
67
let MangledMenu;
+11
-21
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
+11
-21
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
···
1
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
2
3
let code =
4
spacepack.require.m[
5
-
spacepack.findByCode(
6
-
"Menu API only allows Items and groups of Items as children."
7
-
)[0].id
8
].toString();
9
-
code = code.replace(/,.=(?=function .\(.\){.+?,.=function)/, ";return ");
10
-
code = code.replace(/,(?=__contextMenu)/, ";let ");
11
-
const mod = new Function(
12
-
"module",
13
-
"exports",
14
-
"require",
15
-
`(${code}).apply(this, arguments)`
16
-
);
17
const exp: any = {};
18
mod({}, exp, require);
19
-
const Menu = spacepack.findFunctionByStrings(
20
-
exp,
21
-
"Menu API only allows Items and groups of Items as children."
22
-
)!;
23
-
module.exports = (el: any) => {
24
-
return Menu({
25
-
children: el,
26
-
__contextMenu_evilMenu: true
27
-
});
28
-
};
···
1
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
2
3
+
// spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString();
4
let code =
5
spacepack.require.m[
6
+
spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id
7
].toString();
8
+
9
+
const parserSym = code.match(/(?<=_patchMenu\(.,).+?(?=\()/)![0];
10
+
11
+
code = code.replace(/{(.):\(\)=>./, (orig, e) => `{${e}:()=>${parserSym}`);
12
+
const mod = new Function("module", "exports", "require", `(${code}).apply(this, arguments)`);
13
+
14
const exp: any = {};
15
mod({}, exp, require);
16
+
17
+
const parser = spacepack.findFunctionByStrings(exp, "Menu API only allows Items and groups of Items as children.")!;
18
+
module.exports = parser;
+19
packages/core-extensions/src/devToolsExtensions/host.ts
+19
packages/core-extensions/src/devToolsExtensions/host.ts
···
···
1
+
import { app, session } from "electron";
2
+
import { resolve } from "node:path";
3
+
import Logger from "@moonlight-mod/core/util/logger";
4
+
5
+
const logger = new Logger("DevTools Extensions");
6
+
7
+
app.whenReady().then(async () => {
8
+
const paths = moonlightHost.getConfigOption<string[]>("devToolsExtensions", "paths") ?? [];
9
+
10
+
for (const path of paths) {
11
+
const resolved = resolve(path);
12
+
13
+
try {
14
+
await session.defaultSession.loadExtension(resolved);
15
+
} catch (err) {
16
+
logger.error(`Failed to load an extension in "${resolved}":`, err);
17
+
}
18
+
}
19
+
});
+22
packages/core-extensions/src/devToolsExtensions/manifest.json
+22
packages/core-extensions/src/devToolsExtensions/manifest.json
···
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "devToolsExtensions",
4
+
"meta": {
5
+
"name": "DevTools Extensions",
6
+
"tagline": "Loads Chrome extensions into Electron DevTools",
7
+
"authors": [
8
+
"Cynosphere"
9
+
],
10
+
"tags": [
11
+
"development"
12
+
]
13
+
},
14
+
"settings": {
15
+
"paths": {
16
+
"advice": "restart",
17
+
"displayName": "Extension Paths",
18
+
"type": "list"
19
+
}
20
+
},
21
+
"apiLevel": 2
22
+
}
+4
-8
packages/core-extensions/src/disableSentry/host.ts
+4
-8
packages/core-extensions/src/disableSentry/host.ts
···
5
6
if (moonlightHost.asarPath !== "moonlightDesktop") {
7
try {
8
-
const hostSentryPath = require.resolve(
9
-
join(moonlightHost.asarPath, "node_modules", "@sentry", "electron")
10
-
);
11
-
require.cache[hostSentryPath] = new Module(
12
-
hostSentryPath,
13
-
require.cache[require.resolve(moonlightHost.asarPath)]
14
-
);
15
require.cache[hostSentryPath]!.exports = {
16
init: () => {},
17
captureException: () => {},
18
setTag: () => {},
19
-
setUser: () => {}
20
};
21
logger.debug("Stubbed Sentry host side!");
22
} catch (err) {
···
5
6
if (moonlightHost.asarPath !== "moonlightDesktop") {
7
try {
8
+
const hostSentryPath = require.resolve(join(moonlightHost.asarPath, "node_modules", "@sentry", "electron"));
9
+
require.cache[hostSentryPath] = new Module(hostSentryPath, require.cache[require.resolve(moonlightHost.asarPath)]);
10
require.cache[hostSentryPath]!.exports = {
11
init: () => {},
12
captureException: () => {},
13
setTag: () => {},
14
+
setUser: () => {},
15
+
captureMessage: () => {}
16
};
17
logger.debug("Stubbed Sentry host side!");
18
} catch (err) {
+2
-2
packages/core-extensions/src/disableSentry/index.ts
+2
-2
packages/core-extensions/src/disableSentry/index.ts
+5
-1
packages/core-extensions/src/disableSentry/manifest.json
+5
-1
packages/core-extensions/src/disableSentry/manifest.json
···
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
"id": "disableSentry",
4
"apiLevel": 2,
5
"meta": {
···
12
"https://*.sentry.io/*",
13
"https://*.discord.com/error-reporting-proxy/*",
14
"https://discord.com/assets/sentry.*.js",
15
+
"https://*.discord.com/assets/sentry.*.js",
16
+
"https://*.discordapp.com/error-reporting-proxy/*",
17
+
"https://discordapp.com/assets/sentry.*.js",
18
+
"https://*.discordapp.com/assets/sentry.*.js"
19
]
20
}
+13
-19
packages/core-extensions/src/disableSentry/node.ts
+13
-19
packages/core-extensions/src/disableSentry/node.ts
···
5
6
const logger = moonlightNode.getLogger("disableSentry");
7
8
-
if (!ipcRenderer.sendSync(constants.ipcGetIsMoonlightDesktop)) {
9
-
const preloadPath = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
10
-
try {
11
-
const sentryPath = require.resolve(
12
-
resolve(preloadPath, "..", "node_modules", "@sentry", "electron")
13
-
);
14
-
require.cache[sentryPath] = new Module(
15
-
sentryPath,
16
-
require.cache[require.resolve(preloadPath)]
17
-
);
18
-
require.cache[sentryPath]!.exports = {
19
-
init: () => {},
20
-
setTag: () => {},
21
-
setUser: () => {}
22
-
};
23
-
logger.debug("Stubbed Sentry node side!");
24
-
} catch (err) {
25
-
logger.error("Failed to stub Sentry:", err);
26
-
}
27
}
···
5
6
const logger = moonlightNode.getLogger("disableSentry");
7
8
+
const preloadPath = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
9
+
try {
10
+
const sentryPath = require.resolve(resolve(preloadPath, "..", "node_modules", "@sentry", "electron"));
11
+
require.cache[sentryPath] = new Module(sentryPath, require.cache[require.resolve(preloadPath)]);
12
+
require.cache[sentryPath]!.exports = {
13
+
init: () => {},
14
+
setTag: () => {},
15
+
setUser: () => {},
16
+
captureMessage: () => {}
17
+
};
18
+
logger.debug("Stubbed Sentry node side!");
19
+
} catch (err) {
20
+
logger.error("Failed to stub Sentry:", err);
21
}
+1
-2
packages/core-extensions/src/disableSentry/webpackModules/stub.ts
+1
-2
packages/core-extensions/src/disableSentry/webpackModules/stub.ts
+39
-2
packages/core-extensions/src/experiments/index.ts
+39
-2
packages/core-extensions/src/experiments/index.ts
···
11
{
12
find: '"scientist:triggered"', // Scientist? Triggered.
13
replace: {
14
+
match: ".personal_connection_id",
15
+
replacement: ".personal_connection_id || true"
16
+
}
17
+
},
18
+
19
+
// Enable staff help menu
20
+
{
21
+
find: ".HEADER_BAR)",
22
+
replace: {
23
+
match: /&&\((.)\?\(0,/,
24
+
replacement: (_, isStaff) =>
25
+
`&&(((moonlight.getConfigOption("experiments","devtools")??false)?true:${isStaff})?(0,`
26
+
}
27
+
},
28
+
// staff help menu - visual refresh
29
+
{
30
+
find: '("AppTitleBar")',
31
+
replace: {
32
+
match: /{hasBugReporterAccess:(\i)}=\i\.\i\.useExperiment\({location:"HeaderBar"},{autoTrackExposure:!1}\);/,
33
+
replacement: (orig, isStaff) =>
34
+
`${orig}if(moonlight.getConfigOption("experiments","devtools")??false)${isStaff}=true;`
35
+
}
36
+
},
37
+
{
38
+
find: 'navId:"staff-help-popout",',
39
+
replace: {
40
+
match: /isDiscordDeveloper:(\i)}\),/,
41
+
replacement: (_, isStaff) =>
42
+
`isDiscordDeveloper:(moonlight.getConfigOption("experiments","devtools")??false)||${isStaff}}),`
43
+
}
44
+
},
45
+
46
+
// Enable further staff-locked options
47
+
{
48
+
find: "shouldShowLurkerModeUpsellPopout:",
49
+
replace: {
50
+
match: /\.useReducedMotion,isStaff:(.),/,
51
+
replacement: (_, isStaff) =>
52
+
`.useReducedMotion,isStaff:(moonlight.getConfigOption("experiments","staffSettings")??false)?true:${isStaff},`
53
}
54
}
55
];
+11
-2
packages/core-extensions/src/experiments/manifest.json
+11
-2
packages/core-extensions/src/experiments/manifest.json
···
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
"id": "experiments",
4
"apiLevel": 2,
5
"meta": {
···
9
"tags": ["dangerZone"]
10
},
11
"settings": {
12
+
"devtools": {
13
+
"advice": "reload",
14
+
"displayName": "Enable staff help menu (DevTools)",
15
+
"type": "boolean",
16
+
"default": false
17
+
},
18
+
"staffSettings": {
19
+
"advice": "reload",
20
"displayName": "Allow access to other staff settings elsewhere",
21
+
"type": "boolean",
22
+
"default": false
23
}
24
}
25
}
+6
-18
packages/core-extensions/src/markdown/index.ts
+6
-18
packages/core-extensions/src/markdown/index.ts
···
6
replace: [
7
{
8
match: /={newline:(.+?)},(.{1,2})=\(0,/,
9
-
replacement: (_, rules, RULES) =>
10
-
`=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,`
11
},
12
{
13
match: /(?<=;(.{1,2}\.Z)={RULES:.+?})/,
14
-
replacement: (_, rulesets) =>
15
-
`;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});`
16
}
17
]
18
},
···
25
`__slateRules,${rulesDef}=__slateRules=require("markdown_markdown")._addSlateRules({link:{${rules}}),${syntaxBefore}=new Set`
26
},
27
{
28
-
match:
29
-
/(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"subtext":{(.+?)}default:/,
30
-
replacement: (
31
-
_,
32
-
start,
33
-
rule,
34
-
body,
35
-
plaintextReturn,
36
-
otherRules,
37
-
inlineStyleBody
38
-
) =>
39
-
`${start}if(${rule}.type.startsWith("__moonlight_")){if(__slateRules[${rule}.type].type=="inlineStyle"){${inlineStyleBody}}else{${plaintextReturn}}}${body}case"emoticon":${plaintextReturn}${otherRules}case"link":{${inlineStyleBody}}default:`
40
}
41
]
42
},
···
44
find: '"Slate: Unknown decoration attribute: "',
45
replace: {
46
match: /=({strong:.+?});/,
47
-
replacement: (_, rules) =>
48
-
`=require("markdown_markdown")._addSlateDecorators(${rules});`
49
}
50
}
51
];
···
6
replace: [
7
{
8
match: /={newline:(.+?)},(.{1,2})=\(0,/,
9
+
replacement: (_, rules, RULES) => `=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,`
10
},
11
{
12
match: /(?<=;(.{1,2}\.Z)={RULES:.+?})/,
13
+
replacement: (_, rulesets) => `;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});`
14
}
15
]
16
},
···
23
`__slateRules,${rulesDef}=__slateRules=require("markdown_markdown")._addSlateRules({link:{${rules}}),${syntaxBefore}=new Set`
24
},
25
{
26
+
match: /(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"subtext":{(.+?)}default:/,
27
+
replacement: (_, start, rule, body, plaintextReturn, otherRules, inlineStyleBody) =>
28
+
`${start}if(${rule}.type.startsWith("__moonlight_")){if(__slateRules[${rule}.type].type=="inlineStyle"){${inlineStyleBody}}else{${plaintextReturn}}}${body}case"emoticon":${plaintextReturn}${otherRules}case"subtext":{${inlineStyleBody}}default:`
29
}
30
]
31
},
···
33
find: '"Slate: Unknown decoration attribute: "',
34
replace: {
35
match: /=({strong:.+?});/,
36
+
replacement: (_, rules) => `=require("markdown_markdown")._addSlateDecorators(${rules});`
37
}
38
}
39
];
+1
packages/core-extensions/src/markdown/manifest.json
+1
packages/core-extensions/src/markdown/manifest.json
+4
-17
packages/core-extensions/src/markdown/webpackModules/markdown.ts
+4
-17
packages/core-extensions/src/markdown/webpackModules/markdown.ts
···
1
-
/* eslint-disable no-console */
2
-
import {
3
-
MarkdownRule,
4
-
Ruleset,
5
-
SlateRule
6
-
} from "@moonlight-mod/types/coreExtensions/markdown";
7
8
-
export const rules: Record<
9
-
string,
10
-
(rules: Record<string, MarkdownRule>) => MarkdownRule
11
-
> = {};
12
-
export const slateRules: Record<
13
-
string,
14
-
(rules: Record<string, SlateRule>) => SlateRule
15
-
> = {};
16
export const slateDecorators: Record<string, string> = {};
17
export const ruleBlacklists: Record<Ruleset, Record<string, boolean>> = {
18
RULES: {},
···
67
return originalRules;
68
}
69
70
-
export function _applyRulesetBlacklist(
71
-
rulesets: Record<Ruleset, Record<string, MarkdownRule>>
72
-
) {
73
for (const ruleset of Object.keys(rulesets) as Ruleset[]) {
74
if (ruleset === "RULES") continue;
75
···
1
+
import { MarkdownRule, Ruleset, SlateRule } from "@moonlight-mod/types/coreExtensions/markdown";
2
3
+
export const rules: Record<string, (rules: Record<string, MarkdownRule>) => MarkdownRule> = {};
4
+
export const slateRules: Record<string, (rules: Record<string, SlateRule>) => SlateRule> = {};
5
export const slateDecorators: Record<string, string> = {};
6
export const ruleBlacklists: Record<Ruleset, Record<string, boolean>> = {
7
RULES: {},
···
56
return originalRules;
57
}
58
59
+
export function _applyRulesetBlacklist(rulesets: Record<Ruleset, Record<string, MarkdownRule>>) {
60
for (const ruleset of Object.keys(rulesets) as Ruleset[]) {
61
if (ruleset === "RULES") continue;
62
+108
packages/core-extensions/src/moonbase/host.ts
+108
packages/core-extensions/src/moonbase/host.ts
···
···
1
+
import * as electron from "electron";
2
+
import * as fs from "node:fs/promises";
3
+
import * as path from "node:path";
4
+
import getNatives from "./native";
5
+
import { MoonlightBranch } from "@moonlight-mod/types";
6
+
7
+
const natives = getNatives();
8
+
9
+
const confirm = (action: string) =>
10
+
electron.dialog
11
+
.showMessageBox({
12
+
title: "Are you sure?",
13
+
message: `Are you sure? This will ${action} and restart Discord.`,
14
+
type: "warning",
15
+
buttons: ["OK", "Cancel"]
16
+
})
17
+
.then((r) => r.response === 0);
18
+
19
+
async function updateAndRestart() {
20
+
if (!(await confirm("update moonlight"))) return;
21
+
const newVersion = await natives.checkForMoonlightUpdate();
22
+
23
+
if (newVersion === null) {
24
+
electron.dialog.showMessageBox({ message: "You are already on the latest version of moonlight." });
25
+
return;
26
+
}
27
+
28
+
try {
29
+
await natives.updateMoonlight();
30
+
await electron.dialog.showMessageBox({ message: "Update successful, restarting Discord." });
31
+
electron.app.relaunch();
32
+
electron.app.exit(0);
33
+
} catch {
34
+
await electron.dialog.showMessageBox({
35
+
message: "Failed to update moonlight. Please use the installer instead.",
36
+
type: "error"
37
+
});
38
+
}
39
+
}
40
+
41
+
async function resetConfig() {
42
+
if (!(await confirm("reset your configuration"))) return;
43
+
44
+
const config = await moonlightHost.getConfigPath();
45
+
const dir = path.dirname(config);
46
+
const branch = path.basename(config, ".json");
47
+
await fs.rename(config, path.join(dir, `${branch}-backup-${Math.floor(Date.now() / 1000)}.json`));
48
+
49
+
await electron.dialog.showMessageBox({ message: "Configuration reset, restarting Discord." });
50
+
electron.app.relaunch();
51
+
electron.app.exit(0);
52
+
}
53
+
54
+
async function changeBranch(branch: MoonlightBranch) {
55
+
if (moonlightHost.branch === branch) return;
56
+
if (!(await confirm("switch branches"))) return;
57
+
try {
58
+
await natives.updateMoonlight(branch);
59
+
await electron.dialog.showMessageBox({ message: "Branch switch successful, restarting Discord." });
60
+
electron.app.relaunch();
61
+
electron.app.exit(0);
62
+
} catch (e) {
63
+
await electron.dialog.showMessageBox({ message: "Failed to switch branches:\n" + e, type: "error" });
64
+
}
65
+
}
66
+
67
+
function showAbout() {
68
+
electron.dialog.showMessageBox({
69
+
title: "About moonlight",
70
+
message: `moonlight ${moonlightHost.branch} ${moonlightHost.version}`
71
+
});
72
+
}
73
+
74
+
electron.app.whenReady().then(() => {
75
+
const original = electron.Menu.buildFromTemplate;
76
+
electron.Menu.buildFromTemplate = function (entries) {
77
+
const i = entries.findIndex((e) => e.label === "Check for Updates...");
78
+
if (i === -1) return original.call(this, entries);
79
+
80
+
if (!entries.find((e) => e.label === "moonlight")) {
81
+
const options: Electron.MenuItemConstructorOptions[] = [
82
+
{ label: "Update and restart", click: updateAndRestart },
83
+
{ label: "Reset config", click: resetConfig }
84
+
];
85
+
86
+
if (moonlightHost.branch !== MoonlightBranch.DEV) {
87
+
options.push({
88
+
label: "Switch branch",
89
+
submenu: [MoonlightBranch.STABLE, MoonlightBranch.NIGHTLY].map((branch) => ({
90
+
label: branch,
91
+
type: "radio",
92
+
checked: moonlightHost.branch === branch,
93
+
click: () => changeBranch(branch)
94
+
}))
95
+
});
96
+
}
97
+
98
+
options.push({ label: "About", click: showAbout });
99
+
100
+
entries.splice(i + 1, 0, {
101
+
label: "moonlight",
102
+
submenu: options
103
+
});
104
+
}
105
+
106
+
return original.call(this, entries);
107
+
};
108
+
});
+59
-59
packages/core-extensions/src/moonbase/index.tsx
+59
-59
packages/core-extensions/src/moonbase/index.tsx
···
1
-
import { ExtensionWebpackModule } from "@moonlight-mod/types";
2
3
export const webpackModules: Record<string, ExtensionWebpackModule> = {
4
stores: {
5
-
dependencies: [
6
-
{ id: "discord/packages/flux" },
7
-
{ id: "discord/Dispatcher" }
8
-
]
9
},
10
11
ui: {
···
14
{ id: "react" },
15
{ id: "discord/components/common/index" },
16
{ ext: "moonbase", id: "stores" },
17
-
{ id: "discord/modules/guild_settings/IntegrationCard.css" },
18
"Masks.PANEL_BUTTON",
19
'"Missing channel in Channel.openChannelContextMenu"',
20
".forumOrHome]:"
21
]
22
},
23
24
settings: {
25
dependencies: [
26
{ ext: "spacepack", id: "spacepack" },
27
{ ext: "settings", id: "settings" },
28
{ id: "react" },
29
-
{ ext: "moonbase", id: "ui" }
30
],
31
entrypoint: true
32
},
···
35
dependencies: [
36
{ id: "react" },
37
{ ext: "moonbase", id: "stores" },
38
{ ext: "notices", id: "notices" },
39
{
40
ext: "spacepack",
41
id: "spacepack"
42
-
}
43
],
44
entrypoint: true
45
},
46
47
moonbase: {
48
dependencies: [{ ext: "moonbase", id: "stores" }]
49
}
50
};
51
-
52
-
const bg = "#222034";
53
-
const fg = "#FFFBA6";
54
-
55
-
export const styles = [
56
-
`
57
-
.moonbase-settings > :first-child {
58
-
margin-top: 0px;
59
-
}
60
-
61
-
textarea.moonbase-resizeable {
62
-
resize: vertical
63
-
}
64
-
65
-
.moonbase-updates-notice {
66
-
background-color: ${bg};
67
-
color: ${fg};
68
-
line-height: unset;
69
-
height: 36px;
70
-
}
71
-
72
-
.moonbase-updates-notice button {
73
-
color: ${fg};
74
-
border-color: ${fg};
75
-
}
76
-
77
-
.moonbase-updates-notice_text-wrapper {
78
-
display: inline-flex;
79
-
align-items: center;
80
-
line-height: 36px;
81
-
gap: 2px;
82
-
}
83
-
84
-
.moonbase-update-section {
85
-
background-color: ${bg};
86
-
--info-help-foreground: ${fg};
87
-
border: none !important;
88
-
color: ${fg};
89
-
90
-
display: flex;
91
-
flex-direction: row;
92
-
justify-content: space-between;
93
-
}
94
-
95
-
.moonbase-update-section > button {
96
-
color: ${fg};
97
-
background-color: transparent;
98
-
border-color: ${fg};
99
-
}
100
-
`.trim()
101
-
];
···
1
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
2
+
3
+
export const patches: Patch[] = [
4
+
{
5
+
find: "window.DiscordErrors=",
6
+
replace: [
7
+
// replace reporting line with update status
8
+
{
9
+
// CvQlAA mapped to ERRORS_ACTION_TO_TAKE
10
+
// FIXME: Better patch find?
11
+
match: /,(\(0,(\i)\.jsx\))\("p",{children:\i\.\i\.string\(\i\.\i\.CvQlAA\)}\)/,
12
+
replacement: (_, createElement, ReactJSX) =>
13
+
`,${createElement}(require("moonbase_crashScreen")?.UpdateText??${ReactJSX}.Fragment,{state:this.state,setState:this.setState.bind(this)})`
14
+
},
15
+
16
+
// wrap actions field to display error details
17
+
{
18
+
match: /(?<=return(\(0,(\i)\.jsx\))\(.+?,)action:(\i),className:/,
19
+
replacement: (_, createElement, ReactJSX, action) =>
20
+
`action:require("moonbase_crashScreen")?.wrapAction?${createElement}(require("moonbase_crashScreen").wrapAction,{action:${action},state:this.state}):${action},className:`
21
+
},
22
+
23
+
// add update button
24
+
// +hivLS -> ERRORS_RELOAD
25
+
{
26
+
match: /(?<=\["\+hivLS"\]\)}\),(\(0,(\i)\.jsx\))\(\i,{}\))/,
27
+
replacement: (_, createElement, ReactJSX) =>
28
+
`,${createElement}(require("moonbase_crashScreen")?.UpdateButton??${ReactJSX}.Fragment,{state:this.state,setState:this.setState.bind(this)})`
29
+
}
30
+
]
31
+
}
32
+
];
33
34
export const webpackModules: Record<string, ExtensionWebpackModule> = {
35
stores: {
36
+
dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }]
37
},
38
39
ui: {
···
42
{ id: "react" },
43
{ id: "discord/components/common/index" },
44
{ ext: "moonbase", id: "stores" },
45
+
{ ext: "moonbase", id: "ThemeDarkIcon" },
46
+
{ id: "discord/modules/guild_settings/web/AppCard.css" },
47
+
{ ext: "contextMenu", id: "contextMenu" },
48
+
{ id: "discord/modules/modals/Modals" },
49
"Masks.PANEL_BUTTON",
50
'"Missing channel in Channel.openChannelContextMenu"',
51
".forumOrHome]:"
52
]
53
},
54
55
+
ThemeDarkIcon: {
56
+
dependencies: [{ ext: "common", id: "icons" }, { id: "react" }]
57
+
},
58
+
59
settings: {
60
dependencies: [
61
{ ext: "spacepack", id: "spacepack" },
62
{ ext: "settings", id: "settings" },
63
{ id: "react" },
64
+
{ ext: "moonbase", id: "ui" },
65
+
{ ext: "contextMenu", id: "contextMenu" },
66
+
':"USER_SETTINGS_MODAL_SET_SECTION"'
67
],
68
entrypoint: true
69
},
···
72
dependencies: [
73
{ id: "react" },
74
{ ext: "moonbase", id: "stores" },
75
+
{ ext: "moonbase", id: "ThemeDarkIcon" },
76
{ ext: "notices", id: "notices" },
77
{
78
ext: "spacepack",
79
id: "spacepack"
80
+
},
81
+
{ id: "discord/Constants" },
82
+
{ id: "discord/components/common/index" }
83
],
84
entrypoint: true
85
},
86
87
moonbase: {
88
dependencies: [{ ext: "moonbase", id: "stores" }]
89
+
},
90
+
91
+
crashScreen: {
92
+
dependencies: [
93
+
{ ext: "spacepack", id: "spacepack" },
94
+
{ id: "react" },
95
+
{ ext: "moonbase", id: "stores" },
96
+
{ id: "discord/packages/flux" },
97
+
{ id: "discord/components/common/index" },
98
+
/tabBar:"tabBar_[a-z0-9]+",tabBarItem:"tabBarItem_[a-z0-9]+"/
99
+
]
100
}
101
};
+18
-5
packages/core-extensions/src/moonbase/manifest.json
+18
-5
packages/core-extensions/src/moonbase/manifest.json
···
1
{
2
"id": "moonbase",
3
"apiLevel": 2,
4
"meta": {
···
6
"tagline": "The official settings UI for moonlight",
7
"authors": ["Cynosphere", "NotNite", "redstonekasi"]
8
},
9
-
"dependencies": ["spacepack", "settings", "common", "notices"],
10
"settings": {
11
"sections": {
12
"displayName": "Split into sections",
13
"description": "Show the Moonbase tabs as separate sections",
14
-
"type": "boolean"
15
},
16
"saveFilter": {
17
"displayName": "Persist filter",
18
"description": "Save extension filter in config",
19
-
"type": "boolean"
20
},
21
"updateChecking": {
22
"displayName": "Automatic update checking",
23
"description": "Checks for updates to moonlight",
24
"type": "boolean",
25
-
"default": "true"
26
},
27
"updateBanner": {
28
"displayName": "Show update banner",
29
"description": "Shows a banner for moonlight and extension updates",
30
"type": "boolean",
31
-
"default": "true"
32
}
33
},
34
"cors": [
···
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
"id": "moonbase",
4
"apiLevel": 2,
5
"meta": {
···
7
"tagline": "The official settings UI for moonlight",
8
"authors": ["Cynosphere", "NotNite", "redstonekasi"]
9
},
10
+
"dependencies": ["spacepack", "settings", "common", "notices", "contextMenu"],
11
"settings": {
12
"sections": {
13
+
"advice": "reload",
14
"displayName": "Split into sections",
15
"description": "Show the Moonbase tabs as separate sections",
16
+
"type": "boolean",
17
+
"default": false
18
+
},
19
+
"oldLocation": {
20
+
"advice": "reload",
21
+
"displayName": "Put Moonbase back at the bottom",
22
+
"type": "boolean",
23
+
"default": false
24
},
25
"saveFilter": {
26
+
"advice": "none",
27
"displayName": "Persist filter",
28
"description": "Save extension filter in config",
29
+
"type": "boolean",
30
+
"default": false
31
},
32
"updateChecking": {
33
+
"advice": "none",
34
"displayName": "Automatic update checking",
35
"description": "Checks for updates to moonlight",
36
"type": "boolean",
37
+
"default": true
38
},
39
"updateBanner": {
40
+
"advice": "none",
41
"displayName": "Show update banner",
42
"description": "Shows a banner for moonlight and extension updates",
43
"type": "boolean",
44
+
"default": true
45
}
46
},
47
"cors": [
+46
-71
packages/core-extensions/src/moonbase/native.ts
+46
-71
packages/core-extensions/src/moonbase/native.ts
···
1
import { MoonlightBranch } from "@moonlight-mod/types";
2
import type { MoonbaseNatives, RepositoryManifest } from "./types";
3
import extractAsar from "@moonlight-mod/core/asar";
4
-
import {
5
-
distDir,
6
-
repoUrlFile,
7
-
installedVersionFile
8
-
} from "@moonlight-mod/types/constants";
9
import { parseTarGzip } from "nanotar";
10
11
const githubRepo = "moonlight-mod/moonlight";
12
const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`;
···
15
const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref";
16
const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz";
17
18
-
export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`;
19
20
async function getStableRelease(): Promise<{
21
name: string;
···
26
}> {
27
const req = await fetch(githubApiUrl, {
28
cache: "no-store",
29
-
headers: {
30
-
"User-Agent": userAgent
31
-
}
32
});
33
return await req.json();
34
}
35
36
export default function getNatives(): MoonbaseNatives {
37
-
const logger = moonlightNode.getLogger("moonbase/natives");
38
39
return {
40
async checkForMoonlightUpdate() {
41
try {
42
-
if (moonlightNode.branch === MoonlightBranch.STABLE) {
43
const json = await getStableRelease();
44
-
return json.name !== moonlightNode.version ? json.name : null;
45
-
} else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) {
46
const req = await fetch(nightlyRefUrl, {
47
cache: "no-store",
48
-
headers: {
49
-
"User-Agent": userAgent
50
-
}
51
});
52
const ref = (await req.text()).split("\n")[0];
53
-
return ref !== moonlightNode.version ? ref : null;
54
}
55
56
return null;
···
60
}
61
},
62
63
-
async updateMoonlight() {
64
// Note: this won't do anything on browser, we should probably disable it
65
// entirely when running in browser.
66
async function downloadStable(): Promise<[ArrayBuffer, string]> {
···
71
logger.debug(`Downloading ${asset.browser_download_url}`);
72
const req = await fetch(asset.browser_download_url, {
73
cache: "no-store",
74
-
headers: {
75
-
"User-Agent": userAgent
76
-
}
77
});
78
79
return [await req.arrayBuffer(), json.name];
···
83
logger.debug(`Downloading ${nightlyZipUrl}`);
84
const zipReq = await fetch(nightlyZipUrl, {
85
cache: "no-store",
86
-
headers: {
87
-
"User-Agent": userAgent
88
-
}
89
});
90
91
const refReq = await fetch(nightlyRefUrl, {
92
cache: "no-store",
93
-
headers: {
94
-
"User-Agent": userAgent
95
-
}
96
});
97
const ref = (await refReq.text()).split("\n")[0];
98
···
100
}
101
102
const [tar, ref] =
103
-
moonlightNode.branch === MoonlightBranch.STABLE
104
? await downloadStable()
105
-
: moonlightNode.branch === MoonlightBranch.NIGHTLY
106
? await downloadNightly()
107
: [null, null];
108
109
if (!tar || !ref) return;
110
111
-
const dist = moonlightFS.join(moonlightNode.getMoonlightDir(), distDir);
112
-
if (await moonlightFS.exists(dist)) await moonlightFS.rmdir(dist);
113
-
await moonlightFS.mkdir(dist);
114
115
logger.debug("Extracting update");
116
const files = await parseTarGzip(tar);
···
119
// @ts-expect-error What do you mean their own types are wrong
120
if (file.type !== "file") continue;
121
122
-
const fullFile = moonlightFS.join(dist, file.name);
123
-
const fullDir = moonlightFS.dirname(fullFile);
124
-
if (!(await moonlightFS.exists(fullDir)))
125
-
await moonlightFS.mkdir(fullDir);
126
-
await moonlightFS.writeFile(fullFile, file.data);
127
}
128
129
logger.debug("Writing version file:", ref);
130
-
const versionFile = moonlightFS.join(
131
-
moonlightNode.getMoonlightDir(),
132
-
installedVersionFile
133
-
);
134
-
await moonlightFS.writeFileString(versionFile, ref.trim());
135
136
logger.debug("Update extracted");
137
},
···
143
try {
144
const req = await fetch(repo, {
145
cache: "no-store",
146
-
headers: {
147
-
"User-Agent": userAgent
148
-
}
149
});
150
const json = await req.json();
151
ret[repo] = json;
···
159
160
async installExtension(manifest, url, repo) {
161
const req = await fetch(url, {
162
-
headers: {
163
-
"User-Agent": userAgent
164
-
}
165
});
166
167
-
const dir = moonlightNode.getExtensionDir(manifest.id);
168
// remake it in case of updates
169
-
if (await moonlightFS.exists(dir)) await moonlightFS.rmdir(dir);
170
-
await moonlightFS.mkdir(dir);
171
172
const buffer = await req.arrayBuffer();
173
const files = extractAsar(buffer);
174
for (const [file, buf] of Object.entries(files)) {
175
-
const fullFile = moonlightFS.join(dir, file);
176
-
const fullDir = moonlightFS.dirname(fullFile);
177
178
-
if (!(await moonlightFS.exists(fullDir)))
179
-
await moonlightFS.mkdir(fullDir);
180
-
await moonlightFS.writeFile(moonlightFS.join(dir, file), buf);
181
}
182
183
-
await moonlightFS.writeFileString(
184
-
moonlightFS.join(dir, repoUrlFile),
185
-
repo
186
-
);
187
},
188
189
async deleteExtension(id) {
190
-
const dir = moonlightNode.getExtensionDir(id);
191
-
await moonlightFS.rmdir(dir);
192
-
},
193
-
194
-
getExtensionConfig(id, key) {
195
-
const config = moonlightNode.config.extensions[id];
196
-
if (typeof config === "object") {
197
-
return config.config?.[key];
198
-
}
199
-
200
-
return undefined;
201
}
202
};
203
}
···
1
import { MoonlightBranch } from "@moonlight-mod/types";
2
import type { MoonbaseNatives, RepositoryManifest } from "./types";
3
import extractAsar from "@moonlight-mod/core/asar";
4
+
import { distDir, repoUrlFile, installedVersionFile } from "@moonlight-mod/types/constants";
5
import { parseTarGzip } from "nanotar";
6
+
7
+
const moonlightGlobal = globalThis.moonlightHost ?? globalThis.moonlightNode;
8
9
const githubRepo = "moonlight-mod/moonlight";
10
const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`;
···
13
const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref";
14
const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz";
15
16
+
export const userAgent = `moonlight/${moonlightGlobal.version} (https://github.com/moonlight-mod/moonlight)`;
17
+
18
+
// User-Agent header causes trouble on Firefox
19
+
const isBrowser = globalThis.moonlightNode != null && globalThis.moonlightNode.isBrowser;
20
+
const sharedHeaders: Record<string, string> = {};
21
+
if (!isBrowser) sharedHeaders["User-Agent"] = userAgent;
22
23
async function getStableRelease(): Promise<{
24
name: string;
···
29
}> {
30
const req = await fetch(githubApiUrl, {
31
cache: "no-store",
32
+
headers: sharedHeaders
33
});
34
return await req.json();
35
}
36
37
export default function getNatives(): MoonbaseNatives {
38
+
const logger = moonlightGlobal.getLogger("moonbase/natives");
39
40
return {
41
async checkForMoonlightUpdate() {
42
try {
43
+
if (moonlightGlobal.branch === MoonlightBranch.STABLE) {
44
const json = await getStableRelease();
45
+
return json.name !== moonlightGlobal.version ? json.name : null;
46
+
} else if (moonlightGlobal.branch === MoonlightBranch.NIGHTLY) {
47
const req = await fetch(nightlyRefUrl, {
48
cache: "no-store",
49
+
headers: sharedHeaders
50
});
51
const ref = (await req.text()).split("\n")[0];
52
+
return ref !== moonlightGlobal.version ? ref : null;
53
}
54
55
return null;
···
59
}
60
},
61
62
+
async updateMoonlight(overrideBranch?: MoonlightBranch) {
63
+
const branch = overrideBranch ?? moonlightGlobal.branch;
64
+
65
// Note: this won't do anything on browser, we should probably disable it
66
// entirely when running in browser.
67
async function downloadStable(): Promise<[ArrayBuffer, string]> {
···
72
logger.debug(`Downloading ${asset.browser_download_url}`);
73
const req = await fetch(asset.browser_download_url, {
74
cache: "no-store",
75
+
headers: sharedHeaders
76
});
77
78
return [await req.arrayBuffer(), json.name];
···
82
logger.debug(`Downloading ${nightlyZipUrl}`);
83
const zipReq = await fetch(nightlyZipUrl, {
84
cache: "no-store",
85
+
headers: sharedHeaders
86
});
87
88
const refReq = await fetch(nightlyRefUrl, {
89
cache: "no-store",
90
+
headers: sharedHeaders
91
});
92
const ref = (await refReq.text()).split("\n")[0];
93
···
95
}
96
97
const [tar, ref] =
98
+
branch === MoonlightBranch.STABLE
99
? await downloadStable()
100
+
: branch === MoonlightBranch.NIGHTLY
101
? await downloadNightly()
102
: [null, null];
103
104
if (!tar || !ref) return;
105
106
+
const dist = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), distDir);
107
+
if (await moonlightNodeSandboxed.fs.exists(dist)) await moonlightNodeSandboxed.fs.rmdir(dist);
108
+
await moonlightNodeSandboxed.fs.mkdir(dist);
109
110
logger.debug("Extracting update");
111
const files = await parseTarGzip(tar);
···
114
// @ts-expect-error What do you mean their own types are wrong
115
if (file.type !== "file") continue;
116
117
+
const fullFile = moonlightNodeSandboxed.fs.join(dist, file.name);
118
+
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
119
+
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
120
+
await moonlightNodeSandboxed.fs.writeFile(fullFile, file.data);
121
}
122
123
logger.debug("Writing version file:", ref);
124
+
const versionFile = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), installedVersionFile);
125
+
await moonlightNodeSandboxed.fs.writeFileString(versionFile, ref.trim());
126
127
logger.debug("Update extracted");
128
},
···
134
try {
135
const req = await fetch(repo, {
136
cache: "no-store",
137
+
headers: sharedHeaders
138
});
139
const json = await req.json();
140
ret[repo] = json;
···
148
149
async installExtension(manifest, url, repo) {
150
const req = await fetch(url, {
151
+
cache: "no-store",
152
+
headers: sharedHeaders
153
});
154
155
+
const dir = moonlightGlobal.getExtensionDir(manifest.id);
156
// remake it in case of updates
157
+
if (await moonlightNodeSandboxed.fs.exists(dir)) await moonlightNodeSandboxed.fs.rmdir(dir);
158
+
await moonlightNodeSandboxed.fs.mkdir(dir);
159
160
const buffer = await req.arrayBuffer();
161
const files = extractAsar(buffer);
162
for (const [file, buf] of Object.entries(files)) {
163
+
const fullFile = moonlightNodeSandboxed.fs.join(dir, file);
164
+
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
165
166
+
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
167
+
await moonlightNodeSandboxed.fs.writeFile(moonlightNodeSandboxed.fs.join(dir, file), buf);
168
}
169
170
+
await moonlightNodeSandboxed.fs.writeFileString(moonlightNodeSandboxed.fs.join(dir, repoUrlFile), repo);
171
},
172
173
async deleteExtension(id) {
174
+
const dir = moonlightGlobal.getExtensionDir(id);
175
+
await moonlightNodeSandboxed.fs.rmdir(dir);
176
}
177
};
178
}
+269
packages/core-extensions/src/moonbase/style.css
+269
packages/core-extensions/src/moonbase/style.css
···
···
1
+
:root {
2
+
--moonbase-bg: #222034;
3
+
--moonbase-fg: #fffba6;
4
+
}
5
+
6
+
.moonbase-settings > :first-child {
7
+
margin-top: 0px;
8
+
}
9
+
10
+
.moonbase-retry-button {
11
+
padding: 8px;
12
+
margin-right: 8px;
13
+
}
14
+
15
+
textarea.moonbase-resizeable {
16
+
resize: vertical;
17
+
}
18
+
19
+
.moonbase-link-buttons {
20
+
border-bottom: 2px solid var(--background-modifier-accent);
21
+
margin-bottom: -2px;
22
+
margin-left: 0 !important;
23
+
padding-right: 20px;
24
+
gap: 1rem;
25
+
}
26
+
27
+
.moonbase-speen {
28
+
animation: moonbase-speen-animation 0.25s linear infinite;
29
+
}
30
+
31
+
@keyframes moonbase-speen-animation {
32
+
from {
33
+
transform: rotate(0deg);
34
+
}
35
+
to {
36
+
transform: rotate(360deg);
37
+
}
38
+
}
39
+
40
+
/* Update notice at the top of the client */
41
+
.moonbase-updates-notice {
42
+
background-color: var(--moonbase-bg);
43
+
color: var(--moonbase-fg);
44
+
--custom-notice-text: var(--moonbase-fg);
45
+
line-height: unset;
46
+
height: 36px;
47
+
}
48
+
49
+
.moonbase-updates-notice button {
50
+
color: var(--moonbase-fg);
51
+
border-color: var(--moonbase-fg);
52
+
}
53
+
54
+
.moonbase-updates-notice_text-wrapper {
55
+
display: inline-flex;
56
+
align-items: center;
57
+
line-height: 36px;
58
+
gap: 2px;
59
+
}
60
+
61
+
/* Help messages in Moonbase UI */
62
+
.moonbase-help-message {
63
+
display: flex;
64
+
flex-direction: row;
65
+
justify-content: space-between;
66
+
}
67
+
68
+
.moonbase-help-message-sticky {
69
+
position: sticky;
70
+
top: 24px;
71
+
z-index: 10;
72
+
background-color: var(--background-primary);
73
+
}
74
+
75
+
.moonbase-extension-update-section {
76
+
margin-top: 15px;
77
+
}
78
+
79
+
.moonbase-update-section {
80
+
background-color: var(--moonbase-bg);
81
+
--info-help-foreground: var(--moonbase-fg);
82
+
border: none !important;
83
+
color: var(--moonbase-fg);
84
+
}
85
+
86
+
.moonbase-update-section button {
87
+
--info-help-foreground: var(--moonbase-fg);
88
+
color: var(--moonbase-fg);
89
+
background-color: transparent;
90
+
border-color: var(--moonbase-fg);
91
+
}
92
+
93
+
.moonbase-help-message-buttons {
94
+
display: flex;
95
+
flex-direction: row;
96
+
gap: 8px;
97
+
align-items: center;
98
+
}
99
+
100
+
.moonbase-update-divider {
101
+
margin: 32px 0;
102
+
}
103
+
104
+
.moonlight-card-info-header {
105
+
margin-bottom: 0.25rem;
106
+
}
107
+
108
+
.moonlight-card-badge {
109
+
border-radius: 0.1875rem;
110
+
padding: 0 0.275rem;
111
+
margin-right: 0.4em;
112
+
background-color: var(--badge-color, var(--bg-mod-strong));
113
+
}
114
+
115
+
/* Crash screen */
116
+
.moonbase-crash-wrapper > [class^="buttons_"] {
117
+
gap: 1rem;
118
+
}
119
+
120
+
.moonbase-crash-wrapper {
121
+
display: flex;
122
+
flex-direction: column;
123
+
align-items: center;
124
+
gap: 1rem;
125
+
height: 50%;
126
+
width: 50vw;
127
+
max-height: 50%;
128
+
max-width: 50vw;
129
+
}
130
+
131
+
.moonbase-crash-tabs {
132
+
width: 100%;
133
+
}
134
+
135
+
.moonbase-crash-details-wrapper {
136
+
overflow-y: scroll;
137
+
color: var(--text-normal);
138
+
background: var(--background-secondary);
139
+
border: 1px solid var(--background-tertiary);
140
+
border-radius: 4px;
141
+
padding: 0.5em;
142
+
143
+
&::-webkit-scrollbar {
144
+
width: 8px;
145
+
height: 8px;
146
+
}
147
+
148
+
&::-webkit-scrollbar-thumb {
149
+
background-clip: padding-box;
150
+
border: 2px solid transparent;
151
+
border-radius: 4px;
152
+
background-color: var(--scrollbar-thin-thumb);
153
+
min-height: 40px;
154
+
}
155
+
156
+
&::-webkit-scrollbar-track {
157
+
border: 2px solid var(--scrollbar-thin-track);
158
+
background-color: var(--scrollbar-thin-track);
159
+
border-color: var(--scrollbar-thin-track);
160
+
}
161
+
}
162
+
163
+
.moonbase-crash-details {
164
+
box-sizing: border-box;
165
+
padding: 0;
166
+
font-family: var(--font-code);
167
+
font-size: 0.75rem;
168
+
line-height: 1rem;
169
+
margin: 6px;
170
+
white-space: pre-wrap;
171
+
background-clip: border-box;
172
+
173
+
& > code {
174
+
font-size: 0.875rem;
175
+
line-height: 1.125rem;
176
+
text-indent: 0;
177
+
white-space: pre-wrap;
178
+
text-size-adjust: none;
179
+
display: block;
180
+
user-select: text;
181
+
}
182
+
}
183
+
184
+
.moonbase-crash-extensions {
185
+
overflow-y: scroll;
186
+
display: grid;
187
+
grid-auto-columns: 25vw;
188
+
gap: 8px;
189
+
190
+
&::-webkit-scrollbar {
191
+
width: 8px;
192
+
height: 8px;
193
+
}
194
+
195
+
&::-webkit-scrollbar-thumb {
196
+
background-clip: padding-box;
197
+
border: 2px solid transparent;
198
+
border-radius: 4px;
199
+
background-color: var(--scrollbar-thin-thumb);
200
+
min-height: 40px;
201
+
}
202
+
203
+
&::-webkit-scrollbar-track {
204
+
border: 2px solid var(--scrollbar-thin-track);
205
+
background-color: var(--scrollbar-thin-track);
206
+
border-color: var(--scrollbar-thin-track);
207
+
}
208
+
}
209
+
210
+
.moonbase-crash-extensionCard {
211
+
color: var(--text-normal);
212
+
background: var(--background-secondary);
213
+
border: 1px solid var(--background-tertiary);
214
+
border-radius: 4px;
215
+
padding: 0.5em;
216
+
display: flex;
217
+
}
218
+
219
+
.moonbase-crash-extensionCard-meta {
220
+
display: flex;
221
+
flex-direction: column;
222
+
flex-grow: 1;
223
+
}
224
+
225
+
.moonbase-crash-extensionCard-title {
226
+
color: var(--text-normal);
227
+
font-family: var(--font-primary);
228
+
font-size: 16px;
229
+
line-height: 1.25;
230
+
font-weight: 600;
231
+
}
232
+
233
+
.moonbase-crash-extensionCard-version {
234
+
color: var(--text-muted);
235
+
font-family: var(--font-primary);
236
+
font-size: 14px;
237
+
line-height: 1.286;
238
+
font-weight: 400;
239
+
}
240
+
241
+
/* About page */
242
+
.moonbase-wordmark {
243
+
width: 100%;
244
+
}
245
+
246
+
.moonbase-devs {
247
+
width: 100%;
248
+
display: flex;
249
+
justify-content: center;
250
+
gap: 0rem 0.5rem;
251
+
padding-top: 0.5rem;
252
+
}
253
+
254
+
.moonbase-dev {
255
+
height: 4rem;
256
+
}
257
+
258
+
.moonbase-dev-avatar {
259
+
width: 2rem;
260
+
border-radius: 50%;
261
+
}
262
+
263
+
.moonbase-gap {
264
+
gap: 0.5rem;
265
+
}
266
+
267
+
.moonbase-about-page {
268
+
gap: 1rem;
269
+
}
+21
-11
packages/core-extensions/src/moonbase/types.ts
+21
-11
packages/core-extensions/src/moonbase/types.ts
···
1
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
2
-
import { DetectedExtension, ExtensionManifest } from "@moonlight-mod/types";
3
4
export type MoonbaseNatives = {
5
checkForMoonlightUpdate(): Promise<string | null>;
6
-
updateMoonlight(): Promise<void>;
7
8
-
fetchRepositories(
9
-
repos: string[]
10
-
): Promise<Record<string, RepositoryManifest[]>>;
11
-
installExtension(
12
-
manifest: RepositoryManifest,
13
-
url: string,
14
-
repo: string
15
-
): Promise<void>;
16
deleteExtension(id: string): Promise<void>;
17
-
getExtensionConfig(id: string, key: string): any;
18
};
19
20
export type RepositoryManifest = ExtensionManifest & {
···
35
state: ExtensionState;
36
compat: ExtensionCompat;
37
hasUpdate: boolean;
38
};
···
1
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
2
+
import { DetectedExtension, ExtensionManifest, MoonlightBranch } from "@moonlight-mod/types";
3
4
export type MoonbaseNatives = {
5
checkForMoonlightUpdate(): Promise<string | null>;
6
+
updateMoonlight(overrideBranch?: MoonlightBranch): Promise<void>;
7
8
+
fetchRepositories(repos: string[]): Promise<Record<string, RepositoryManifest[]>>;
9
+
installExtension(manifest: RepositoryManifest, url: string, repo: string): Promise<void>;
10
deleteExtension(id: string): Promise<void>;
11
};
12
13
export type RepositoryManifest = ExtensionManifest & {
···
28
state: ExtensionState;
29
compat: ExtensionCompat;
30
hasUpdate: boolean;
31
+
changelog?: string;
32
+
settingsOverride?: ExtensionManifest["settings"];
33
};
34
+
35
+
export enum UpdateState {
36
+
Ready,
37
+
Working,
38
+
Installed,
39
+
Failed
40
+
}
41
+
42
+
// Ordered in terms of priority
43
+
export enum RestartAdvice {
44
+
NotNeeded, // No action is needed
45
+
ReloadSuggested, // A reload might be needed
46
+
ReloadNeeded, // A reload is needed
47
+
RestartNeeded // A restart is needed
48
+
}
+36
packages/core-extensions/src/moonbase/webpackModules/ThemeDarkIcon.tsx
+36
packages/core-extensions/src/moonbase/webpackModules/ThemeDarkIcon.tsx
···
···
1
+
// RIP to ThemeDarkIcon ????-2025
2
+
// <Cynthia> Failed to remap "ThemeDarkIcon" in "discord/components/common/index"
3
+
// <NotNite> bro are you fucking kidding me
4
+
// <NotNite> that's literally the icon we use for the update banner
5
+
6
+
import React from "@moonlight-mod/wp/react";
7
+
import icons from "@moonlight-mod/wp/common_icons";
8
+
import type { IconProps } from "@moonlight-mod/types/coreExtensions/common";
9
+
10
+
export default function ThemeDarkIcon(props?: IconProps) {
11
+
const parsed = icons.parseProps(props);
12
+
13
+
return (
14
+
<svg
15
+
aria-hidden="true"
16
+
role="img"
17
+
xmlns="http://www.w3.org/2000/svg"
18
+
width={parsed.width}
19
+
height={parsed.height}
20
+
fill="none"
21
+
viewBox="0 0 24 24"
22
+
>
23
+
<path
24
+
fill={parsed.fill}
25
+
className={parsed.className}
26
+
d="M20.52 18.96c.32-.4-.01-.96-.52-.96A11 11 0 0 1 9.77 2.94c.31-.78-.3-1.68-1.1-1.43a11 11 0 1 0 11.85 17.45Z"
27
+
/>
28
+
29
+
<path
30
+
fill={parsed.fill}
31
+
className={parsed.className}
32
+
d="m17.73 9.27-.76-2.02a.5.5 0 0 0-.94 0l-.76 2.02-2.02.76a.5.5 0 0 0 0 .94l2.02.76.76 2.02a.5.5 0 0 0 .94 0l.76-2.02 2.02-.76a.5.5 0 0 0 0-.94l-2.02-.76ZM19.73 2.62l.45 1.2 1.2.45c.21.08.21.38 0 .46l-1.2.45-.45 1.2a.25.25 0 0 1-.46 0l-.45-1.2-1.2-.45a.25.25 0 0 1 0-.46l1.2-.45.45-1.2a.25.25 0 0 1 .46 0Z"
33
+
/>
34
+
</svg>
35
+
);
36
+
}
+263
packages/core-extensions/src/moonbase/webpackModules/crashScreen.tsx
+263
packages/core-extensions/src/moonbase/webpackModules/crashScreen.tsx
···
···
1
+
import React from "@moonlight-mod/wp/react";
2
+
import { Button, TabBar } from "@moonlight-mod/wp/discord/components/common/index";
3
+
import { useStateFromStores, useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
4
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
5
+
import { RepositoryManifest, UpdateState } from "../types";
6
+
import { ConfigExtension, DetectedExtension } from "@moonlight-mod/types";
7
+
import DiscoveryClasses from "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css";
8
+
9
+
const MODULE_REGEX = /Webpack-Module\/(\d+)\/(\d+)/g;
10
+
11
+
const logger = moonlight.getLogger("moonbase/crashScreen");
12
+
13
+
type ErrorState = {
14
+
error: Error;
15
+
info: {
16
+
componentStack: string;
17
+
};
18
+
__moonlight_update?: UpdateState;
19
+
};
20
+
21
+
type WrapperProps = {
22
+
action: React.ReactNode;
23
+
state: ErrorState;
24
+
};
25
+
26
+
type UpdateCardProps = {
27
+
id: number;
28
+
ext: {
29
+
version: string;
30
+
download: string;
31
+
updateManifest: RepositoryManifest;
32
+
};
33
+
};
34
+
35
+
const updateStrings: Record<UpdateState, string> = {
36
+
[UpdateState.Ready]: "A new version of moonlight is available.",
37
+
[UpdateState.Working]: "Updating moonlight...",
38
+
[UpdateState.Installed]: "Updated moonlight. Click Reload to apply changes.",
39
+
[UpdateState.Failed]: "Failed to update moonlight. Please use the installer."
40
+
};
41
+
const buttonStrings: Record<UpdateState, string> = {
42
+
[UpdateState.Ready]: "Update moonlight",
43
+
[UpdateState.Working]: "Updating moonlight...",
44
+
[UpdateState.Installed]: "",
45
+
[UpdateState.Failed]: "Update failed"
46
+
};
47
+
const extensionButtonStrings: Record<UpdateState, string> = {
48
+
[UpdateState.Ready]: "Update",
49
+
[UpdateState.Working]: "Updating...",
50
+
[UpdateState.Installed]: "Updated",
51
+
[UpdateState.Failed]: "Update failed"
52
+
};
53
+
54
+
function ExtensionUpdateCard({ id, ext }: UpdateCardProps) {
55
+
const [state, setState] = React.useState(UpdateState.Ready);
56
+
const installed = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.getExtension(id), [id]);
57
+
58
+
return (
59
+
<div className="moonbase-crash-extensionCard">
60
+
<div className="moonbase-crash-extensionCard-meta">
61
+
<div className="moonbase-crash-extensionCard-title">
62
+
{ext.updateManifest.meta?.name ?? ext.updateManifest.id}
63
+
</div>
64
+
<div className="moonbase-crash-extensionCard-version">{`v${installed?.manifest?.version ?? "???"} -> v${
65
+
ext.version
66
+
}`}</div>
67
+
</div>
68
+
<div className="moonbase-crash-extensionCard-button">
69
+
<Button
70
+
color={Button.Colors.GREEN}
71
+
disabled={state !== UpdateState.Ready}
72
+
onClick={() => {
73
+
setState(UpdateState.Working);
74
+
MoonbaseSettingsStore.installExtension(id)
75
+
.then(() => setState(UpdateState.Installed))
76
+
.catch(() => setState(UpdateState.Failed));
77
+
}}
78
+
>
79
+
{extensionButtonStrings[state]}
80
+
</Button>
81
+
</div>
82
+
</div>
83
+
);
84
+
}
85
+
86
+
function ExtensionDisableCard({ ext }: { ext: DetectedExtension }) {
87
+
function disableWithDependents() {
88
+
const disable = new Set<string>();
89
+
disable.add(ext.id);
90
+
for (const [id, dependencies] of moonlightNode.processedExtensions.dependencyGraph) {
91
+
if (dependencies?.has(ext.id)) disable.add(id);
92
+
}
93
+
94
+
const config = structuredClone(moonlightNode.config);
95
+
for (const id in config.extensions) {
96
+
if (!disable.has(id)) continue;
97
+
if (typeof config.extensions[id] === "boolean") config.extensions[id] = false;
98
+
else (config.extensions[id] as ConfigExtension).enabled = false;
99
+
}
100
+
101
+
let msg = `Are you sure you want to disable "${ext.manifest.meta?.name ?? ext.id}"`;
102
+
if (disable.size > 1) {
103
+
msg += ` and its ${disable.size - 1} dependent${disable.size - 1 === 1 ? "" : "s"}`;
104
+
}
105
+
msg += "?";
106
+
107
+
if (confirm(msg)) {
108
+
moonlightNode.writeConfig(config);
109
+
window.location.reload();
110
+
}
111
+
}
112
+
113
+
return (
114
+
<div className="moonbase-crash-extensionCard">
115
+
<div className="moonbase-crash-extensionCard-meta">
116
+
<div className="moonbase-crash-extensionCard-title">{ext.manifest.meta?.name ?? ext.id}</div>
117
+
<div className="moonbase-crash-extensionCard-version">{`v${ext.manifest.version ?? "???"}`}</div>
118
+
</div>
119
+
<div className="moonbase-crash-extensionCard-button">
120
+
<Button color={Button.Colors.RED} onClick={disableWithDependents}>
121
+
Disable
122
+
</Button>
123
+
</div>
124
+
</div>
125
+
);
126
+
}
127
+
128
+
export function wrapAction({ action, state }: WrapperProps) {
129
+
const [tab, setTab] = React.useState("crash");
130
+
131
+
const { updates, updateCount } = useStateFromStoresObject([MoonbaseSettingsStore], () => {
132
+
const { updates } = MoonbaseSettingsStore;
133
+
return {
134
+
updates: Object.entries(updates),
135
+
updateCount: Object.keys(updates).length
136
+
};
137
+
});
138
+
139
+
const causes = React.useMemo(() => {
140
+
const causes = new Set<string>();
141
+
if (state.error.stack) {
142
+
for (const [, , id] of state.error.stack.matchAll(MODULE_REGEX))
143
+
for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
144
+
}
145
+
for (const [, , id] of state.info.componentStack.matchAll(MODULE_REGEX))
146
+
for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
147
+
148
+
for (const [path, id] of Object.entries(moonlight.moonmap.modules)) {
149
+
const MAPPING_REGEX = new RegExp(
150
+
// @ts-expect-error Only Firefox has RegExp.escape
151
+
`(${RegExp.escape ? RegExp.escape(path) : path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`,
152
+
"g"
153
+
);
154
+
155
+
if (state.error.stack) {
156
+
for (const match of state.error.stack.matchAll(MAPPING_REGEX))
157
+
if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
158
+
}
159
+
for (const match of state.info.componentStack.matchAll(MAPPING_REGEX))
160
+
if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
161
+
}
162
+
163
+
return [...causes];
164
+
}, []);
165
+
166
+
return (
167
+
<div className="moonbase-crash-wrapper">
168
+
{action}
169
+
<TabBar
170
+
className={`${DiscoveryClasses.tabBar} moonbase-crash-tabs`}
171
+
type="top"
172
+
selectedItem={tab}
173
+
onItemSelect={(v) => setTab(v)}
174
+
>
175
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id="crash">
176
+
Crash details
177
+
</TabBar.Item>
178
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id="extensions" disabled={updateCount === 0}>
179
+
{`Extension updates (${updateCount})`}
180
+
</TabBar.Item>
181
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id="causes" disabled={causes.length === 0}>
182
+
{`Possible causes (${causes.length})`}
183
+
</TabBar.Item>
184
+
</TabBar>
185
+
{tab === "crash" ? (
186
+
<div className="moonbase-crash-details-wrapper">
187
+
<pre className="moonbase-crash-details">
188
+
<code>
189
+
{state.error.stack}
190
+
{"\n\nComponent stack:"}
191
+
{state.info.componentStack}
192
+
</code>
193
+
</pre>
194
+
</div>
195
+
) : null}
196
+
{tab === "extensions" ? (
197
+
<div className="moonbase-crash-extensions">
198
+
{updates.map(([id, ext]) => (
199
+
<ExtensionUpdateCard id={Number(id)} ext={ext} />
200
+
))}
201
+
</div>
202
+
) : null}
203
+
{tab === "causes" ? (
204
+
<div className="moonbase-crash-extensions">
205
+
{causes
206
+
.map((ext) => moonlightNode.extensions.find((e) => e.id === ext)!)
207
+
.map((ext) => (
208
+
<ExtensionDisableCard ext={ext} />
209
+
))}
210
+
</div>
211
+
) : null}
212
+
</div>
213
+
);
214
+
}
215
+
216
+
export function UpdateText({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) {
217
+
if (!state.__moonlight_update) {
218
+
setState({
219
+
...state,
220
+
__moonlight_update: UpdateState.Ready
221
+
});
222
+
}
223
+
const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion);
224
+
225
+
return newVersion == null ? null : (
226
+
<p>{state.__moonlight_update !== undefined ? updateStrings[state.__moonlight_update] : ""}</p>
227
+
);
228
+
}
229
+
230
+
export function UpdateButton({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) {
231
+
const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion);
232
+
return newVersion == null ||
233
+
state.__moonlight_update === UpdateState.Installed ||
234
+
state.__moonlight_update === undefined ? null : (
235
+
<Button
236
+
size={Button.Sizes.LARGE}
237
+
disabled={state.__moonlight_update !== UpdateState.Ready}
238
+
onClick={() => {
239
+
setState({
240
+
...state,
241
+
__moonlight_update: UpdateState.Working
242
+
});
243
+
244
+
MoonbaseSettingsStore.updateMoonlight()
245
+
.then(() => {
246
+
setState({
247
+
...state,
248
+
__moonlight_update: UpdateState.Installed
249
+
});
250
+
})
251
+
.catch((e) => {
252
+
logger.error(e);
253
+
setState({
254
+
...state,
255
+
__moonlight_update: UpdateState.Failed
256
+
});
257
+
});
258
+
}}
259
+
>
260
+
{state.__moonlight_update !== undefined ? buttonStrings[state.__moonlight_update] : ""}
261
+
</Button>
262
+
);
263
+
}
+25
-38
packages/core-extensions/src/moonbase/webpackModules/settings.tsx
+25
-38
packages/core-extensions/src/moonbase/webpackModules/settings.tsx
···
1
import settings from "@moonlight-mod/wp/settings_settings";
2
import React from "@moonlight-mod/wp/react";
3
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
4
-
import { Moonbase, pages } from "@moonlight-mod/wp/moonbase_ui";
5
-
6
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
7
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
8
-
9
-
import Update from "./ui/update";
10
-
11
-
const { MenuItem, Text, Breadcrumbs } = Components;
12
-
13
-
const Margins = spacepack.require("discord/styles/shared/Margins.css");
14
-
15
-
const { open } = spacepack.findByExports("setSection", "clearSubsection")[0]
16
-
.exports.Z;
17
18
const notice = {
19
stores: [MoonbaseSettingsStore],
20
element: () => {
21
// Require it here because lazy loading SUX
22
-
const SettingsNotice = spacepack.findByCode(
23
-
"onSaveButtonColor",
24
-
"FocusRingScope"
25
-
)[0].exports.Z;
26
return (
27
<SettingsNotice
28
submitting={MoonbaseSettingsStore.submitting}
···
37
}
38
};
39
40
-
function addSection(
41
-
id: string,
42
-
name: string,
43
-
element: React.FunctionComponent
44
-
) {
45
-
settings.addSection(`moonbase-${id}`, name, element, null, -2, notice);
46
}
47
48
// FIXME: move to component types
···
53
54
function renderBreadcrumb(crumb: Breadcrumb, last: boolean) {
55
return (
56
-
<Text
57
-
variant="heading-lg/semibold"
58
-
tag="h2"
59
-
color={last ? "header-primary" : "header-secondary"}
60
-
>
61
{crumb.label}
62
</Text>
63
);
64
}
65
66
-
if (
67
-
MoonbaseSettingsStore.getExtensionConfigRaw<boolean>(
68
-
"moonbase",
69
-
"sections",
70
-
false
71
-
)
72
-
) {
73
-
settings.addHeader("Moonbase", -2);
74
75
-
for (const page of pages) {
76
addSection(page.id, page.name, () => {
77
const breadcrumbs = [
78
{ id: "moonbase", label: "Moonbase" },
···
89
{page.name}
90
</Breadcrumbs>
91
92
<Update />
93
94
<page.element />
···
96
);
97
});
98
}
99
} else {
100
-
settings.addSection("moonbase", "Moonbase", Moonbase, null, -2, notice);
101
102
settings.addSectionMenuItems(
103
"moonbase",
···
106
key={page.id}
107
id={`moonbase-${page.id}`}
108
label={page.name}
109
-
action={() => open("moonbase", i)}
110
/>
111
))
112
);
···
1
import settings from "@moonlight-mod/wp/settings_settings";
2
import React from "@moonlight-mod/wp/react";
3
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
4
+
import { Moonbase, pages, RestartAdviceMessage, Update } from "@moonlight-mod/wp/moonbase_ui";
5
+
import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators";
6
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
7
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
8
+
import { Text, Breadcrumbs } from "@moonlight-mod/wp/discord/components/common/index";
9
+
import { MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu";
10
11
const notice = {
12
stores: [MoonbaseSettingsStore],
13
element: () => {
14
// Require it here because lazy loading SUX
15
+
const SettingsNotice = spacepack.require("discord/components/common/SettingsNotice").default;
16
return (
17
<SettingsNotice
18
submitting={MoonbaseSettingsStore.submitting}
···
27
}
28
};
29
30
+
const oldLocation = MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "oldLocation", false);
31
+
const position = oldLocation ? -2 : -9999;
32
+
33
+
function addSection(id: string, name: string, element: React.FunctionComponent) {
34
+
settings.addSection(`moonbase-${id}`, name, element, null, position, notice);
35
}
36
37
// FIXME: move to component types
···
42
43
function renderBreadcrumb(crumb: Breadcrumb, last: boolean) {
44
return (
45
+
<Text variant="heading-lg/semibold" tag="h2" color={last ? "header-primary" : "header-secondary"}>
46
{crumb.label}
47
</Text>
48
);
49
}
50
51
+
if (!oldLocation) {
52
+
settings.addDivider(position);
53
+
}
54
+
55
+
if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "sections", false)) {
56
+
if (oldLocation) settings.addHeader("Moonbase", position);
57
58
+
const _pages = oldLocation ? pages : pages.reverse();
59
+
for (const page of _pages) {
60
addSection(page.id, page.name, () => {
61
const breadcrumbs = [
62
{ id: "moonbase", label: "Moonbase" },
···
73
{page.name}
74
</Breadcrumbs>
75
76
+
<RestartAdviceMessage />
77
<Update />
78
79
<page.element />
···
81
);
82
});
83
}
84
+
85
+
if (!oldLocation) settings.addHeader("Moonbase", position);
86
} else {
87
+
settings.addSection("moonbase", "Moonbase", Moonbase, null, position, notice);
88
89
settings.addSectionMenuItems(
90
"moonbase",
···
93
key={page.id}
94
id={`moonbase-${page.id}`}
95
label={page.name}
96
+
action={() => UserSettingsModalActionCreators.open("moonbase", i.toString())}
97
/>
98
))
99
);
+245
-138
packages/core-extensions/src/moonbase/webpackModules/stores.ts
+245
-138
packages/core-extensions/src/moonbase/webpackModules/stores.ts
···
1
-
import { Config, ExtensionLoadSource } from "@moonlight-mod/types";
2
import {
3
ExtensionState,
4
MoonbaseExtension,
5
MoonbaseNatives,
6
-
RepositoryManifest
7
} from "../types";
8
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
9
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
10
import getNatives from "../native";
11
import { mainRepo } from "@moonlight-mod/types/constants";
12
-
import {
13
-
checkExtensionCompat,
14
-
ExtensionCompat
15
-
} from "@moonlight-mod/core/extension/loader";
16
import { CustomComponent } from "@moonlight-mod/types/coreExtensions/moonbase";
17
18
const logger = moonlight.getLogger("moonbase");
19
···
21
if (moonlightNode.isBrowser) natives = getNatives();
22
23
class MoonbaseSettingsStore extends Store<any> {
24
-
private origConfig: Config;
25
private config: Config;
26
private extensionIndex: number;
27
-
private configComponents: Record<string, Record<string, CustomComponent>> =
28
-
{};
29
30
modified: boolean;
31
submitting: boolean;
32
installing: boolean;
33
34
newVersion: string | null;
35
shouldShowNotice: boolean;
36
37
extensions: { [id: number]: MoonbaseExtension };
38
updates: {
39
[id: number]: {
···
46
constructor() {
47
super(Dispatcher);
48
49
-
this.origConfig = moonlightNode.config;
50
-
this.config = this.clone(this.origConfig);
51
this.extensionIndex = 0;
52
53
this.modified = false;
···
64
this.extensions[uniqueId] = {
65
...ext,
66
uniqueId,
67
-
state: moonlight.enabledExtensions.has(ext.id)
68
-
? ExtensionState.Enabled
69
-
: ExtensionState.Disabled,
70
compat: checkExtensionCompat(ext.manifest),
71
hasUpdate: false
72
};
73
}
74
75
-
natives!
76
-
.fetchRepositories(this.config.repositories)
77
-
.then((ret) => {
78
-
for (const [repo, exts] of Object.entries(ret)) {
79
-
try {
80
-
for (const ext of exts) {
81
-
const uniqueId = this.extensionIndex++;
82
-
const extensionData = {
83
-
id: ext.id,
84
-
uniqueId,
85
-
manifest: ext,
86
-
source: { type: ExtensionLoadSource.Normal, url: repo },
87
-
state: ExtensionState.NotDownloaded,
88
-
compat: ExtensionCompat.Compatible,
89
-
hasUpdate: false
90
-
};
91
92
-
// Don't present incompatible updates
93
-
if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible)
94
-
continue;
95
96
-
const existing = this.getExisting(extensionData);
97
-
if (existing != null) {
98
-
// Make sure the download URL is properly updated
99
-
for (const [id, e] of Object.entries(this.extensions)) {
100
-
if (e.id === ext.id && e.source.url === repo) {
101
-
this.extensions[parseInt(id)].manifest = {
102
-
...e.manifest,
103
-
download: ext.download
104
-
};
105
-
break;
106
-
}
107
-
}
108
109
-
if (this.hasUpdate(extensionData)) {
110
-
this.updates[existing.uniqueId] = {
111
-
version: ext.version!,
112
-
download: ext.download,
113
-
updateManifest: ext
114
-
};
115
-
existing.hasUpdate = true;
116
-
}
117
118
-
continue;
119
-
}
120
121
-
this.extensions[uniqueId] = extensionData;
122
-
}
123
-
} catch (e) {
124
-
logger.error(`Error processing repository ${repo}`, e);
125
}
126
}
127
128
-
this.emitChange();
129
-
})
130
-
.then(() =>
131
-
this.getExtensionConfigRaw("moonbase", "updateChecking", true)
132
-
? natives!.checkForMoonlightUpdate()
133
-
: new Promise<null>((resolve) => resolve(null))
134
-
)
135
-
.then((version) => {
136
-
this.newVersion = version;
137
-
this.emitChange();
138
-
})
139
-
.then(() => {
140
-
this.shouldShowNotice =
141
-
this.newVersion != null || Object.keys(this.updates).length > 0;
142
-
this.emitChange();
143
-
});
144
}
145
146
private getExisting(ext: MoonbaseExtension) {
147
-
return Object.values(this.extensions).find(
148
-
(e) => e.id === ext.id && e.source.url === ext.source.url
149
-
);
150
}
151
152
private hasUpdate(ext: MoonbaseExtension) {
153
-
const existing = Object.values(this.extensions).find(
154
-
(e) => e.id === ext.id && e.source.url === ext.source.url
155
-
);
156
if (existing == null) return false;
157
158
-
return (
159
-
existing.manifest.version !== ext.manifest.version &&
160
-
existing.state !== ExtensionState.NotDownloaded
161
-
);
162
}
163
164
// Jank
165
private isModified() {
166
-
const orig = JSON.stringify(this.origConfig);
167
const curr = JSON.stringify(this.config);
168
return orig !== curr;
169
}
···
172
return this.submitting || this.installing;
173
}
174
175
showNotice() {
176
return this.modified;
177
}
···
181
}
182
183
getExtensionUniqueId(id: string) {
184
-
return Object.values(this.extensions).find((ext) => ext.id === id)
185
-
?.uniqueId;
186
}
187
188
getExtensionConflicting(uniqueId: number) {
189
const ext = this.getExtension(uniqueId);
190
if (ext.state !== ExtensionState.NotDownloaded) return false;
191
return Object.values(this.extensions).some(
192
-
(e) =>
193
-
e.id === ext.id &&
194
-
e.uniqueId !== uniqueId &&
195
-
e.state !== ExtensionState.NotDownloaded
196
);
197
}
198
···
215
216
getExtensionConfig<T>(uniqueId: number, key: string): T | undefined {
217
const ext = this.getExtension(uniqueId);
218
-
const defaultValue = ext.manifest.settings?.[key]?.default;
219
-
const clonedDefaultValue = this.clone(defaultValue);
220
-
const cfg = this.config.extensions[ext.id];
221
-
222
-
if (cfg == null || typeof cfg === "boolean") return clonedDefaultValue;
223
-
return cfg.config?.[key] ?? clonedDefaultValue;
224
}
225
226
-
getExtensionConfigRaw<T>(
227
-
id: string,
228
-
key: string,
229
-
defaultValue: T | undefined
230
-
): T | undefined {
231
const cfg = this.config.extensions[id];
232
-
233
if (cfg == null || typeof cfg === "boolean") return defaultValue;
234
return cfg.config?.[key] ?? defaultValue;
235
}
236
237
getExtensionConfigName(uniqueId: number, key: string) {
238
const ext = this.getExtension(uniqueId);
239
-
return ext.manifest.settings?.[key]?.displayName ?? key;
240
}
241
242
getExtensionConfigDescription(uniqueId: number, key: string) {
243
const ext = this.getExtension(uniqueId);
244
-
return ext.manifest.settings?.[key]?.description;
245
}
246
247
setExtensionConfig(id: string, key: string, value: any) {
248
-
const oldConfig = this.config.extensions[id];
249
-
const newConfig =
250
-
typeof oldConfig === "boolean"
251
-
? {
252
-
enabled: oldConfig,
253
-
config: { [key]: value }
254
-
}
255
-
: {
256
-
...oldConfig,
257
-
config: { ...(oldConfig?.config ?? {}), [key]: value }
258
-
};
259
-
260
-
this.config.extensions[id] = newConfig;
261
this.modified = this.isModified();
262
this.emitChange();
263
}
···
284
this.emitChange();
285
}
286
287
async installExtension(uniqueId: number) {
288
const ext = this.getExtension(uniqueId);
289
if (!("download" in ext.manifest)) {
···
299
this.extensions[uniqueId].state = ExtensionState.Disabled;
300
}
301
302
-
if (update != null)
303
-
this.extensions[uniqueId].compat = checkExtensionCompat(
304
-
update.updateManifest
305
-
);
306
307
delete this.updates[uniqueId];
308
} catch (e) {
···
310
}
311
312
this.installing = false;
313
this.emitChange();
314
}
315
···
335
336
const deps: Record<string, MoonbaseExtension[]> = {};
337
for (const dep of missingDeps) {
338
-
const candidates = Object.values(this.extensions).filter(
339
-
(e) => e.id === dep
340
-
);
341
342
deps[dep] = candidates.sort((a, b) => {
343
const aRank = this.getRank(a);
344
const bRank = this.getRank(b);
345
if (aRank === bRank) {
346
-
const repoIndex = this.config.repositories.indexOf(a.source.url!);
347
-
const otherRepoIndex = this.config.repositories.indexOf(
348
-
b.source.url!
349
-
);
350
return repoIndex - otherRepoIndex;
351
} else {
352
return bRank - aRank;
···
370
}
371
372
this.installing = false;
373
this.emitChange();
374
}
375
376
async updateMoonlight() {
377
-
await natives.updateMoonlight();
378
}
379
380
getConfigOption<K extends keyof Config>(key: K): Config[K] {
···
392
return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id;
393
}
394
395
-
registerConfigComponent(
396
-
ext: string,
397
-
name: string,
398
-
component: CustomComponent
399
-
) {
400
if (!(ext in this.configComponents)) this.configComponents[ext] = {};
401
this.configComponents[ext][name] = component;
402
}
···
405
return this.configComponents[ext]?.[name];
406
}
407
408
writeConfig() {
409
this.submitting = true;
410
411
moonlightNode.writeConfig(this.config);
412
-
this.origConfig = this.clone(this.config);
413
414
this.submitting = false;
415
this.modified = false;
416
this.emitChange();
417
}
418
419
reset() {
420
this.submitting = false;
421
this.modified = false;
422
-
this.config = this.clone(this.origConfig);
423
this.emitChange();
424
}
425
426
// Required because electron likes to make it immutable sometimes.
···
1
+
import { Config, ExtensionEnvironment, ExtensionLoadSource, ExtensionSettingsAdvice } from "@moonlight-mod/types";
2
import {
3
ExtensionState,
4
MoonbaseExtension,
5
MoonbaseNatives,
6
+
RepositoryManifest,
7
+
RestartAdvice,
8
+
UpdateState
9
} from "../types";
10
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
11
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
12
import getNatives from "../native";
13
import { mainRepo } from "@moonlight-mod/types/constants";
14
+
import { checkExtensionCompat, ExtensionCompat } from "@moonlight-mod/core/extension/loader";
15
import { CustomComponent } from "@moonlight-mod/types/coreExtensions/moonbase";
16
+
import { getConfigOption, setConfigOption } from "@moonlight-mod/core/util/config";
17
+
import diff from "microdiff";
18
19
const logger = moonlight.getLogger("moonbase");
20
···
22
if (moonlightNode.isBrowser) natives = getNatives();
23
24
class MoonbaseSettingsStore extends Store<any> {
25
+
private initialConfig: Config;
26
+
private savedConfig: Config;
27
private config: Config;
28
private extensionIndex: number;
29
+
private configComponents: Record<string, Record<string, CustomComponent>> = {};
30
31
modified: boolean;
32
submitting: boolean;
33
installing: boolean;
34
35
+
#updateState = UpdateState.Ready;
36
+
get updateState() {
37
+
return this.#updateState;
38
+
}
39
newVersion: string | null;
40
shouldShowNotice: boolean;
41
42
+
restartAdvice = RestartAdvice.NotNeeded;
43
+
44
extensions: { [id: number]: MoonbaseExtension };
45
updates: {
46
[id: number]: {
···
53
constructor() {
54
super(Dispatcher);
55
56
+
this.initialConfig = moonlightNode.config;
57
+
this.savedConfig = moonlightNode.config;
58
+
this.config = this.clone(this.savedConfig);
59
this.extensionIndex = 0;
60
61
this.modified = false;
···
72
this.extensions[uniqueId] = {
73
...ext,
74
uniqueId,
75
+
state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled : ExtensionState.Disabled,
76
compat: checkExtensionCompat(ext.manifest),
77
hasUpdate: false
78
};
79
}
80
81
+
this.checkUpdates();
82
+
}
83
84
+
async checkUpdates() {
85
+
await Promise.all([this.checkExtensionUpdates(), this.checkMoonlightUpdates()]);
86
+
this.shouldShowNotice = this.newVersion != null || Object.keys(this.updates).length > 0;
87
+
this.emitChange();
88
+
}
89
90
+
private async checkExtensionUpdates() {
91
+
const repositories = await natives!.fetchRepositories(this.savedConfig.repositories);
92
93
+
// Reset update state
94
+
for (const id in this.extensions) {
95
+
const ext = this.extensions[id];
96
+
ext.hasUpdate = false;
97
+
ext.changelog = undefined;
98
+
}
99
+
this.updates = {};
100
101
+
for (const [repo, exts] of Object.entries(repositories)) {
102
+
for (const ext of exts) {
103
+
const uniqueId = this.extensionIndex++;
104
+
const extensionData = {
105
+
id: ext.id,
106
+
uniqueId,
107
+
manifest: ext,
108
+
source: { type: ExtensionLoadSource.Normal, url: repo },
109
+
state: ExtensionState.NotDownloaded,
110
+
compat: ExtensionCompat.Compatible,
111
+
hasUpdate: false
112
+
};
113
114
+
// Don't present incompatible updates
115
+
if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue;
116
+
117
+
const existing = this.getExisting(extensionData);
118
+
if (existing != null) {
119
+
// Make sure the download URL is properly updated
120
+
existing.manifest = {
121
+
...existing.manifest,
122
+
download: ext.download
123
+
};
124
+
125
+
if (this.hasUpdate(extensionData)) {
126
+
this.updates[existing.uniqueId] = {
127
+
version: ext.version!,
128
+
download: ext.download,
129
+
updateManifest: ext
130
+
};
131
+
existing.hasUpdate = true;
132
+
existing.changelog = ext.meta?.changelog;
133
}
134
+
} else {
135
+
this.extensions[uniqueId] = extensionData;
136
}
137
+
}
138
+
}
139
+
}
140
141
+
private async checkMoonlightUpdates() {
142
+
this.newVersion = this.getExtensionConfigRaw("moonbase", "updateChecking", true)
143
+
? await natives!.checkForMoonlightUpdate()
144
+
: null;
145
}
146
147
private getExisting(ext: MoonbaseExtension) {
148
+
return Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url);
149
}
150
151
private hasUpdate(ext: MoonbaseExtension) {
152
+
const existing = Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url);
153
if (existing == null) return false;
154
155
+
return existing.manifest.version !== ext.manifest.version && existing.state !== ExtensionState.NotDownloaded;
156
}
157
158
// Jank
159
private isModified() {
160
+
const orig = JSON.stringify(this.savedConfig);
161
const curr = JSON.stringify(this.config);
162
return orig !== curr;
163
}
···
166
return this.submitting || this.installing;
167
}
168
169
+
// Required for the settings store contract
170
showNotice() {
171
return this.modified;
172
}
···
176
}
177
178
getExtensionUniqueId(id: string) {
179
+
return Object.values(this.extensions).find((ext) => ext.id === id)?.uniqueId;
180
}
181
182
getExtensionConflicting(uniqueId: number) {
183
const ext = this.getExtension(uniqueId);
184
if (ext.state !== ExtensionState.NotDownloaded) return false;
185
return Object.values(this.extensions).some(
186
+
(e) => e.id === ext.id && e.uniqueId !== uniqueId && e.state !== ExtensionState.NotDownloaded
187
);
188
}
189
···
206
207
getExtensionConfig<T>(uniqueId: number, key: string): T | undefined {
208
const ext = this.getExtension(uniqueId);
209
+
const settings = ext.settingsOverride ?? ext.manifest.settings;
210
+
return getConfigOption(ext.id, key, this.config, settings);
211
}
212
213
+
getExtensionConfigRaw<T>(id: string, key: string, defaultValue: T | undefined): T | undefined {
214
const cfg = this.config.extensions[id];
215
if (cfg == null || typeof cfg === "boolean") return defaultValue;
216
return cfg.config?.[key] ?? defaultValue;
217
}
218
219
getExtensionConfigName(uniqueId: number, key: string) {
220
const ext = this.getExtension(uniqueId);
221
+
const settings = ext.settingsOverride ?? ext.manifest.settings;
222
+
return settings?.[key]?.displayName ?? key;
223
}
224
225
getExtensionConfigDescription(uniqueId: number, key: string) {
226
const ext = this.getExtension(uniqueId);
227
+
const settings = ext.settingsOverride ?? ext.manifest.settings;
228
+
return settings?.[key]?.description;
229
}
230
231
setExtensionConfig(id: string, key: string, value: any) {
232
+
setConfigOption(this.config, id, key, value);
233
this.modified = this.isModified();
234
this.emitChange();
235
}
···
256
this.emitChange();
257
}
258
259
+
dismissAllExtensionUpdates() {
260
+
for (const id in this.extensions) {
261
+
this.extensions[id].hasUpdate = false;
262
+
}
263
+
this.emitChange();
264
+
}
265
+
266
+
async updateAllExtensions() {
267
+
for (const id of Object.keys(this.updates)) {
268
+
try {
269
+
await this.installExtension(parseInt(id));
270
+
} catch (e) {
271
+
logger.error("Error bulk updating extension", id, e);
272
+
}
273
+
}
274
+
}
275
+
276
async installExtension(uniqueId: number) {
277
const ext = this.getExtension(uniqueId);
278
if (!("download" in ext.manifest)) {
···
288
this.extensions[uniqueId].state = ExtensionState.Disabled;
289
}
290
291
+
if (update != null) {
292
+
const existing = this.extensions[uniqueId];
293
+
existing.settingsOverride = update.updateManifest.settings;
294
+
existing.compat = checkExtensionCompat(update.updateManifest);
295
+
existing.manifest = update.updateManifest;
296
+
existing.changelog = update.updateManifest.meta?.changelog;
297
+
}
298
299
delete this.updates[uniqueId];
300
} catch (e) {
···
302
}
303
304
this.installing = false;
305
+
this.restartAdvice = this.#computeRestartAdvice();
306
this.emitChange();
307
}
308
···
328
329
const deps: Record<string, MoonbaseExtension[]> = {};
330
for (const dep of missingDeps) {
331
+
const candidates = Object.values(this.extensions).filter((e) => e.id === dep);
332
333
deps[dep] = candidates.sort((a, b) => {
334
const aRank = this.getRank(a);
335
const bRank = this.getRank(b);
336
if (aRank === bRank) {
337
+
const repoIndex = this.savedConfig.repositories.indexOf(a.source.url!);
338
+
const otherRepoIndex = this.savedConfig.repositories.indexOf(b.source.url!);
339
return repoIndex - otherRepoIndex;
340
} else {
341
return bRank - aRank;
···
359
}
360
361
this.installing = false;
362
+
this.restartAdvice = this.#computeRestartAdvice();
363
this.emitChange();
364
}
365
366
async updateMoonlight() {
367
+
this.#updateState = UpdateState.Working;
368
+
this.emitChange();
369
+
370
+
await natives
371
+
.updateMoonlight()
372
+
.then(() => (this.#updateState = UpdateState.Installed))
373
+
.catch((e) => {
374
+
logger.error(e);
375
+
this.#updateState = UpdateState.Failed;
376
+
});
377
+
378
+
this.emitChange();
379
}
380
381
getConfigOption<K extends keyof Config>(key: K): Config[K] {
···
393
return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id;
394
}
395
396
+
registerConfigComponent(ext: string, name: string, component: CustomComponent) {
397
if (!(ext in this.configComponents)) this.configComponents[ext] = {};
398
this.configComponents[ext][name] = component;
399
}
···
402
return this.configComponents[ext]?.[name];
403
}
404
405
+
#computeRestartAdvice() {
406
+
// If moonlight update needs a restart, always hide advice.
407
+
if (this.#updateState === UpdateState.Installed) return RestartAdvice.NotNeeded;
408
+
409
+
const i = this.initialConfig; // Initial config, from startup
410
+
const n = this.config; // New config about to be saved
411
+
412
+
let returnedAdvice = RestartAdvice.NotNeeded;
413
+
const updateAdvice = (r: RestartAdvice) => (returnedAdvice < r ? (returnedAdvice = r) : returnedAdvice);
414
+
415
+
// Top-level keys, repositories is not needed here because Moonbase handles it.
416
+
if (i.patchAll !== n.patchAll) updateAdvice(RestartAdvice.ReloadNeeded);
417
+
if (i.loggerLevel !== n.loggerLevel) updateAdvice(RestartAdvice.ReloadNeeded);
418
+
if (diff(i.devSearchPaths ?? [], n.devSearchPaths ?? [], { cyclesFix: false }).length !== 0)
419
+
return updateAdvice(RestartAdvice.RestartNeeded);
420
+
421
+
// Extension specific logic
422
+
for (const id in n.extensions) {
423
+
// Installed extension (might not be detected yet)
424
+
const ext = Object.values(this.extensions).find((e) => e.id === id && e.state !== ExtensionState.NotDownloaded);
425
+
// Installed and detected extension
426
+
const detected = moonlightNode.extensions.find((e) => e.id === id);
427
+
428
+
// If it's not installed at all, we don't care
429
+
if (!ext) continue;
430
+
431
+
const initState = i.extensions[id];
432
+
const newState = n.extensions[id];
433
+
434
+
const newEnabled = typeof newState === "boolean" ? newState : newState.enabled;
435
+
// If it's enabled but not detected yet, restart.
436
+
if (newEnabled && !detected) {
437
+
return updateAdvice(RestartAdvice.RestartNeeded);
438
+
}
439
+
440
+
// Toggling extensions specifically wants to rely on the initial state,
441
+
// that's what was considered when loading extensions.
442
+
const initEnabled = initState && (typeof initState === "boolean" ? initState : initState.enabled);
443
+
if (initEnabled !== newEnabled || detected?.manifest.version !== ext.manifest.version) {
444
+
// If we have the extension locally, we confidently know if it has host/preload scripts.
445
+
// If not, we have to respect the environment specified in the manifest.
446
+
// If that is the default, we can't know what's needed.
447
+
448
+
if (detected?.scripts.hostPath || detected?.scripts.nodePath) {
449
+
return updateAdvice(RestartAdvice.RestartNeeded);
450
+
}
451
+
452
+
switch (ext.manifest.environment) {
453
+
case ExtensionEnvironment.Both:
454
+
case ExtensionEnvironment.Web:
455
+
updateAdvice(RestartAdvice.ReloadNeeded);
456
+
continue;
457
+
case ExtensionEnvironment.Desktop:
458
+
return updateAdvice(RestartAdvice.RestartNeeded);
459
+
default:
460
+
updateAdvice(RestartAdvice.ReloadNeeded);
461
+
continue;
462
+
}
463
+
}
464
+
465
+
const initConfig = typeof initState === "boolean" ? {} : { ...initState?.config };
466
+
const newConfig = typeof newState === "boolean" ? {} : { ...newState?.config };
467
+
468
+
const def = ext.manifest.settings;
469
+
if (!def) continue;
470
+
471
+
for (const key in def) {
472
+
const defaultValue = def[key].default;
473
+
474
+
initConfig[key] ??= defaultValue;
475
+
newConfig[key] ??= defaultValue;
476
+
}
477
+
478
+
const changedKeys = diff(initConfig, newConfig, { cyclesFix: false }).map((c) => c.path[0]);
479
+
for (const key in def) {
480
+
if (!changedKeys.includes(key)) continue;
481
+
482
+
const advice = def[key].advice;
483
+
switch (advice) {
484
+
case ExtensionSettingsAdvice.None:
485
+
updateAdvice(RestartAdvice.NotNeeded);
486
+
continue;
487
+
case ExtensionSettingsAdvice.Reload:
488
+
updateAdvice(RestartAdvice.ReloadNeeded);
489
+
continue;
490
+
case ExtensionSettingsAdvice.Restart:
491
+
updateAdvice(RestartAdvice.RestartNeeded);
492
+
continue;
493
+
default:
494
+
updateAdvice(RestartAdvice.ReloadSuggested);
495
+
}
496
+
}
497
+
}
498
+
499
+
return returnedAdvice;
500
+
}
501
+
502
writeConfig() {
503
this.submitting = true;
504
+
this.restartAdvice = this.#computeRestartAdvice();
505
+
const modifiedRepos = diff(this.savedConfig.repositories, this.config.repositories);
506
507
moonlightNode.writeConfig(this.config);
508
+
this.savedConfig = this.clone(this.config);
509
510
this.submitting = false;
511
this.modified = false;
512
this.emitChange();
513
+
514
+
if (modifiedRepos.length !== 0) this.checkUpdates();
515
}
516
517
reset() {
518
this.submitting = false;
519
this.modified = false;
520
+
this.config = this.clone(this.savedConfig);
521
this.emitChange();
522
+
}
523
+
524
+
restartDiscord() {
525
+
if (moonlightNode.isBrowser) {
526
+
window.location.reload();
527
+
} else {
528
+
// @ts-expect-error TODO: DiscordNative
529
+
window.DiscordNative.app.relaunch();
530
+
}
531
}
532
533
// Required because electron likes to make it immutable sometimes.
+47
packages/core-extensions/src/moonbase/webpackModules/ui/HelpMessage.tsx
+47
packages/core-extensions/src/moonbase/webpackModules/ui/HelpMessage.tsx
···
···
1
+
import React from "@moonlight-mod/wp/react";
2
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
3
+
import { Text } from "@moonlight-mod/wp/discord/components/common/index";
4
+
import HelpMessageClasses from "@moonlight-mod/wp/discord/components/common/HelpMessage.css";
5
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
6
+
7
+
// reimpl of HelpMessage but with a custom icon
8
+
export default function HelpMessage({
9
+
className,
10
+
text,
11
+
icon,
12
+
children,
13
+
type = "info"
14
+
}: {
15
+
className?: string;
16
+
text: string;
17
+
icon: React.ComponentType<any>;
18
+
type?: "warning" | "positive" | "error" | "info";
19
+
children?: React.ReactNode;
20
+
}) {
21
+
return (
22
+
<div
23
+
className={`${Margins.marginBottom20} ${HelpMessageClasses[type]} ${HelpMessageClasses.container} moonbase-help-message ${className}`}
24
+
>
25
+
<Flex direction={Flex.Direction.HORIZONTAL}>
26
+
<div
27
+
className={HelpMessageClasses.iconDiv}
28
+
style={{
29
+
alignItems: "center"
30
+
}}
31
+
>
32
+
{React.createElement(icon, {
33
+
size: "sm",
34
+
color: "currentColor",
35
+
className: HelpMessageClasses.icon
36
+
})}
37
+
</div>
38
+
39
+
<Text variant="text-sm/medium" color="currentColor" className={HelpMessageClasses.text}>
40
+
{text}
41
+
</Text>
42
+
43
+
{children}
44
+
</Flex>
45
+
</div>
46
+
);
47
+
}
+43
packages/core-extensions/src/moonbase/webpackModules/ui/RestartAdvice.tsx
+43
packages/core-extensions/src/moonbase/webpackModules/ui/RestartAdvice.tsx
···
···
1
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
2
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
3
+
import { Button, CircleWarningIcon } from "@moonlight-mod/wp/discord/components/common/index";
4
+
import React from "@moonlight-mod/wp/react";
5
+
import { RestartAdvice } from "../../types";
6
+
import HelpMessage from "./HelpMessage";
7
+
8
+
const strings: Record<RestartAdvice, string> = {
9
+
[RestartAdvice.NotNeeded]: "how did you even",
10
+
[RestartAdvice.ReloadSuggested]: "A reload might be needed to apply some of the changed options.",
11
+
[RestartAdvice.ReloadNeeded]: "A reload is needed to apply some of the changed options.",
12
+
[RestartAdvice.RestartNeeded]: "A restart is needed to apply some of the changed options."
13
+
};
14
+
15
+
const buttonStrings: Record<RestartAdvice, string> = {
16
+
[RestartAdvice.NotNeeded]: "huh?",
17
+
[RestartAdvice.ReloadSuggested]: "Reload",
18
+
[RestartAdvice.ReloadNeeded]: "Reload",
19
+
[RestartAdvice.RestartNeeded]: "Restart"
20
+
};
21
+
22
+
const actions: Record<RestartAdvice, () => void> = {
23
+
[RestartAdvice.NotNeeded]: () => {},
24
+
[RestartAdvice.ReloadSuggested]: () => window.location.reload(),
25
+
[RestartAdvice.ReloadNeeded]: () => window.location.reload(),
26
+
[RestartAdvice.RestartNeeded]: () => MoonbaseSettingsStore.restartDiscord()
27
+
};
28
+
29
+
export default function RestartAdviceMessage() {
30
+
const restartAdvice = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.restartAdvice);
31
+
32
+
if (restartAdvice === RestartAdvice.NotNeeded) return null;
33
+
34
+
return (
35
+
<div className="moonbase-help-message-sticky">
36
+
<HelpMessage text={strings[restartAdvice]} icon={CircleWarningIcon} type="warning">
37
+
<Button color={Button.Colors.YELLOW} size={Button.Sizes.TINY} onClick={actions[restartAdvice]}>
38
+
{buttonStrings[restartAdvice]}
39
+
</Button>
40
+
</HelpMessage>
41
+
</div>
42
+
);
43
+
}
+110
packages/core-extensions/src/moonbase/webpackModules/ui/about.tsx
+110
packages/core-extensions/src/moonbase/webpackModules/ui/about.tsx
···
···
1
+
import {
2
+
Text,
3
+
useThemeContext,
4
+
Button,
5
+
AngleBracketsIcon,
6
+
BookCheckIcon,
7
+
ClydeIcon
8
+
} from "@moonlight-mod/wp/discord/components/common/index";
9
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
10
+
import React from "@moonlight-mod/wp/react";
11
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
12
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
13
+
14
+
const wordmark = "https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/heads/main/img/wordmark.png";
15
+
const wordmarkLight =
16
+
"https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/heads/main/img/wordmark-light.png";
17
+
18
+
function parse(str: string) {
19
+
return MarkupUtils.parse(str, true, {
20
+
allowHeading: true,
21
+
allowLinks: true,
22
+
allowList: true
23
+
});
24
+
}
25
+
26
+
function Dev({ name, picture, link }: { name: string; picture: string; link: string }) {
27
+
return (
28
+
<Button onClick={() => window.open(link)} color={Button.Colors.PRIMARY} className="moonbase-dev">
29
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap">
30
+
<img src={picture} alt={name} className="moonbase-dev-avatar" />
31
+
32
+
<Text variant="text-md/semibold">{name}</Text>
33
+
</Flex>
34
+
</Button>
35
+
);
36
+
}
37
+
38
+
function IconButton({
39
+
text,
40
+
link,
41
+
icon,
42
+
openInClient
43
+
}: {
44
+
text: string;
45
+
link: string;
46
+
icon: React.FC<any>;
47
+
openInClient?: boolean;
48
+
}) {
49
+
return (
50
+
<Button
51
+
onClick={() => {
52
+
if (openInClient) {
53
+
try {
54
+
const { handleClick } = spacepack.require("discord/utils/MaskedLinkUtils");
55
+
handleClick({ href: link });
56
+
} catch {
57
+
window.open(link);
58
+
}
59
+
} else {
60
+
// Will open externally in the user's browser
61
+
window.open(link);
62
+
}
63
+
}}
64
+
>
65
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap">
66
+
{React.createElement(icon, {
67
+
size: "sm",
68
+
color: "currentColor"
69
+
})}
70
+
{text}
71
+
</Flex>
72
+
</Button>
73
+
);
74
+
}
75
+
76
+
export default function AboutPage() {
77
+
const darkTheme = useThemeContext()?.theme !== "light";
78
+
79
+
return (
80
+
<Flex direction={Flex.Direction.VERTICAL} align={Flex.Align.CENTER} className="moonbase-about-page">
81
+
<img src={darkTheme ? wordmarkLight : wordmark} alt="moonlight wordmark" className="moonbase-wordmark" />
82
+
83
+
<Text variant="heading-lg/medium">created by:</Text>
84
+
<div className="moonbase-devs">
85
+
<Dev name="Cynosphere" picture="https://github.com/Cynosphere.png" link="https://github.com/Cynosphere" />
86
+
<Dev name="NotNite" picture="https://github.com/NotNite.png" link="https://github.com/NotNite" />
87
+
<Dev name="adryd" picture="https://github.com/adryd325.png" link="https://github.com/adryd325" />
88
+
<Dev name="redstonekasi" picture="https://github.com/redstonekasi.png" link="https://github.com/redstonekasi" />
89
+
</div>
90
+
91
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap">
92
+
<IconButton text="View source" icon={AngleBracketsIcon} link="https://github.com/moonlight-mod/moonlight" />
93
+
<IconButton text="Open the docs" icon={BookCheckIcon} link="https://moonlight-mod.github.io/" />
94
+
<IconButton text="Join the server" icon={ClydeIcon} link="https://discord.gg/FdZBTFCP6F" openInClient={true} />
95
+
</Flex>
96
+
97
+
<Flex direction={Flex.Direction.VERTICAL} align={Flex.Align.START}>
98
+
<Text variant="text-sm/normal">
99
+
{parse(`moonlight \`${window.moonlight.version}\` on \`${window.moonlight.branch}\``)}
100
+
</Text>
101
+
102
+
<Text variant="text-sm/normal">
103
+
{parse(
104
+
"moonlight is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.html) (`LGPL-3.0-or-later`)."
105
+
)}
106
+
</Text>
107
+
</Flex>
108
+
</Flex>
109
+
);
110
+
}
+18
-43
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
+18
-43
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
···
1
import { LogLevel } from "@moonlight-mod/types";
2
3
-
const logLevels = Object.values(LogLevel).filter(
4
-
(v) => typeof v === "string"
5
-
) as string[];
6
7
import React from "@moonlight-mod/wp/react";
8
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
···
18
Clickable
19
} from "@moonlight-mod/wp/discord/components/common/index";
20
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
21
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
22
23
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
24
25
-
const FormClasses = spacepack.findByCode("dividerDefault:")[0].exports;
26
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
27
-
28
-
let RemoveButtonClasses: any;
29
spacepack
30
.lazyLoad(
31
"renderArtisanalHack",
···
34
)
35
.then(
36
() =>
37
-
(RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0]
38
-
.exports)
39
);
40
41
-
// FIXME: type component keys
42
-
const { CircleXIcon } = Components;
43
-
44
function RemoveEntryButton({ onClick }: { onClick: () => void }) {
45
return (
46
-
<div className={RemoveButtonClasses.removeButtonContainer}>
47
<Tooltip text="Remove entry" position="top">
48
{(props: any) => (
49
-
<Clickable
50
-
{...props}
51
-
className={RemoveButtonClasses.removeButton}
52
-
onClick={onClick}
53
-
>
54
<CircleXIcon width={24} height={24} />
55
</Clickable>
56
)}
···
59
);
60
}
61
62
-
function ArrayFormItem({
63
-
config
64
-
}: {
65
-
config: "repositories" | "devSearchPaths";
66
-
}) {
67
const items = MoonbaseSettingsStore.getConfigOption(config) ?? [];
68
return (
69
<Flex
···
123
<>
124
<FormSwitch
125
className={Margins.marginTop20}
126
-
value={MoonbaseSettingsStore.getExtensionConfigRaw<boolean>(
127
-
"moonbase",
128
-
"updateChecking",
129
-
true
130
-
)}
131
onChange={(value: boolean) => {
132
-
MoonbaseSettingsStore.setExtensionConfig(
133
-
"moonbase",
134
-
"updateChecking",
135
-
value
136
-
);
137
}}
138
note="Checks for updates to moonlight"
139
>
140
Automatic update checking
141
</FormSwitch>
142
<FormItem title="Repositories">
143
-
<FormText className={Margins.marginBottom4}>
144
-
A list of remote repositories to display extensions from
145
-
</FormText>
146
<ArrayFormItem config="repositories" />
147
</FormItem>
148
-
<FormDivider className={FormClasses.dividerDefault} />
149
<FormItem title="Extension search paths" className={Margins.marginTop20}>
150
<FormText className={Margins.marginBottom4}>
151
A list of local directories to search for built extensions
152
</FormText>
153
<ArrayFormItem config="devSearchPaths" />
154
</FormItem>
155
-
<FormDivider className={FormClasses.dividerDefault} />
156
<FormSwitch
157
className={Margins.marginTop20}
158
-
value={MoonbaseSettingsStore.getConfigOption("patchAll")}
159
onChange={(value: boolean) => {
160
MoonbaseSettingsStore.setConfigOption("patchAll", value);
161
}}
···
172
value: o.toLowerCase(),
173
label: o[0] + o.slice(1).toLowerCase()
174
}))}
175
-
onChange={(v) =>
176
-
MoonbaseSettingsStore.setConfigOption("loggerLevel", v)
177
-
}
178
/>
179
</FormItem>
180
</>
···
1
import { LogLevel } from "@moonlight-mod/types";
2
3
+
const logLevels = Object.values(LogLevel).filter((v) => typeof v === "string") as string[];
4
5
import React from "@moonlight-mod/wp/react";
6
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
···
16
Clickable
17
} from "@moonlight-mod/wp/discord/components/common/index";
18
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
19
+
import { CircleXIcon } from "@moonlight-mod/wp/discord/components/common/index";
20
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
21
+
import FormSwitchClasses from "@moonlight-mod/wp/discord/components/common/FormSwitch.css";
22
23
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
24
25
+
let GuildSettingsRoleEditClasses: any;
26
spacepack
27
.lazyLoad(
28
"renderArtisanalHack",
···
31
)
32
.then(
33
() =>
34
+
(GuildSettingsRoleEditClasses = spacepack.require(
35
+
"discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"
36
+
))
37
);
38
39
function RemoveEntryButton({ onClick }: { onClick: () => void }) {
40
return (
41
+
<div className={GuildSettingsRoleEditClasses.removeButtonContainer}>
42
<Tooltip text="Remove entry" position="top">
43
{(props: any) => (
44
+
<Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}>
45
<CircleXIcon width={24} height={24} />
46
</Clickable>
47
)}
···
50
);
51
}
52
53
+
function ArrayFormItem({ config }: { config: "repositories" | "devSearchPaths" }) {
54
const items = MoonbaseSettingsStore.getConfigOption(config) ?? [];
55
return (
56
<Flex
···
110
<>
111
<FormSwitch
112
className={Margins.marginTop20}
113
+
value={MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "updateChecking", true) ?? true}
114
onChange={(value: boolean) => {
115
+
MoonbaseSettingsStore.setExtensionConfig("moonbase", "updateChecking", value);
116
}}
117
note="Checks for updates to moonlight"
118
>
119
Automatic update checking
120
</FormSwitch>
121
<FormItem title="Repositories">
122
+
<FormText className={Margins.marginBottom4}>A list of remote repositories to display extensions from</FormText>
123
<ArrayFormItem config="repositories" />
124
</FormItem>
125
+
<FormDivider className={FormSwitchClasses.dividerDefault} />
126
<FormItem title="Extension search paths" className={Margins.marginTop20}>
127
<FormText className={Margins.marginBottom4}>
128
A list of local directories to search for built extensions
129
</FormText>
130
<ArrayFormItem config="devSearchPaths" />
131
</FormItem>
132
+
<FormDivider className={FormSwitchClasses.dividerDefault} />
133
<FormSwitch
134
className={Margins.marginTop20}
135
+
value={MoonbaseSettingsStore.getConfigOption("patchAll") ?? false}
136
onChange={(value: boolean) => {
137
MoonbaseSettingsStore.setConfigOption("patchAll", value);
138
}}
···
149
value: o.toLowerCase(),
150
label: o[0] + o.slice(1).toLowerCase()
151
}))}
152
+
onChange={(v) => MoonbaseSettingsStore.setConfigOption("loggerLevel", v)}
153
/>
154
</FormItem>
155
</>
+253
-194
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
+253
-194
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
···
1
import { ExtensionState } from "../../../types";
2
-
import { ExtensionLoadSource } from "@moonlight-mod/types";
3
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
4
-
5
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
6
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
7
import React from "@moonlight-mod/wp/react";
8
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
9
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
10
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
11
-
import IntegrationCard from "@moonlight-mod/wp/discord/modules/guild_settings/IntegrationCard.css";
12
-
13
import ExtensionInfo from "./info";
14
import Settings from "./settings";
15
-
import installWithDependencyPopup from "./popup";
16
17
export enum ExtensionPage {
18
Info,
19
Description,
20
Settings
21
}
22
23
-
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
24
-
25
-
const { BeakerIcon, DownloadIcon, TrashIcon, CircleWarningIcon, Tooltip } =
26
-
Components;
27
-
28
-
const PanelButton = spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.Z;
29
-
const TabBarClasses = spacepack.findByExports(
30
-
"tabBar",
31
-
"tabBarItem",
32
-
"headerContentWrapper"
33
-
)[0].exports;
34
-
const MarkupClasses = spacepack.findByExports("markup", "inlineFormat")[0]
35
-
.exports;
36
-
37
-
const BuildOverrideClasses = spacepack.findByExports(
38
-
"disabledButtonOverride"
39
-
)[0].exports;
40
-
41
const COMPAT_TEXT_MAP: Record<ExtensionCompat, string> = {
42
[ExtensionCompat.Compatible]: "huh?",
43
[ExtensionCompat.InvalidApiLevel]: "Incompatible API level",
44
[ExtensionCompat.InvalidEnvironment]: "Incompatible platform"
45
};
46
-
47
-
export default function ExtensionCard({ uniqueId }: { uniqueId: number }) {
48
-
const [tab, setTab] = React.useState(ExtensionPage.Info);
49
-
const [restartNeeded, setRestartNeeded] = React.useState(false);
50
51
-
const { ext, enabled, busy, update, conflicting } = useStateFromStores(
52
-
[MoonbaseSettingsStore],
53
-
() => {
54
-
return {
55
-
ext: MoonbaseSettingsStore.getExtension(uniqueId),
56
-
enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId),
57
-
busy: MoonbaseSettingsStore.busy,
58
-
update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId),
59
-
conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId)
60
-
};
61
-
}
62
);
63
64
-
// Why it work like that :sob:
65
-
if (ext == null) return <></>;
66
67
-
const { Card, Text, FormSwitch, TabBar, Button } = Components;
68
69
const tagline = ext.manifest?.meta?.tagline;
70
-
const settings = ext.manifest?.settings;
71
const description = ext.manifest?.meta?.description;
72
const enabledDependants = useStateFromStores([MoonbaseSettingsStore], () =>
73
Object.keys(MoonbaseSettingsStore.extensions)
74
.filter((uniqueId) => {
75
-
const potentialDependant = MoonbaseSettingsStore.getExtension(
76
-
parseInt(uniqueId)
77
-
);
78
79
return (
80
-
potentialDependant.manifest.dependencies?.includes(ext.id) &&
81
MoonbaseSettingsStore.getExtensionEnabled(parseInt(uniqueId))
82
);
83
})
···
85
);
86
const implicitlyEnabled = enabledDependants.length > 0;
87
88
-
return (
89
-
<Card editable={true} className={IntegrationCard.card}>
90
-
<div className={IntegrationCard.cardHeader}>
91
<Flex direction={Flex.Direction.VERTICAL}>
92
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER}>
93
-
<Text variant="text-md/semibold">
94
-
{ext.manifest?.meta?.name ?? ext.id}
95
-
</Text>
96
{ext.source.type === ExtensionLoadSource.Developer && (
97
<Tooltip text="This is a local extension" position="top">
98
-
{(props: any) => (
99
-
<BeakerIcon
100
-
{...props}
101
-
class={BuildOverrideClasses.infoIcon}
102
-
size="xs"
103
-
/>
104
-
)}
105
</Tooltip>
106
)}
107
</Flex>
108
109
-
{tagline != null && (
110
-
<Text variant="text-sm/normal">{MarkupUtils.parse(tagline)}</Text>
111
-
)}
112
</Flex>
113
114
-
<Flex
115
-
direction={Flex.Direction.HORIZONTAL}
116
-
align={Flex.Align.END}
117
-
justify={Flex.Justify.END}
118
-
>
119
-
{ext.state === ExtensionState.NotDownloaded ? (
120
-
<Tooltip
121
-
text={COMPAT_TEXT_MAP[ext.compat]}
122
-
shouldShow={ext.compat !== ExtensionCompat.Compatible}
123
-
>
124
-
{(props: any) => (
125
-
<Button
126
-
{...props}
127
-
color={Button.Colors.BRAND}
128
-
submitting={busy}
129
-
disabled={
130
-
ext.compat !== ExtensionCompat.Compatible || conflicting
131
}
132
-
onClick={async () => {
133
-
await installWithDependencyPopup(uniqueId);
134
}}
135
-
>
136
-
Install
137
-
</Button>
138
-
)}
139
-
</Tooltip>
140
-
) : (
141
-
<div
142
-
// too lazy to learn how <Flex /> works lmao
143
style={{
144
-
display: "flex",
145
-
alignItems: "center",
146
-
gap: "1rem"
147
}}
148
>
149
-
{ext.source.type === ExtensionLoadSource.Normal && (
150
-
<PanelButton
151
-
icon={TrashIcon}
152
-
tooltipText="Delete"
153
-
onClick={() => {
154
-
MoonbaseSettingsStore.deleteExtension(uniqueId);
155
-
}}
156
-
/>
157
)}
158
159
-
{update != null && (
160
-
<PanelButton
161
-
icon={DownloadIcon}
162
-
tooltipText="Update"
163
-
onClick={() => {
164
-
MoonbaseSettingsStore.installExtension(uniqueId);
165
-
}}
166
-
/>
167
)}
168
169
-
{restartNeeded && (
170
-
<PanelButton
171
-
icon={() => (
172
-
<CircleWarningIcon
173
-
color={Components.tokens.colors.STATUS_DANGER}
174
-
/>
175
-
)}
176
-
onClick={() => window.location.reload()}
177
-
tooltipText="You will need to reload/restart your client for this extension to work properly."
178
-
/>
179
)}
180
-
181
-
<FormSwitch
182
-
value={
183
-
ext.compat === ExtensionCompat.Compatible &&
184
-
(enabled || implicitlyEnabled)
185
-
}
186
-
disabled={
187
-
implicitlyEnabled || ext.compat !== ExtensionCompat.Compatible
188
-
}
189
-
hideBorder={true}
190
-
style={{ marginBottom: "0px" }}
191
-
tooltipNote={
192
-
ext.compat !== ExtensionCompat.Compatible
193
-
? COMPAT_TEXT_MAP[ext.compat]
194
-
: implicitlyEnabled
195
-
? `This extension is a dependency of the following enabled extension${
196
-
enabledDependants.length > 1 ? "s" : ""
197
-
}: ${enabledDependants
198
-
.map((a) => a.manifest.meta?.name ?? a.id)
199
-
.join(", ")}`
200
-
: undefined
201
-
}
202
-
onChange={() => {
203
-
setRestartNeeded(true);
204
-
MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled);
205
-
}}
206
-
/>
207
-
</div>
208
-
)}
209
-
</Flex>
210
-
</div>
211
212
-
<div>
213
-
{(description != null || settings != null) && (
214
-
<TabBar
215
-
selectedItem={tab}
216
-
type="top"
217
-
onItemSelect={setTab}
218
-
className={TabBarClasses.tabBar}
219
-
style={{
220
-
padding: "0 20px"
221
-
}}
222
-
>
223
-
<TabBar.Item
224
-
className={TabBarClasses.tabBarItem}
225
-
id={ExtensionPage.Info}
226
>
227
-
Info
228
-
</TabBar.Item>
229
-
230
-
{description != null && (
231
-
<TabBar.Item
232
-
className={TabBarClasses.tabBarItem}
233
-
id={ExtensionPage.Description}
234
-
>
235
-
Description
236
-
</TabBar.Item>
237
-
)}
238
-
239
-
{settings != null && (
240
-
<TabBar.Item
241
-
className={TabBarClasses.tabBarItem}
242
-
id={ExtensionPage.Settings}
243
-
>
244
-
Settings
245
-
</TabBar.Item>
246
-
)}
247
-
</TabBar>
248
)}
249
250
<Flex
251
justify={Flex.Justify.START}
252
wrap={Flex.Wrap.WRAP}
253
style={{
254
-
padding: "16px 16px"
255
}}
256
>
257
-
{tab === ExtensionPage.Info && <ExtensionInfo ext={ext} />}
258
{tab === ExtensionPage.Description && (
259
-
<Text
260
-
variant="text-md/normal"
261
-
class={MarkupClasses.markup}
262
-
style={{ width: "100%" }}
263
-
>
264
{MarkupUtils.parse(description ?? "*No description*", true, {
265
allowHeading: true,
266
allowLinks: true,
···
268
})}
269
</Text>
270
)}
271
-
{tab === ExtensionPage.Settings && <Settings ext={ext} />}
272
</Flex>
273
</div>
274
</Card>
···
1
import { ExtensionState } from "../../../types";
2
+
import { constants, ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types";
3
+
4
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
5
+
import {
6
+
ScienceIcon,
7
+
DownloadIcon,
8
+
TrashIcon,
9
+
AngleBracketsIcon,
10
+
Tooltip,
11
+
Card,
12
+
Text,
13
+
FormSwitch,
14
+
TabBar,
15
+
Button,
16
+
ChannelListIcon,
17
+
HeartIcon,
18
+
WindowTopOutlineIcon,
19
+
WarningIcon
20
+
} from "@moonlight-mod/wp/discord/components/common/index";
21
import React from "@moonlight-mod/wp/react";
22
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
23
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
24
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
25
+
import AppCardClasses from "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCard.css";
26
+
import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton";
27
+
import DiscoveryClasses from "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css";
28
+
import MarkupClasses from "@moonlight-mod/wp/discord/modules/messages/web/Markup.css";
29
+
import BuildOverrideClasses from "@moonlight-mod/wp/discord/modules/build_overrides/web/BuildOverride.css";
30
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
31
+
import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary";
32
import ExtensionInfo from "./info";
33
import Settings from "./settings";
34
+
import { doGenericExtensionPopup, doMissingExtensionPopup } from "./popup";
35
36
export enum ExtensionPage {
37
Info,
38
Description,
39
+
Changelog,
40
Settings
41
}
42
43
const COMPAT_TEXT_MAP: Record<ExtensionCompat, string> = {
44
[ExtensionCompat.Compatible]: "huh?",
45
[ExtensionCompat.InvalidApiLevel]: "Incompatible API level",
46
[ExtensionCompat.InvalidEnvironment]: "Incompatible platform"
47
};
48
+
const CONFLICTING_TEXT = "This extension is already installed from another source.";
49
50
+
function PanelLinkButton({ icon, tooltip, link }: { icon: React.ReactNode; tooltip: string; link: string }) {
51
+
return (
52
+
<PanelButton
53
+
icon={icon}
54
+
tooltipText={tooltip}
55
+
onClick={() => {
56
+
window.open(link);
57
+
}}
58
+
/>
59
);
60
+
}
61
62
+
export default function ExtensionCard({ uniqueId, selectTag }: { uniqueId: number; selectTag: (tag: string) => void }) {
63
+
const { ext, enabled, busy, update, conflicting } = useStateFromStores([MoonbaseSettingsStore], () => {
64
+
return {
65
+
ext: MoonbaseSettingsStore.getExtension(uniqueId),
66
+
enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId),
67
+
busy: MoonbaseSettingsStore.busy,
68
+
update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId),
69
+
conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId)
70
+
};
71
+
});
72
73
+
const [tab, setTab] = React.useState(
74
+
update != null && ext?.changelog != null ? ExtensionPage.Changelog : ExtensionPage.Info
75
+
);
76
77
const tagline = ext.manifest?.meta?.tagline;
78
+
const settings = ext.settingsOverride ?? ext.manifest?.settings;
79
const description = ext.manifest?.meta?.description;
80
+
const changelog = ext.changelog;
81
+
const linkButtons = [
82
+
ext?.manifest?.meta?.source && (
83
+
<PanelLinkButton icon={<AngleBracketsIcon />} tooltip="View source" link={ext.manifest.meta.source} />
84
+
),
85
+
ext?.source?.url && <PanelLinkButton icon={<ChannelListIcon />} tooltip="View repository" link={ext.source.url} />,
86
+
ext?.manifest?.meta?.donate && (
87
+
<PanelLinkButton icon={<HeartIcon />} tooltip="Donate" link={ext.manifest.meta.donate} />
88
+
)
89
+
].filter((x) => x != null);
90
+
91
const enabledDependants = useStateFromStores([MoonbaseSettingsStore], () =>
92
Object.keys(MoonbaseSettingsStore.extensions)
93
.filter((uniqueId) => {
94
+
const potentialDependant = MoonbaseSettingsStore.getExtension(parseInt(uniqueId));
95
96
return (
97
+
potentialDependant.manifest.dependencies?.includes(ext?.id) &&
98
MoonbaseSettingsStore.getExtensionEnabled(parseInt(uniqueId))
99
);
100
})
···
102
);
103
const implicitlyEnabled = enabledDependants.length > 0;
104
105
+
const hasDuplicateEntry = useStateFromStores([MoonbaseSettingsStore], () =>
106
+
Object.entries(MoonbaseSettingsStore.extensions).some(
107
+
([otherUniqueId, otherExt]) =>
108
+
otherExt != null && otherExt?.id === ext?.id && parseInt(otherUniqueId) !== uniqueId
109
+
)
110
+
);
111
+
112
+
return ext == null ? (
113
+
<></>
114
+
) : (
115
+
<Card editable={true} className={AppCardClasses.card}>
116
+
<div className={AppCardClasses.cardHeader}>
117
<Flex direction={Flex.Direction.VERTICAL}>
118
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER}>
119
+
<Text variant="text-md/semibold">{ext.manifest?.meta?.name ?? ext.id}</Text>
120
{ext.source.type === ExtensionLoadSource.Developer && (
121
<Tooltip text="This is a local extension" position="top">
122
+
{(props: any) => <ScienceIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />}
123
+
</Tooltip>
124
+
)}
125
+
126
+
{hasDuplicateEntry && ext?.source?.url && (
127
+
<Tooltip text={`This extension is from the following repository: ${ext.source.url}`} position="top">
128
+
{(props: any) => <WindowTopOutlineIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />}
129
+
</Tooltip>
130
+
)}
131
+
132
+
{ext.manifest?.meta?.deprecated && (
133
+
<Tooltip text="This extension is deprecated" position="top">
134
+
{(props: any) => <WarningIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />}
135
</Tooltip>
136
)}
137
</Flex>
138
139
+
{tagline != null && <Text variant="text-sm/normal">{MarkupUtils.parse(tagline)}</Text>}
140
</Flex>
141
142
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.END} justify={Flex.Justify.END}>
143
+
<div
144
+
// too lazy to learn how <Flex /> works lmao
145
+
style={{
146
+
display: "flex",
147
+
alignItems: "center",
148
+
gap: "1rem"
149
+
}}
150
+
>
151
+
{ext.state === ExtensionState.NotDownloaded ? (
152
+
<Tooltip
153
+
text={conflicting ? CONFLICTING_TEXT : COMPAT_TEXT_MAP[ext.compat]}
154
+
shouldShow={conflicting || ext.compat !== ExtensionCompat.Compatible}
155
+
>
156
+
{(props: any) => (
157
+
<Button
158
+
{...props}
159
+
color={Button.Colors.BRAND}
160
+
submitting={busy}
161
+
disabled={ext.compat !== ExtensionCompat.Compatible || conflicting}
162
+
onClick={async () => {
163
+
await MoonbaseSettingsStore.installExtension(uniqueId);
164
+
const deps = await MoonbaseSettingsStore.getDependencies(uniqueId);
165
+
if (deps != null) {
166
+
await doMissingExtensionPopup(deps);
167
+
}
168
+
169
+
// Don't auto enable dangerous extensions
170
+
if (!ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) {
171
+
MoonbaseSettingsStore.setExtensionEnabled(uniqueId, true);
172
+
}
173
+
}}
174
+
>
175
+
Install
176
+
</Button>
177
+
)}
178
+
</Tooltip>
179
+
) : (
180
+
<>
181
+
{ext.source.type === ExtensionLoadSource.Normal && (
182
+
<PanelButton
183
+
icon={TrashIcon}
184
+
tooltipText="Delete"
185
+
onClick={() => {
186
+
MoonbaseSettingsStore.deleteExtension(uniqueId);
187
+
}}
188
+
/>
189
+
)}
190
+
191
+
{update != null && (
192
+
<PanelButton
193
+
icon={DownloadIcon}
194
+
tooltipText="Update"
195
+
onClick={() => {
196
+
MoonbaseSettingsStore.installExtension(uniqueId);
197
+
}}
198
+
/>
199
+
)}
200
+
201
+
<FormSwitch
202
+
value={ext.compat === ExtensionCompat.Compatible && (enabled || implicitlyEnabled)}
203
+
disabled={implicitlyEnabled || ext.compat !== ExtensionCompat.Compatible}
204
+
hideBorder={true}
205
+
style={{ marginBottom: "0px" }}
206
+
// @ts-expect-error fix type later
207
+
tooltipNote={
208
+
ext.compat !== ExtensionCompat.Compatible ? (
209
+
COMPAT_TEXT_MAP[ext.compat]
210
+
) : implicitlyEnabled ? (
211
+
<div style={{ display: "flex", flexDirection: "column" }}>
212
+
<div>{`This extension is a dependency of the following enabled extension${
213
+
enabledDependants.length > 1 ? "s" : ""
214
+
}:`}</div>
215
+
{enabledDependants.map((dep) => (
216
+
<div>{"โข " + (dep.manifest.meta?.name ?? dep.id)}</div>
217
+
))}
218
+
</div>
219
+
) : undefined
220
}
221
+
onChange={() => {
222
+
const toggle = () => {
223
+
MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled);
224
+
};
225
+
226
+
if (enabled && constants.builtinExtensions.includes(ext.id)) {
227
+
doGenericExtensionPopup(
228
+
"Built in extension",
229
+
"This extension is enabled by default. Disabling it might have consequences. Are you sure you want to disable it?",
230
+
uniqueId,
231
+
toggle
232
+
);
233
+
} else if (!enabled && ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) {
234
+
doGenericExtensionPopup(
235
+
"Dangerous extension",
236
+
"This extension is marked as dangerous. Enabling it might have consequences. Are you sure you want to enable it?",
237
+
uniqueId,
238
+
toggle
239
+
);
240
+
} else {
241
+
toggle();
242
+
}
243
}}
244
+
/>
245
+
</>
246
+
)}
247
+
</div>
248
+
</Flex>
249
+
</div>
250
+
251
+
<div>
252
+
{(description != null || changelog != null || settings != null || linkButtons.length > 0) && (
253
+
<Flex>
254
+
<TabBar
255
+
selectedItem={tab}
256
+
type="top"
257
+
onItemSelect={setTab}
258
+
className={DiscoveryClasses.tabBar}
259
style={{
260
+
padding: "0 20px"
261
}}
262
>
263
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Info}>
264
+
Info
265
+
</TabBar.Item>
266
+
267
+
{description != null && (
268
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Description}>
269
+
Description
270
+
</TabBar.Item>
271
)}
272
273
+
{changelog != null && (
274
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Changelog}>
275
+
Changelog
276
+
</TabBar.Item>
277
)}
278
279
+
{settings != null && (
280
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Settings}>
281
+
Settings
282
+
</TabBar.Item>
283
)}
284
+
</TabBar>
285
286
+
<Flex
287
+
align={Flex.Align.CENTER}
288
+
justify={Flex.Justify.END}
289
+
direction={Flex.Direction.HORIZONTAL}
290
+
grow={1}
291
+
className="moonbase-link-buttons"
292
>
293
+
{linkButtons.length > 0 && linkButtons}
294
+
</Flex>
295
+
</Flex>
296
)}
297
298
<Flex
299
justify={Flex.Justify.START}
300
wrap={Flex.Wrap.WRAP}
301
style={{
302
+
padding: "16px 16px",
303
+
// This looks wonky in the settings tab
304
+
rowGap: tab === ExtensionPage.Info ? "16px" : undefined
305
}}
306
>
307
+
{tab === ExtensionPage.Info && <ExtensionInfo ext={ext} selectTag={selectTag} />}
308
{tab === ExtensionPage.Description && (
309
+
<Text variant="text-md/normal" className={MarkupClasses.markup} style={{ width: "100%" }}>
310
{MarkupUtils.parse(description ?? "*No description*", true, {
311
allowHeading: true,
312
allowLinks: true,
···
314
})}
315
</Text>
316
)}
317
+
{tab === ExtensionPage.Changelog && (
318
+
<Text variant="text-md/normal" className={MarkupClasses.markup} style={{ width: "100%" }}>
319
+
{MarkupUtils.parse(changelog ?? "*No changelog*", true, {
320
+
allowHeading: true,
321
+
allowLinks: true,
322
+
allowList: true
323
+
})}
324
+
</Text>
325
+
)}
326
+
{tab === ExtensionPage.Settings && (
327
+
<ErrorBoundary>
328
+
<Settings ext={ext} />
329
+
</ErrorBoundary>
330
+
)}
331
</Flex>
332
</div>
333
</Card>
+109
-117
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
+109
-117
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
···
1
import { tagNames } from "./info";
2
-
3
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
4
import * as React from "@moonlight-mod/wp/react";
5
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
···
11
Popout,
12
Dialog,
13
Menu,
14
-
MenuGroup,
15
-
MenuCheckboxItem,
16
-
MenuItem
17
} from "@moonlight-mod/wp/discord/components/common/index";
18
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
19
20
export enum Filter {
21
Core = 1 << 0,
···
25
Disabled = 1 << 4,
26
Installed = 1 << 5,
27
Repository = 1 << 6,
28
-
Incompatible = 1 << 7
29
}
30
export const defaultFilter = 127 as Filter;
31
32
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
33
-
const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0]
34
-
.exports;
35
-
36
-
let FilterDialogClasses: any;
37
-
let FilterBarClasses: any;
38
spacepack
39
-
.lazyLoad(
40
-
'"Missing channel in Channel.openChannelContextMenu"',
41
-
/e\("(\d+)"\)/g,
42
-
/webpackId:(\d+?),/
43
-
)
44
.then(() => {
45
-
FilterBarClasses = spacepack.findByCode("tagsButtonWithCount:")[0].exports;
46
-
FilterDialogClasses = spacepack.findByCode(
47
-
"countContainer:",
48
-
"tagContainer:"
49
-
)[0].exports;
50
});
51
52
-
const TagItem = spacepack.findByCode(".FORUM_TAG_A11Y_FILTER_BY_TAG")[0].exports
53
-
.Z;
54
-
55
-
// FIXME: type component keys
56
-
const { ChevronSmallDownIcon, ChevronSmallUpIcon, ArrowsUpDownIcon } =
57
-
Components;
58
-
59
-
function toggleTag(
60
-
selectedTags: Set<string>,
61
-
setSelectedTags: (tags: Set<string>) => void,
62
-
tag: string
63
-
) {
64
const newState = new Set(selectedTags);
65
if (newState.has(tag)) newState.delete(tag);
66
else newState.add(tag);
···
76
setFilter: (filter: Filter) => void;
77
closePopout: () => void;
78
}) {
79
-
const toggleFilter = (set: Filter) =>
80
-
setFilter(filter & set ? filter & ~set : filter | set);
81
82
return (
83
<div className={SortMenuClasses.container}>
84
-
<Menu navId="sort-filter" hideScrollbar={true} onClose={closePopout}>
85
<MenuGroup label="Type">
86
<MenuCheckboxItem
87
id="t-core"
88
label="Core"
89
-
checked={filter & Filter.Core}
90
action={() => toggleFilter(Filter.Core)}
91
/>
92
<MenuCheckboxItem
93
id="t-normal"
94
label="Normal"
95
-
checked={filter & Filter.Normal}
96
action={() => toggleFilter(Filter.Normal)}
97
/>
98
<MenuCheckboxItem
99
id="t-developer"
100
label="Developer"
101
-
checked={filter & Filter.Developer}
102
action={() => toggleFilter(Filter.Developer)}
103
/>
104
</MenuGroup>
···
106
<MenuCheckboxItem
107
id="s-enabled"
108
label="Enabled"
109
-
checked={filter & Filter.Enabled}
110
action={() => toggleFilter(Filter.Enabled)}
111
/>
112
<MenuCheckboxItem
113
id="s-disabled"
114
label="Disabled"
115
-
checked={filter & Filter.Disabled}
116
action={() => toggleFilter(Filter.Disabled)}
117
/>
118
</MenuGroup>
···
120
<MenuCheckboxItem
121
id="l-installed"
122
label="Installed"
123
-
checked={filter & Filter.Installed}
124
action={() => toggleFilter(Filter.Installed)}
125
/>
126
<MenuCheckboxItem
127
id="l-repository"
128
label="Repository"
129
-
checked={filter & Filter.Repository}
130
action={() => toggleFilter(Filter.Repository)}
131
/>
132
</MenuGroup>
···
134
<MenuCheckboxItem
135
id="l-incompatible"
136
label="Show incompatible"
137
-
checked={filter & Filter.Incompatible}
138
action={() => toggleFilter(Filter.Incompatible)}
139
/>
140
<MenuItem
141
id="reset-all"
142
className={SortMenuClasses.clearText}
143
-
label={
144
-
<Text variant="text-sm/medium" color="none">
145
-
Reset to default
146
-
</Text>
147
-
}
148
action={() => {
149
setFilter(defaultFilter);
150
closePopout();
···
156
);
157
}
158
159
-
function TagButtonPopout({
160
-
selectedTags,
161
-
setSelectedTags,
162
-
setPopoutRef,
163
-
closePopout
164
-
}: any) {
165
return (
166
-
<Dialog ref={setPopoutRef} className={FilterDialogClasses.container}>
167
-
<div className={FilterDialogClasses.header}>
168
-
<div className={FilterDialogClasses.headerLeft}>
169
-
<Heading
170
-
color="interactive-normal"
171
-
variant="text-xs/bold"
172
-
className={FilterDialogClasses.headerText}
173
-
>
174
Select tags
175
</Heading>
176
-
<div className={FilterDialogClasses.countContainer}>
177
-
<Text
178
-
className={FilterDialogClasses.countText}
179
-
color="none"
180
-
variant="text-xs/medium"
181
-
>
182
{selectedTags.size}
183
</Text>
184
</div>
185
</div>
186
</div>
187
-
<div className={FilterDialogClasses.tagContainer}>
188
{Object.keys(tagNames).map((tag) => (
189
<TagItem
190
key={tag}
191
-
className={FilterDialogClasses.tag}
192
-
tag={{ name: tagNames[tag as keyof typeof tagNames] }}
193
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
194
selected={selectedTags.has(tag)}
195
/>
196
))}
197
</div>
198
-
<div className={FilterDialogClasses.separator} />
199
<Button
200
look={Button.Looks.LINK}
201
size={Button.Sizes.MIN}
202
color={Button.Colors.CUSTOM}
203
-
className={FilterDialogClasses.clear}
204
onClick={() => {
205
setSelectedTags(new Set());
206
closePopout();
···
225
selectedTags: Set<string>;
226
setSelectedTags: (tags: Set<string>) => void;
227
}) {
228
-
const windowSize = useStateFromStores([WindowStore], () =>
229
-
WindowStore.windowSize()
230
-
);
231
232
const tagsContainer = React.useRef<HTMLDivElement>(null);
233
const tagListInner = React.useRef<HTMLDivElement>(null);
234
const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0);
235
React.useLayoutEffect(() => {
236
if (tagsContainer.current === null || tagListInner.current === null) return;
237
-
const { left: containerX, top: containerY } =
238
-
tagsContainer.current.getBoundingClientRect();
239
let offset = 0;
240
for (const child of tagListInner.current.children) {
241
-
const {
242
-
right: childX,
243
-
top: childY,
244
-
height
245
-
} = child.getBoundingClientRect();
246
if (childY - containerY > height) break;
247
const newOffset = childX - containerX;
248
if (newOffset > offset) {
···
250
}
251
}
252
setTagsButtonOffset(offset);
253
-
}, [windowSize]);
254
255
return (
256
<div
···
258
style={{
259
paddingTop: "12px"
260
}}
261
-
className={`${FilterBarClasses.tagsContainer} ${Margins.marginBottom8}`}
262
>
263
<Popout
264
renderPopout={({ closePopout }: any) => (
265
-
<FilterButtonPopout
266
-
filter={filter}
267
-
setFilter={setFilter}
268
-
closePopout={closePopout}
269
-
/>
270
)}
271
position="bottom"
272
align="left"
···
276
{...props}
277
size={Button.Sizes.MIN}
278
color={Button.Colors.CUSTOM}
279
-
className={FilterBarClasses.sortDropdown}
280
-
innerClassName={FilterBarClasses.sortDropdownInner}
281
>
282
<ArrowsUpDownIcon size="xs" />
283
-
<Text
284
-
className={FilterBarClasses.sortDropdownText}
285
-
variant="text-sm/medium"
286
-
color="interactive-normal"
287
-
>
288
Sort & filter
289
</Text>
290
{isShown ? (
···
295
</Button>
296
)}
297
</Popout>
298
-
<div className={FilterBarClasses.divider} />
299
-
<div className={FilterBarClasses.tagList}>
300
-
<div ref={tagListInner} className={FilterBarClasses.tagListInner}>
301
{Object.keys(tagNames).map((tag) => (
302
<TagItem
303
key={tag}
304
-
className={FilterBarClasses.tag}
305
-
tag={{ name: tagNames[tag as keyof typeof tagNames] }}
306
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
307
selected={selectedTags.has(tag)}
308
/>
···
330
left: tagsButtonOffset
331
}}
332
// TODO: Use Discord's class name utility
333
-
className={`${FilterBarClasses.tagsButton} ${
334
-
selectedTags.size > 0 ? FilterBarClasses.tagsButtonWithCount : ""
335
-
}`}
336
-
innerClassName={FilterBarClasses.tagsButtonInner}
337
>
338
{selectedTags.size > 0 ? (
339
-
<div
340
-
style={{ boxSizing: "content-box" }}
341
-
className={FilterBarClasses.countContainer}
342
-
>
343
-
<Text
344
-
className={FilterBarClasses.countText}
345
-
color="none"
346
-
variant="text-xs/medium"
347
-
>
348
{selectedTags.size}
349
</Text>
350
</div>
···
359
</Button>
360
)}
361
</Popout>
362
</div>
363
);
364
}
···
1
import { tagNames } from "./info";
2
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
3
import * as React from "@moonlight-mod/wp/react";
4
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
···
10
Popout,
11
Dialog,
12
Menu,
13
+
ChevronSmallDownIcon,
14
+
ChevronSmallUpIcon,
15
+
ArrowsUpDownIcon,
16
+
RetryIcon,
17
+
Tooltip
18
} from "@moonlight-mod/wp/discord/components/common/index";
19
+
import { MenuGroup, MenuCheckboxItem, MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu";
20
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
21
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
22
+
import TagItem from "@moonlight-mod/wp/discord/modules/forums/web/Tag";
23
24
export enum Filter {
25
Core = 1 << 0,
···
29
Disabled = 1 << 4,
30
Installed = 1 << 5,
31
Repository = 1 << 6,
32
+
Incompatible = 1 << 7,
33
+
Deprecated = 1 << 8
34
}
35
export const defaultFilter = 127 as Filter;
36
37
+
let HeaderClasses: any;
38
+
let ForumsClasses: any;
39
+
let SortMenuClasses: any;
40
spacepack
41
+
.lazyLoad('"Missing channel in Channel.openChannelContextMenu"', /e\("(\d+)"\)/g, /webpackId:(\d+?),/)
42
.then(() => {
43
+
ForumsClasses = spacepack.require("discord/modules/forums/web/Forums.css");
44
+
HeaderClasses = spacepack.require("discord/modules/forums/web/Header.css");
45
+
SortMenuClasses = spacepack.require("discord/modules/forums/web/SortMenu.css");
46
});
47
48
+
function toggleTag(selectedTags: Set<string>, setSelectedTags: (tags: Set<string>) => void, tag: string) {
49
const newState = new Set(selectedTags);
50
if (newState.has(tag)) newState.delete(tag);
51
else newState.add(tag);
···
61
setFilter: (filter: Filter) => void;
62
closePopout: () => void;
63
}) {
64
+
const toggleFilter = (set: Filter) => setFilter(filter & set ? filter & ~set : filter | set);
65
66
return (
67
<div className={SortMenuClasses.container}>
68
+
<Menu navId="sort-filter" hideScroller={true} onClose={closePopout}>
69
<MenuGroup label="Type">
70
<MenuCheckboxItem
71
id="t-core"
72
label="Core"
73
+
checked={(filter & Filter.Core) === Filter.Core}
74
action={() => toggleFilter(Filter.Core)}
75
/>
76
<MenuCheckboxItem
77
id="t-normal"
78
label="Normal"
79
+
checked={(filter & Filter.Normal) === Filter.Normal}
80
action={() => toggleFilter(Filter.Normal)}
81
/>
82
<MenuCheckboxItem
83
id="t-developer"
84
label="Developer"
85
+
checked={(filter & Filter.Developer) === Filter.Developer}
86
action={() => toggleFilter(Filter.Developer)}
87
/>
88
</MenuGroup>
···
90
<MenuCheckboxItem
91
id="s-enabled"
92
label="Enabled"
93
+
checked={(filter & Filter.Enabled) === Filter.Enabled}
94
action={() => toggleFilter(Filter.Enabled)}
95
/>
96
<MenuCheckboxItem
97
id="s-disabled"
98
label="Disabled"
99
+
checked={(filter & Filter.Disabled) === Filter.Disabled}
100
action={() => toggleFilter(Filter.Disabled)}
101
/>
102
</MenuGroup>
···
104
<MenuCheckboxItem
105
id="l-installed"
106
label="Installed"
107
+
checked={(filter & Filter.Installed) === Filter.Installed}
108
action={() => toggleFilter(Filter.Installed)}
109
/>
110
<MenuCheckboxItem
111
id="l-repository"
112
label="Repository"
113
+
checked={(filter & Filter.Repository) === Filter.Repository}
114
action={() => toggleFilter(Filter.Repository)}
115
/>
116
</MenuGroup>
···
118
<MenuCheckboxItem
119
id="l-incompatible"
120
label="Show incompatible"
121
+
checked={(filter & Filter.Incompatible) === Filter.Incompatible}
122
action={() => toggleFilter(Filter.Incompatible)}
123
/>
124
+
<MenuCheckboxItem
125
+
id="l-deprecated"
126
+
label="Show deprecated"
127
+
checked={(filter & Filter.Deprecated) === Filter.Deprecated}
128
+
action={() => toggleFilter(Filter.Deprecated)}
129
+
/>
130
<MenuItem
131
id="reset-all"
132
className={SortMenuClasses.clearText}
133
+
label="Reset to default"
134
action={() => {
135
setFilter(defaultFilter);
136
closePopout();
···
142
);
143
}
144
145
+
function TagButtonPopout({ selectedTags, setSelectedTags, setPopoutRef, closePopout }: any) {
146
return (
147
+
<Dialog ref={setPopoutRef} className={HeaderClasses.container}>
148
+
<div className={HeaderClasses.header}>
149
+
<div className={HeaderClasses.headerLeft}>
150
+
<Heading color="interactive-normal" variant="text-xs/bold" className={HeaderClasses.headerText}>
151
Select tags
152
</Heading>
153
+
<div className={HeaderClasses.countContainer}>
154
+
<Text className={HeaderClasses.countText} color="none" variant="text-xs/medium">
155
{selectedTags.size}
156
</Text>
157
</div>
158
</div>
159
</div>
160
+
<div className={HeaderClasses.tagContainer}>
161
{Object.keys(tagNames).map((tag) => (
162
<TagItem
163
key={tag}
164
+
className={HeaderClasses.tag}
165
+
tag={{ name: tagNames[tag as keyof typeof tagNames], id: tagNames[tag as keyof typeof tagNames] }}
166
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
167
selected={selectedTags.has(tag)}
168
/>
169
))}
170
</div>
171
+
<div className={HeaderClasses.separator} />
172
<Button
173
look={Button.Looks.LINK}
174
size={Button.Sizes.MIN}
175
color={Button.Colors.CUSTOM}
176
+
className={HeaderClasses.clear}
177
onClick={() => {
178
setSelectedTags(new Set());
179
closePopout();
···
198
selectedTags: Set<string>;
199
setSelectedTags: (tags: Set<string>) => void;
200
}) {
201
+
const windowSize = useStateFromStores([WindowStore], () => WindowStore.windowSize());
202
203
const tagsContainer = React.useRef<HTMLDivElement>(null);
204
const tagListInner = React.useRef<HTMLDivElement>(null);
205
const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0);
206
+
const [checkingUpdates, setCheckingUpdates] = React.useState(false);
207
+
208
React.useLayoutEffect(() => {
209
if (tagsContainer.current === null || tagListInner.current === null) return;
210
+
const { left: containerX, top: containerY } = tagsContainer.current.getBoundingClientRect();
211
let offset = 0;
212
for (const child of tagListInner.current.children) {
213
+
const { right: childX, top: childY, height } = child.getBoundingClientRect();
214
if (childY - containerY > height) break;
215
const newOffset = childX - containerX;
216
if (newOffset > offset) {
···
218
}
219
}
220
setTagsButtonOffset(offset);
221
+
}, [windowSize, tagsContainer.current, tagListInner.current, tagListInner.current?.getBoundingClientRect()?.width]);
222
223
return (
224
<div
···
226
style={{
227
paddingTop: "12px"
228
}}
229
+
className={`${ForumsClasses.tagsContainer} ${Margins.marginBottom8}`}
230
>
231
+
<Tooltip text="Refresh updates" position="top">
232
+
{(props: any) => (
233
+
<Button
234
+
{...props}
235
+
size={Button.Sizes.MIN}
236
+
color={Button.Colors.CUSTOM}
237
+
className={`${ForumsClasses.sortDropdown} moonbase-retry-button`}
238
+
innerClassName={ForumsClasses.sortDropdownInner}
239
+
onClick={() => {
240
+
(async () => {
241
+
try {
242
+
setCheckingUpdates(true);
243
+
await MoonbaseSettingsStore.checkUpdates();
244
+
} finally {
245
+
// artificial delay because the spin is fun
246
+
await new Promise((r) => setTimeout(r, 500));
247
+
setCheckingUpdates(false);
248
+
}
249
+
})();
250
+
}}
251
+
>
252
+
<RetryIcon size={"custom"} width={16} className={checkingUpdates ? "moonbase-speen" : ""} />
253
+
</Button>
254
+
)}
255
+
</Tooltip>
256
<Popout
257
renderPopout={({ closePopout }: any) => (
258
+
<FilterButtonPopout filter={filter} setFilter={setFilter} closePopout={closePopout} />
259
)}
260
position="bottom"
261
align="left"
···
265
{...props}
266
size={Button.Sizes.MIN}
267
color={Button.Colors.CUSTOM}
268
+
className={ForumsClasses.sortDropdown}
269
+
innerClassName={ForumsClasses.sortDropdownInner}
270
>
271
<ArrowsUpDownIcon size="xs" />
272
+
<Text className={ForumsClasses.sortDropdownText} variant="text-sm/medium" color="interactive-normal">
273
Sort & filter
274
</Text>
275
{isShown ? (
···
280
</Button>
281
)}
282
</Popout>
283
+
<div className={ForumsClasses.divider} />
284
+
<div className={ForumsClasses.tagList}>
285
+
<div ref={tagListInner} className={ForumsClasses.tagListInner}>
286
{Object.keys(tagNames).map((tag) => (
287
<TagItem
288
key={tag}
289
+
className={ForumsClasses.tag}
290
+
tag={{ name: tagNames[tag as keyof typeof tagNames], id: tag }}
291
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
292
selected={selectedTags.has(tag)}
293
/>
···
315
left: tagsButtonOffset
316
}}
317
// TODO: Use Discord's class name utility
318
+
className={`${ForumsClasses.tagsButton} ${selectedTags.size > 0 ? ForumsClasses.tagsButtonWithCount : ""}`}
319
+
innerClassName={ForumsClasses.tagsButtonInner}
320
>
321
{selectedTags.size > 0 ? (
322
+
<div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}>
323
+
<Text className={ForumsClasses.countText} color="none" variant="text-xs/medium">
324
{selectedTags.size}
325
</Text>
326
</div>
···
335
</Button>
336
)}
337
</Popout>
338
+
<Button
339
+
size={Button.Sizes.MIN}
340
+
color={Button.Colors.CUSTOM}
341
+
className={`${ForumsClasses.tagsButton} ${ForumsClasses.tagsButtonPlaceholder}`}
342
+
innerClassName={ForumsClasses.tagsButtonInner}
343
+
>
344
+
{selectedTags.size > 0 ? (
345
+
<div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}>
346
+
<Text className={ForumsClasses.countText} color="none" variant="text-xs/medium">
347
+
{selectedTags.size}
348
+
</Text>
349
+
</div>
350
+
) : null}
351
+
352
+
<ChevronSmallUpIcon size={"custom"} width={20} />
353
+
</Button>
354
</div>
355
);
356
}
+101
-54
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
+101
-54
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
···
6
import React from "@moonlight-mod/wp/react";
7
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
8
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
9
10
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
11
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
12
13
-
const SearchBar: any = Object.values(
14
-
spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0].exports
15
-
)[0];
16
17
export default function ExtensionsPage() {
18
-
const { extensions, savedFilter } = useStateFromStoresObject(
19
-
[MoonbaseSettingsStore],
20
-
() => {
21
-
return {
22
-
extensions: MoonbaseSettingsStore.extensions,
23
-
savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw<number>(
24
-
"moonbase",
25
-
"filter",
26
-
defaultFilter
27
-
)
28
-
};
29
-
}
30
-
);
31
32
const [query, setQuery] = React.useState("");
33
34
let filter: Filter, setFilter: (filter: Filter) => void;
35
-
if (
36
-
MoonbaseSettingsStore.getExtensionConfigRaw<boolean>(
37
-
"moonbase",
38
-
"saveFilter",
39
-
false
40
-
)
41
-
) {
42
filter = savedFilter ?? defaultFilter;
43
-
setFilter = (filter) =>
44
-
MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter);
45
} else {
46
-
const state = React.useState(defaultFilter);
47
-
filter = state[0];
48
-
setFilter = state[1];
49
}
50
const [selectedTags, setSelectedTags] = React.useState(new Set<string>());
51
const sorted = Object.values(extensions).sort((a, b) => {
52
const aName = a.manifest.meta?.name ?? a.id;
53
const bName = b.manifest.meta?.name ?? b.id;
···
60
ext.manifest.id?.toLowerCase().includes(query) ||
61
ext.manifest.meta?.name?.toLowerCase().includes(query) ||
62
ext.manifest.meta?.tagline?.toLowerCase().includes(query) ||
63
ext.manifest.meta?.description?.toLowerCase().includes(query)) &&
64
-
[...selectedTags.values()].every(
65
-
(tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)
66
-
) &&
67
// This seems very bad, sorry
68
!(
69
-
(!(filter & Filter.Core) &&
70
-
ext.source.type === ExtensionLoadSource.Core) ||
71
-
(!(filter & Filter.Normal) &&
72
-
ext.source.type === ExtensionLoadSource.Normal) ||
73
-
(!(filter & Filter.Developer) &&
74
-
ext.source.type === ExtensionLoadSource.Developer) ||
75
-
(!(filter & Filter.Enabled) &&
76
-
MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) ||
77
-
(!(filter & Filter.Disabled) &&
78
-
!MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) ||
79
-
(!(filter & Filter.Installed) &&
80
-
ext.state !== ExtensionState.NotDownloaded) ||
81
-
(!(filter & Filter.Repository) &&
82
-
ext.state === ExtensionState.NotDownloaded)
83
) &&
84
(filter & Filter.Incompatible ||
85
ext.compat === ExtensionCompat.Compatible ||
86
-
(ext.compat === ExtensionCompat.InvalidApiLevel && ext.hasUpdate))
87
);
88
89
return (
90
<>
···
101
spellCheck: "false"
102
}}
103
/>
104
-
<FilterBar
105
-
filter={filter}
106
-
setFilter={setFilter}
107
-
selectedTags={selectedTags}
108
-
setSelectedTags={setSelectedTags}
109
-
/>
110
-
{filtered.map((ext) => (
111
-
<ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} />
112
))}
113
</>
114
);
···
6
import React from "@moonlight-mod/wp/react";
7
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
8
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
9
+
import {
10
+
FormDivider,
11
+
CircleInformationIcon,
12
+
XSmallIcon,
13
+
Button
14
+
} from "@moonlight-mod/wp/discord/components/common/index";
15
+
import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton";
16
17
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
18
+
import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary";
19
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
20
+
import HelpMessage from "../HelpMessage";
21
22
+
const SearchBar = spacepack.require("discord/uikit/search/SearchBar").default;
23
+
24
+
const validTags: string[] = Object.values(ExtensionTag);
25
26
export default function ExtensionsPage() {
27
+
const { extensions, savedFilter } = useStateFromStoresObject([MoonbaseSettingsStore], () => {
28
+
return {
29
+
extensions: MoonbaseSettingsStore.extensions,
30
+
savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw<number>("moonbase", "filter", defaultFilter)
31
+
};
32
+
});
33
34
const [query, setQuery] = React.useState("");
35
+
const [hitUpdateAll, setHitUpdateAll] = React.useState(false);
36
+
37
+
const filterState = React.useState(defaultFilter);
38
39
let filter: Filter, setFilter: (filter: Filter) => void;
40
+
if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "saveFilter", false)) {
41
filter = savedFilter ?? defaultFilter;
42
+
setFilter = (filter) => MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter);
43
} else {
44
+
filter = filterState[0];
45
+
setFilter = filterState[1];
46
}
47
+
48
const [selectedTags, setSelectedTags] = React.useState(new Set<string>());
49
+
const selectTag = React.useCallback(
50
+
(tag: string) => {
51
+
const newState = new Set(selectedTags);
52
+
if (validTags.includes(tag)) newState.add(tag);
53
+
setSelectedTags(newState);
54
+
},
55
+
[selectedTags]
56
+
);
57
+
58
const sorted = Object.values(extensions).sort((a, b) => {
59
const aName = a.manifest.meta?.name ?? a.id;
60
const bName = b.manifest.meta?.name ?? b.id;
···
67
ext.manifest.id?.toLowerCase().includes(query) ||
68
ext.manifest.meta?.name?.toLowerCase().includes(query) ||
69
ext.manifest.meta?.tagline?.toLowerCase().includes(query) ||
70
+
(ext.manifest?.settings != null &&
71
+
Object.entries(ext.manifest.settings).some(([key, setting]) =>
72
+
(setting.displayName ?? key).toLowerCase().includes(query)
73
+
)) ||
74
+
(ext.manifest?.meta?.authors != null &&
75
+
ext.manifest.meta.authors.some((author) =>
76
+
(typeof author === "string" ? author : author.name).toLowerCase().includes(query)
77
+
)) ||
78
ext.manifest.meta?.description?.toLowerCase().includes(query)) &&
79
+
[...selectedTags.values()].every((tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)) &&
80
// This seems very bad, sorry
81
!(
82
+
(!(filter & Filter.Core) && ext.source.type === ExtensionLoadSource.Core) ||
83
+
(!(filter & Filter.Normal) && ext.source.type === ExtensionLoadSource.Normal) ||
84
+
(!(filter & Filter.Developer) && ext.source.type === ExtensionLoadSource.Developer) ||
85
+
(!(filter & Filter.Enabled) && MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) ||
86
+
(!(filter & Filter.Disabled) && !MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) ||
87
+
(!(filter & Filter.Installed) && ext.state !== ExtensionState.NotDownloaded) ||
88
+
(!(filter & Filter.Repository) && ext.state === ExtensionState.NotDownloaded)
89
) &&
90
(filter & Filter.Incompatible ||
91
ext.compat === ExtensionCompat.Compatible ||
92
+
(ext.compat === ExtensionCompat.InvalidApiLevel && ext.hasUpdate)) &&
93
+
(filter & Filter.Deprecated ||
94
+
ext.manifest?.meta?.deprecated !== true ||
95
+
ext.state !== ExtensionState.NotDownloaded)
96
);
97
+
98
+
// Prioritize extensions with updates
99
+
const filteredWithUpdates = filtered.filter((ext) => ext!.hasUpdate);
100
+
const filteredWithoutUpdates = filtered.filter((ext) => !ext!.hasUpdate);
101
102
return (
103
<>
···
114
spellCheck: "false"
115
}}
116
/>
117
+
<FilterBar filter={filter} setFilter={setFilter} selectedTags={selectedTags} setSelectedTags={setSelectedTags} />
118
+
119
+
{filteredWithUpdates.length > 0 && (
120
+
<HelpMessage
121
+
icon={CircleInformationIcon}
122
+
text="Extension updates are available"
123
+
className="moonbase-extension-update-section"
124
+
>
125
+
<div className="moonbase-help-message-buttons">
126
+
<Button
127
+
color={Button.Colors.BRAND}
128
+
size={Button.Sizes.TINY}
129
+
disabled={hitUpdateAll}
130
+
onClick={() => {
131
+
setHitUpdateAll(true);
132
+
MoonbaseSettingsStore.updateAllExtensions();
133
+
}}
134
+
>
135
+
Update all
136
+
</Button>
137
+
<PanelButton
138
+
icon={XSmallIcon}
139
+
onClick={() => {
140
+
MoonbaseSettingsStore.dismissAllExtensionUpdates();
141
+
}}
142
+
/>
143
+
</div>
144
+
</HelpMessage>
145
+
)}
146
+
147
+
{filteredWithUpdates.map((ext) => (
148
+
<ErrorBoundary>
149
+
<ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} />
150
+
</ErrorBoundary>
151
+
))}
152
+
{filteredWithUpdates.length > 0 && filteredWithoutUpdates.length > 0 && (
153
+
<FormDivider className="moonbase-update-divider" />
154
+
)}
155
+
{filteredWithoutUpdates.map((ext) => (
156
+
<ErrorBoundary>
157
+
<ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} />
158
+
</ErrorBoundary>
159
))}
160
</>
161
);
+50
-44
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx
+50
-44
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx
···
2
import { MoonbaseExtension } from "../../../types";
3
4
import React from "@moonlight-mod/wp/react";
5
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
6
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
7
8
type Dependency = {
9
id: string;
···
34
[ExtensionTag.Library]: "Library"
35
};
36
37
-
const UserInfoClasses = spacepack.findByCode(
38
-
"infoScroller",
39
-
"userInfoSection",
40
-
"userInfoSectionHeader"
41
-
)[0].exports;
42
-
43
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
44
45
-
// FIXME: type component keys
46
-
const { Text } = Components;
47
-
48
-
function InfoSection({
49
-
title,
50
-
children
51
-
}: {
52
-
title: string;
53
-
children: React.ReactNode;
54
-
}) {
55
return (
56
<div
57
style={{
58
marginRight: "1em"
59
}}
60
>
61
-
<Text variant="eyebrow" className={UserInfoClasses.userInfoSectionHeader}>
62
{title}
63
</Text>
64
···
69
70
function Badge({
71
color,
72
-
children
73
}: {
74
color: string;
75
children: React.ReactNode;
76
}) {
77
return (
78
<span
79
-
style={{
80
-
borderRadius: ".1875rem",
81
-
padding: "0 0.275rem",
82
-
marginRight: "0.4em",
83
-
backgroundColor: color,
84
-
color: "#fff"
85
-
}}
86
>
87
{children}
88
</span>
89
);
90
}
91
92
-
export default function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) {
93
const authors = ext.manifest?.meta?.authors;
94
const tags = ext.manifest?.meta?.tags;
95
const version = ext.manifest?.version;
96
97
const dependencies: Dependency[] = [];
98
if (ext.manifest.dependencies != null) {
99
dependencies.push(
100
...ext.manifest.dependencies.map((dep) => ({
···
114
}
115
116
if (ext.manifest.incompatible != null) {
117
-
dependencies.push(
118
...ext.manifest.incompatible.map((dep) => ({
119
id: dep,
120
type: DependencyType.Incompatible
···
152
<InfoSection title="Tags">
153
{tags.map((tag, i) => {
154
const name = tagNames[tag];
155
156
return (
157
-
<Badge
158
-
key={i}
159
-
color={
160
-
tag === ExtensionTag.DangerZone
161
-
? "var(--red-400)"
162
-
: "var(--brand-500)"
163
-
}
164
-
>
165
{name}
166
</Badge>
167
);
···
172
{dependencies.length > 0 && (
173
<InfoSection title="Dependencies">
174
{dependencies.map((dep) => {
175
-
const colors = {
176
-
[DependencyType.Dependency]: "var(--brand-500)",
177
-
[DependencyType.Optional]: "var(--orange-400)",
178
-
[DependencyType.Incompatible]: "var(--red-400)"
179
-
};
180
-
const color = colors[dep.type];
181
const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id);
182
183
return (
184
-
<Badge color={color} key={dep.id}>
185
{name}
186
</Badge>
187
);
···
2
import { MoonbaseExtension } from "../../../types";
3
4
import React from "@moonlight-mod/wp/react";
5
+
import { Text } from "@moonlight-mod/wp/discord/components/common/index";
6
7
type Dependency = {
8
id: string;
···
33
[ExtensionTag.Library]: "Library"
34
};
35
36
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
37
38
+
function InfoSection({ title, children }: { title: string; children: React.ReactNode }) {
39
return (
40
<div
41
style={{
42
marginRight: "1em"
43
}}
44
>
45
+
<Text variant="eyebrow" className="moonlight-card-info-header">
46
{title}
47
</Text>
48
···
53
54
function Badge({
55
color,
56
+
children,
57
+
style = {},
58
+
onClick
59
}: {
60
color: string;
61
children: React.ReactNode;
62
+
style?: React.CSSProperties;
63
+
onClick?: () => void;
64
}) {
65
+
if (onClick) style.cursor ??= "pointer";
66
return (
67
<span
68
+
className="moonlight-card-badge"
69
+
style={
70
+
{
71
+
"--badge-color": color,
72
+
...style
73
+
} as React.CSSProperties
74
+
}
75
+
onClick={onClick}
76
>
77
{children}
78
</span>
79
);
80
}
81
82
+
export default function ExtensionInfo({
83
+
ext,
84
+
selectTag
85
+
}: {
86
+
ext: MoonbaseExtension;
87
+
selectTag: (tag: string) => void;
88
+
}) {
89
const authors = ext.manifest?.meta?.authors;
90
const tags = ext.manifest?.meta?.tags;
91
const version = ext.manifest?.version;
92
93
const dependencies: Dependency[] = [];
94
+
const incompatible: Dependency[] = [];
95
+
96
if (ext.manifest.dependencies != null) {
97
dependencies.push(
98
...ext.manifest.dependencies.map((dep) => ({
···
112
}
113
114
if (ext.manifest.incompatible != null) {
115
+
incompatible.push(
116
...ext.manifest.incompatible.map((dep) => ({
117
id: dep,
118
type: DependencyType.Incompatible
···
150
<InfoSection title="Tags">
151
{tags.map((tag, i) => {
152
const name = tagNames[tag];
153
+
let color = "var(--bg-mod-strong)";
154
+
let style;
155
+
if (tag === ExtensionTag.DangerZone) {
156
+
color = "var(--red-460)";
157
+
style = { color: "var(--primary-230)" };
158
+
}
159
160
return (
161
+
<Badge key={i} color={color} style={style} onClick={() => selectTag(tag)}>
162
{name}
163
</Badge>
164
);
···
169
{dependencies.length > 0 && (
170
<InfoSection title="Dependencies">
171
{dependencies.map((dep) => {
172
+
const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id);
173
+
174
+
// TODO: figure out a decent way to distinguish suggested
175
+
return (
176
+
<Badge color="var(--bg-mod-strong)" key={dep.id}>
177
+
{name}
178
+
</Badge>
179
+
);
180
+
})}
181
+
</InfoSection>
182
+
)}
183
+
184
+
{incompatible.length > 0 && (
185
+
<InfoSection title="Incompatible">
186
+
{incompatible.map((dep) => {
187
const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id);
188
189
return (
190
+
<Badge color="var(--bg-mod-strong)" key={dep.id}>
191
{name}
192
</Badge>
193
);
+79
-46
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx
+79
-46
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx
···
1
// TODO: clean up the styling here
2
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
3
import React from "@moonlight-mod/wp/react";
4
import { MoonbaseExtension } from "core-extensions/src/moonbase/types";
5
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
6
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
7
import { ExtensionLoadSource } from "@moonlight-mod/types";
8
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
9
10
-
const {
11
-
openModalLazy,
12
-
closeModal
13
-
} = require("@moonlight-mod/wp/discord/components/common/index");
14
-
const Popup = spacepack.findByCode(".minorContainer", "secondaryAction")[0]
15
-
.exports.default;
16
17
const presentableLoadSources: Record<ExtensionLoadSource, string> = {
18
[ExtensionLoadSource.Developer]: "Local extension", // should never show up
···
31
option: string | undefined;
32
setOption: (pick: string | undefined) => void;
33
}) {
34
-
const { SingleSelect } = Components;
35
-
36
return (
37
<SingleSelect
38
key={id}
···
42
return {
43
value: candidate.uniqueId.toString(),
44
label:
45
-
candidate.source.url ??
46
-
presentableLoadSources[candidate.source.type] ??
47
-
candidate.manifest.version ??
48
-
""
49
};
50
})}
51
onChange={(value: string) => {
52
setOption(value);
53
}}
54
-
// @ts-expect-error no thanks
55
placeholder="Missing extension"
56
/>
57
);
58
}
59
60
-
function OurPopup({
61
deps,
62
-
transitionState,
63
-
id
64
}: {
65
deps: Record<string, MoonbaseExtension[]>;
66
transitionState: number | null;
67
-
id: string;
68
}) {
69
-
const { Text } = Components;
70
-
71
-
const amountNotAvailable = Object.values(deps).filter(
72
-
(candidates) => candidates.length === 0
73
-
).length;
74
75
-
const [options, setOptions] = React.useState<
76
-
Record<string, string | undefined>
77
-
>(
78
Object.fromEntries(
79
Object.entries(deps).map(([id, candidates]) => [
80
id,
···
84
);
85
86
return (
87
-
<Popup
88
body={
89
<Flex
90
style={{
···
93
direction={Flex.Direction.VERTICAL}
94
>
95
<Text variant="text-md/normal">
96
-
This extension depends on other extensions which are not downloaded.
97
-
Choose which extensions to download.
98
</Text>
99
100
{amountNotAvailable > 0 && (
101
<Text variant="text-md/normal">
102
{amountNotAvailable} extension
103
-
{amountNotAvailable > 1 ? "s" : ""} could not be found, and must
104
-
be installed manually.
105
</Text>
106
)}
107
···
142
}
143
cancelText="Cancel"
144
confirmText="Install"
145
-
onCancel={() => {
146
-
closeModal(id);
147
-
}}
148
onConfirm={() => {
149
-
closeModal(id);
150
151
for (const pick of Object.values(options)) {
152
if (pick != null) {
···
160
);
161
}
162
163
-
export async function doPopup(deps: Record<string, MoonbaseExtension[]>) {
164
-
const id: string = await openModalLazy(async () => {
165
-
// eslint-disable-next-line react/display-name
166
return ({ transitionState }: { transitionState: number | null }) => {
167
-
return <OurPopup transitionState={transitionState} deps={deps} id={id} />;
168
};
169
});
170
}
171
172
-
export default async function installWithDependencyPopup(uniqueId: number) {
173
-
await MoonbaseSettingsStore.installExtension(uniqueId);
174
-
const deps = await MoonbaseSettingsStore.getDependencies(uniqueId);
175
-
if (deps != null) {
176
-
await doPopup(deps);
177
-
}
178
}
···
1
// TODO: clean up the styling here
2
import React from "@moonlight-mod/wp/react";
3
import { MoonbaseExtension } from "core-extensions/src/moonbase/types";
4
+
import { openModalLazy, useModalsStore, closeModal } from "@moonlight-mod/wp/discord/modules/modals/Modals";
5
+
import { SingleSelect, Text } from "@moonlight-mod/wp/discord/components/common/index";
6
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
7
import { ExtensionLoadSource } from "@moonlight-mod/types";
8
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
9
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
10
11
+
let ConfirmModal: typeof import("@moonlight-mod/wp/discord/components/modals/ConfirmModal").default;
12
+
13
+
function close() {
14
+
const ModalStore = useModalsStore.getState();
15
+
closeModal(ModalStore.default[0].key);
16
+
}
17
+
18
+
// do this to avoid a hard dependency
19
+
function lazyLoad() {
20
+
if (!ConfirmModal) {
21
+
ConfirmModal = spacepack.require("discord/components/modals/ConfirmModal").default;
22
+
}
23
+
}
24
25
const presentableLoadSources: Record<ExtensionLoadSource, string> = {
26
[ExtensionLoadSource.Developer]: "Local extension", // should never show up
···
39
option: string | undefined;
40
setOption: (pick: string | undefined) => void;
41
}) {
42
return (
43
<SingleSelect
44
key={id}
···
48
return {
49
value: candidate.uniqueId.toString(),
50
label:
51
+
candidate.source.url ?? presentableLoadSources[candidate.source.type] ?? candidate.manifest.version ?? ""
52
};
53
})}
54
onChange={(value: string) => {
55
setOption(value);
56
}}
57
placeholder="Missing extension"
58
/>
59
);
60
}
61
62
+
function MissingExtensionPopup({
63
deps,
64
+
transitionState
65
}: {
66
deps: Record<string, MoonbaseExtension[]>;
67
transitionState: number | null;
68
}) {
69
+
lazyLoad();
70
+
const amountNotAvailable = Object.values(deps).filter((candidates) => candidates.length === 0).length;
71
72
+
const [options, setOptions] = React.useState<Record<string, string | undefined>>(
73
Object.fromEntries(
74
Object.entries(deps).map(([id, candidates]) => [
75
id,
···
79
);
80
81
return (
82
+
<ConfirmModal
83
body={
84
<Flex
85
style={{
···
88
direction={Flex.Direction.VERTICAL}
89
>
90
<Text variant="text-md/normal">
91
+
This extension depends on other extensions which are not downloaded. Choose which extensions to download.
92
</Text>
93
94
{amountNotAvailable > 0 && (
95
<Text variant="text-md/normal">
96
{amountNotAvailable} extension
97
+
{amountNotAvailable > 1 ? "s" : ""} could not be found, and must be installed manually.
98
</Text>
99
)}
100
···
135
}
136
cancelText="Cancel"
137
confirmText="Install"
138
+
onCancel={close}
139
onConfirm={() => {
140
+
close();
141
142
for (const pick of Object.values(options)) {
143
if (pick != null) {
···
151
);
152
}
153
154
+
export async function doMissingExtensionPopup(deps: Record<string, MoonbaseExtension[]>) {
155
+
await openModalLazy(async () => {
156
return ({ transitionState }: { transitionState: number | null }) => {
157
+
return <MissingExtensionPopup transitionState={transitionState} deps={deps} />;
158
};
159
});
160
}
161
162
+
function GenericExtensionPopup({
163
+
title,
164
+
content,
165
+
transitionState,
166
+
uniqueId,
167
+
cb
168
+
}: {
169
+
title: string;
170
+
content: string;
171
+
transitionState: number | null;
172
+
uniqueId: number;
173
+
cb: () => void;
174
+
}) {
175
+
lazyLoad();
176
+
177
+
return (
178
+
<ConfirmModal
179
+
title={title}
180
+
body={
181
+
<Flex>
182
+
<Text variant="text-md/normal">{content}</Text>
183
+
</Flex>
184
+
}
185
+
confirmText="Yes"
186
+
cancelText="No"
187
+
onCancel={close}
188
+
onConfirm={() => {
189
+
close();
190
+
cb();
191
+
}}
192
+
transitionState={transitionState}
193
+
/>
194
+
);
195
+
}
196
+
197
+
export async function doGenericExtensionPopup(title: string, content: string, uniqueId: number, cb: () => void) {
198
+
await openModalLazy(async () => {
199
+
return ({ transitionState }: { transitionState: number | null }) => {
200
+
return (
201
+
<GenericExtensionPopup
202
+
title={title}
203
+
content={content}
204
+
transitionState={transitionState}
205
+
uniqueId={uniqueId}
206
+
cb={cb}
207
+
/>
208
+
);
209
+
};
210
+
});
211
}
+104
-145
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
+104
-145
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
···
11
12
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
13
import React from "@moonlight-mod/wp/react";
14
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
15
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
16
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
17
18
type SettingsProps = {
19
ext: MoonbaseExtension;
···
21
setting: ExtensionSettingsManifest;
22
disabled: boolean;
23
};
24
-
25
type SettingsComponent = React.ComponentType<SettingsProps>;
26
27
-
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
28
const Margins = spacepack.require("discord/styles/shared/Margins.css");
29
30
function useConfigEntry<T>(uniqueId: number, name: string) {
31
return useStateFromStores(
32
[MoonbaseSettingsStore],
33
() => {
34
return {
35
value: MoonbaseSettingsStore.getExtensionConfig<T>(uniqueId, name),
36
-
displayName: MoonbaseSettingsStore.getExtensionConfigName(
37
-
uniqueId,
38
-
name
39
-
),
40
-
description: MoonbaseSettingsStore.getExtensionConfigDescription(
41
-
uniqueId,
42
-
name
43
-
)
44
};
45
},
46
[uniqueId, name]
···
48
}
49
50
function Boolean({ ext, name, setting, disabled }: SettingsProps) {
51
-
const { FormSwitch } = Components;
52
-
const { value, displayName, description } = useConfigEntry<boolean>(
53
-
ext.uniqueId,
54
-
name
55
-
);
56
57
return (
58
<FormSwitch
···
62
onChange={(value: boolean) => {
63
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
64
}}
65
-
note={description}
66
className={`${Margins.marginReset} ${Margins.marginTop20}`}
67
>
68
{displayName}
···
71
}
72
73
function Number({ ext, name, setting, disabled }: SettingsProps) {
74
-
const { FormItem, FormText, Slider } = Components;
75
-
const { value, displayName, description } = useConfigEntry<number>(
76
-
ext.uniqueId,
77
-
name
78
-
);
79
80
const castedSetting = setting as NumberSettingType;
81
-
const min = castedSetting.min ?? 0;
82
-
const max = castedSetting.max ?? 100;
83
84
return (
85
<FormItem className={Margins.marginTop20} title={displayName}>
86
-
{description && <FormText>{description}</FormText>}
87
-
<Slider
88
-
initialValue={value ?? 0}
89
-
disabled={disabled}
90
-
minValue={castedSetting.min ?? 0}
91
-
maxValue={castedSetting.max ?? 100}
92
-
onValueChange={(value: number) => {
93
-
const rounded = Math.max(min, Math.min(max, Math.round(value)));
94
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
95
-
}}
96
-
/>
97
</FormItem>
98
);
99
}
100
101
function String({ ext, name, setting, disabled }: SettingsProps) {
102
-
const { FormItem, FormText, TextInput } = Components;
103
-
const { value, displayName, description } = useConfigEntry<string>(
104
-
ext.uniqueId,
105
-
name
106
-
);
107
108
return (
109
<FormItem className={Margins.marginTop20} title={displayName}>
110
-
{description && (
111
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
112
-
)}
113
<TextInput
114
value={value ?? ""}
115
-
onChange={(value: string) => {
116
-
if (disabled) return;
117
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
118
-
}}
119
/>
120
</FormItem>
121
);
122
}
123
124
function MultilineString({ ext, name, setting, disabled }: SettingsProps) {
125
-
const { FormItem, FormText, TextArea } = Components;
126
-
const { value, displayName, description } = useConfigEntry<string>(
127
-
ext.uniqueId,
128
-
name
129
-
);
130
131
return (
132
<FormItem className={Margins.marginTop20} title={displayName}>
133
-
{description && (
134
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
135
-
)}
136
<TextArea
137
rows={5}
138
value={value ?? ""}
139
className={"moonbase-resizeable"}
140
-
onChange={(value: string) => {
141
-
if (disabled) return;
142
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
143
-
}}
144
/>
145
</FormItem>
146
);
147
}
148
149
function Select({ ext, name, setting, disabled }: SettingsProps) {
150
-
const { FormItem, FormText, SingleSelect } = Components;
151
-
const { value, displayName, description } = useConfigEntry<string>(
152
-
ext.uniqueId,
153
-
name
154
-
);
155
156
const castedSetting = setting as SelectSettingType;
157
const options = castedSetting.options;
158
159
return (
160
<FormItem className={Margins.marginTop20} title={displayName}>
161
-
{description && (
162
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
163
-
)}
164
<SingleSelect
165
autofocus={false}
166
clearable={false}
167
value={value ?? ""}
168
-
options={options.map((o: SelectOption) =>
169
-
typeof o === "string" ? { value: o, label: o } : o
170
-
)}
171
onChange={(value: string) => {
172
if (disabled) return;
173
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
···
178
}
179
180
function MultiSelect({ ext, name, setting, disabled }: SettingsProps) {
181
-
const { FormItem, FormText, Select, useVariableSelect, multiSelect } =
182
-
Components;
183
-
const { value, displayName, description } = useConfigEntry<string | string[]>(
184
-
ext.uniqueId,
185
-
name
186
-
);
187
188
const castedSetting = setting as MultiSelectSettingType;
189
const options = castedSetting.options;
190
191
return (
192
<FormItem className={Margins.marginTop20} title={displayName}>
193
-
{description && (
194
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
195
-
)}
196
-
<Select
197
autofocus={false}
198
clearable={false}
199
closeOnSelect={false}
200
-
options={options.map((o: SelectOption) =>
201
-
typeof o === "string" ? { value: o, label: o } : o
202
-
)}
203
{...useVariableSelect({
204
onSelectInteraction: multiSelect,
205
-
value: new Set(Array.isArray(value) ? value : [value]),
206
onChange: (value: string) => {
207
if (disabled) return;
208
-
MoonbaseSettingsStore.setExtensionConfig(
209
-
ext.id,
210
-
name,
211
-
Array.from(value)
212
-
);
213
}
214
})}
215
/>
···
217
);
218
}
219
220
-
const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0]
221
-
.exports;
222
-
223
-
// FIXME: type component keys
224
-
const { CircleXIcon } = Components;
225
-
226
-
function RemoveEntryButton({
227
-
onClick,
228
-
disabled
229
-
}: {
230
-
onClick: () => void;
231
-
disabled: boolean;
232
-
}) {
233
-
const { Tooltip, Clickable } = Components;
234
return (
235
-
<div className={RemoveButtonClasses.removeButtonContainer}>
236
<Tooltip text="Remove entry" position="top">
237
{(props: any) => (
238
-
<Clickable
239
-
{...props}
240
-
className={RemoveButtonClasses.removeButton}
241
-
onClick={onClick}
242
-
>
243
<CircleXIcon width={16} height={16} />
244
</Clickable>
245
)}
···
249
}
250
251
function List({ ext, name, setting, disabled }: SettingsProps) {
252
-
const { FormItem, FormText, TextInput, Button } = Components;
253
-
const { value, displayName, description } = useConfigEntry<string[]>(
254
-
ext.uniqueId,
255
-
name
256
-
);
257
258
const entries = value ?? [];
259
-
const updateConfig = () =>
260
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries);
261
262
return (
263
<FormItem className={Margins.marginTop20} title={displayName}>
264
-
{description && (
265
-
<FormText className={Margins.marginBottom4}>{description}</FormText>
266
-
)}
267
<Flex direction={Flex.Direction.VERTICAL}>
268
{entries.map((val, i) => (
269
// FIXME: stylesheets
···
315
}
316
317
function Dictionary({ ext, name, setting, disabled }: SettingsProps) {
318
-
const { FormItem, FormText, TextInput, Button } = Components;
319
-
const { value, displayName, description } = useConfigEntry<
320
-
Record<string, string>
321
-
>(ext.uniqueId, name);
322
323
const entries = Object.entries(value ?? {});
324
-
const updateConfig = () =>
325
-
MoonbaseSettingsStore.setExtensionConfig(
326
-
ext.id,
327
-
name,
328
-
Object.fromEntries(entries)
329
-
);
330
331
return (
332
<FormItem className={Margins.marginTop20} title={displayName}>
333
-
{description && (
334
-
<FormText className={Margins.marginBottom4}>{description}</FormText>
335
-
)}
336
<Flex direction={Flex.Direction.VERTICAL}>
337
{entries.map(([key, val], i) => (
338
// FIXME: stylesheets
···
399
[MoonbaseSettingsStore],
400
() => {
401
return {
402
-
component: MoonbaseSettingsStore.getExtensionConfigComponent(
403
-
ext.id,
404
-
name
405
-
)
406
};
407
},
408
[ext.uniqueId, name]
409
);
410
411
if (Component == null) {
412
-
const { Text } = Components;
413
return (
414
-
<Text variant="text/md/normal">{`Custom setting "${displayName}" is missing a component. Perhaps the extension is not installed?`}</Text>
415
);
416
}
417
418
return (
419
-
<Component
420
-
value={value}
421
-
setValue={(value) =>
422
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)
423
-
}
424
-
/>
425
);
426
}
427
···
445
export default function Settings({ ext }: { ext: MoonbaseExtension }) {
446
return (
447
<Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}>
448
-
{Object.entries(ext.manifest.settings!).map(([name, setting]) => (
449
<Setting
450
ext={ext}
451
key={name}
···
11
12
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
13
import React from "@moonlight-mod/wp/react";
14
+
import {
15
+
FormSwitch,
16
+
FormItem,
17
+
FormText,
18
+
TextInput,
19
+
Slider,
20
+
TextArea,
21
+
Tooltip,
22
+
Clickable,
23
+
CircleXIcon,
24
+
Text,
25
+
SingleSelect,
26
+
Button,
27
+
useVariableSelect,
28
+
multiSelect,
29
+
Select as DiscordSelect,
30
+
NumberInputStepper
31
+
} from "@moonlight-mod/wp/discord/components/common/index";
32
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
33
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
34
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
35
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
36
+
import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary";
37
+
38
+
let GuildSettingsRoleEditClasses: any;
39
+
spacepack
40
+
.lazyLoad(
41
+
"renderArtisanalHack",
42
+
/\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"GuildSettings"/,
43
+
/webpackId:(\d+),name:"GuildSettings"/
44
+
)
45
+
.then(
46
+
() =>
47
+
(GuildSettingsRoleEditClasses = spacepack.require(
48
+
"discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"
49
+
))
50
+
);
51
52
type SettingsProps = {
53
ext: MoonbaseExtension;
···
55
setting: ExtensionSettingsManifest;
56
disabled: boolean;
57
};
58
type SettingsComponent = React.ComponentType<SettingsProps>;
59
60
const Margins = spacepack.require("discord/styles/shared/Margins.css");
61
62
+
function markdownify(str: string) {
63
+
return MarkupUtils.parse(str, true, {
64
+
hideSimpleEmbedContent: true,
65
+
allowLinks: true
66
+
});
67
+
}
68
+
69
function useConfigEntry<T>(uniqueId: number, name: string) {
70
return useStateFromStores(
71
[MoonbaseSettingsStore],
72
() => {
73
return {
74
value: MoonbaseSettingsStore.getExtensionConfig<T>(uniqueId, name),
75
+
displayName: MoonbaseSettingsStore.getExtensionConfigName(uniqueId, name),
76
+
description: MoonbaseSettingsStore.getExtensionConfigDescription(uniqueId, name)
77
};
78
},
79
[uniqueId, name]
···
81
}
82
83
function Boolean({ ext, name, setting, disabled }: SettingsProps) {
84
+
const { value, displayName, description } = useConfigEntry<boolean>(ext.uniqueId, name);
85
86
return (
87
<FormSwitch
···
91
onChange={(value: boolean) => {
92
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
93
}}
94
+
note={description != null ? markdownify(description) : undefined}
95
className={`${Margins.marginReset} ${Margins.marginTop20}`}
96
>
97
{displayName}
···
100
}
101
102
function Number({ ext, name, setting, disabled }: SettingsProps) {
103
+
const { value, displayName, description } = useConfigEntry<number>(ext.uniqueId, name);
104
105
const castedSetting = setting as NumberSettingType;
106
+
const min = castedSetting.min;
107
+
const max = castedSetting.max;
108
+
109
+
const onChange = (value: number) => {
110
+
const rounded = min == null || max == null ? Math.round(value) : Math.max(min, Math.min(max, Math.round(value)));
111
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
112
+
};
113
114
return (
115
<FormItem className={Margins.marginTop20} title={displayName}>
116
+
{min == null || max == null ? (
117
+
<Flex justify={Flex.Justify.BETWEEN} direction={Flex.Direction.HORIZONTAL}>
118
+
{description && <FormText>{markdownify(description)}</FormText>}
119
+
<NumberInputStepper value={value ?? 0} onChange={onChange} />
120
+
</Flex>
121
+
) : (
122
+
<>
123
+
{description && <FormText>{markdownify(description)}</FormText>}
124
+
<Slider
125
+
initialValue={value ?? 0}
126
+
disabled={disabled}
127
+
minValue={min}
128
+
maxValue={max}
129
+
onValueChange={onChange}
130
+
onValueRender={(value: number) => `${Math.round(value)}`}
131
+
/>
132
+
</>
133
+
)}
134
</FormItem>
135
);
136
}
137
138
function String({ ext, name, setting, disabled }: SettingsProps) {
139
+
const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name);
140
141
return (
142
<FormItem className={Margins.marginTop20} title={displayName}>
143
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
144
<TextInput
145
value={value ?? ""}
146
+
disabled={disabled}
147
+
onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)}
148
/>
149
</FormItem>
150
);
151
}
152
153
function MultilineString({ ext, name, setting, disabled }: SettingsProps) {
154
+
const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name);
155
156
return (
157
<FormItem className={Margins.marginTop20} title={displayName}>
158
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
159
<TextArea
160
rows={5}
161
value={value ?? ""}
162
+
disabled={disabled}
163
className={"moonbase-resizeable"}
164
+
onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)}
165
/>
166
</FormItem>
167
);
168
}
169
170
function Select({ ext, name, setting, disabled }: SettingsProps) {
171
+
const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name);
172
173
const castedSetting = setting as SelectSettingType;
174
const options = castedSetting.options;
175
176
return (
177
<FormItem className={Margins.marginTop20} title={displayName}>
178
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
179
<SingleSelect
180
autofocus={false}
181
clearable={false}
182
value={value ?? ""}
183
+
options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))}
184
onChange={(value: string) => {
185
if (disabled) return;
186
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
···
191
}
192
193
function MultiSelect({ ext, name, setting, disabled }: SettingsProps) {
194
+
const { value, displayName, description } = useConfigEntry<string | string[]>(ext.uniqueId, name);
195
196
const castedSetting = setting as MultiSelectSettingType;
197
const options = castedSetting.options;
198
199
return (
200
<FormItem className={Margins.marginTop20} title={displayName}>
201
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
202
+
<DiscordSelect
203
autofocus={false}
204
clearable={false}
205
closeOnSelect={false}
206
+
options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))}
207
{...useVariableSelect({
208
onSelectInteraction: multiSelect,
209
+
value: value == null ? new Set() : new Set(Array.isArray(value) ? value : [value]),
210
onChange: (value: string) => {
211
if (disabled) return;
212
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Array.from(value));
213
}
214
})}
215
/>
···
217
);
218
}
219
220
+
function RemoveEntryButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) {
221
return (
222
+
<div className={GuildSettingsRoleEditClasses.removeButtonContainer}>
223
<Tooltip text="Remove entry" position="top">
224
{(props: any) => (
225
+
<Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}>
226
<CircleXIcon width={16} height={16} />
227
</Clickable>
228
)}
···
232
}
233
234
function List({ ext, name, setting, disabled }: SettingsProps) {
235
+
const { value, displayName, description } = useConfigEntry<string[]>(ext.uniqueId, name);
236
237
const entries = value ?? [];
238
+
const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries);
239
240
return (
241
<FormItem className={Margins.marginTop20} title={displayName}>
242
+
{description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>}
243
<Flex direction={Flex.Direction.VERTICAL}>
244
{entries.map((val, i) => (
245
// FIXME: stylesheets
···
291
}
292
293
function Dictionary({ ext, name, setting, disabled }: SettingsProps) {
294
+
const { value, displayName, description } = useConfigEntry<Record<string, string>>(ext.uniqueId, name);
295
296
const entries = Object.entries(value ?? {});
297
+
const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Object.fromEntries(entries));
298
299
return (
300
<FormItem className={Margins.marginTop20} title={displayName}>
301
+
{description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>}
302
<Flex direction={Flex.Direction.VERTICAL}>
303
{entries.map(([key, val], i) => (
304
// FIXME: stylesheets
···
365
[MoonbaseSettingsStore],
366
() => {
367
return {
368
+
component: MoonbaseSettingsStore.getExtensionConfigComponent(ext.id, name)
369
};
370
},
371
[ext.uniqueId, name]
372
);
373
374
if (Component == null) {
375
return (
376
+
<Text variant="text-md/normal">{`Custom setting "${displayName}" is missing a component. Perhaps the extension is not installed?`}</Text>
377
);
378
}
379
380
return (
381
+
<ErrorBoundary>
382
+
<Component value={value} setValue={(value) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} />
383
+
</ErrorBoundary>
384
);
385
}
386
···
404
export default function Settings({ ext }: { ext: MoonbaseExtension }) {
405
return (
406
<Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}>
407
+
{Object.entries(ext.settingsOverride ?? ext.manifest.settings!).map(([name, setting]) => (
408
<Setting
409
ext={ext}
410
key={name}
+23
-29
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
+23
-29
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
···
1
import React from "@moonlight-mod/wp/react";
2
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
3
-
import {
4
-
Text,
5
-
TabBar
6
-
} from "@moonlight-mod/wp/discord/components/common/index";
7
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
8
import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores";
9
10
import ExtensionsPage from "./extensions";
11
import ConfigPage from "./config";
12
import Update from "./update";
13
-
14
-
const { Divider } = spacepack.findByCode(".forumOrHome]:")[0].exports.Z;
15
-
const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0]
16
-
.exports;
17
-
const TabBarClasses = spacepack.findByCode("nowPlayingColumn:")[0].exports;
18
-
const { setSection, clearSubsection } = spacepack.findByExports(
19
-
"setSection",
20
-
"clearSubsection"
21
-
)[0].exports.Z;
22
-
const Margins = spacepack.require("discord/styles/shared/Margins.css");
23
24
export const pages: {
25
id: string;
···
35
id: "config",
36
name: "Config",
37
element: ConfigPage
38
}
39
];
40
41
export function Moonbase(props: { initialTab?: number } = {}) {
42
-
const subsection = useStateFromStores(
43
-
[UserSettingsModalStore],
44
-
() => UserSettingsModalStore.getSubsection() ?? 0
45
-
);
46
const setSubsection = React.useCallback(
47
(to: string) => {
48
-
if (subsection !== to) setSection("moonbase", to);
49
},
50
[subsection]
51
);
···
53
React.useEffect(
54
() => () => {
55
// Normally there's an onSettingsClose prop you can set but we don't expose it and I don't care enough to add support for it right now
56
-
clearSubsection("moonbase");
57
},
58
[]
59
);
60
61
return (
62
<>
63
-
<div className={`${TitleBarClasses.children} ${Margins.marginBottom20}`}>
64
-
<Text
65
-
className={TitleBarClasses.titleWrapper}
66
-
variant="heading-lg/semibold"
67
-
tag="h2"
68
-
>
69
Moonbase
70
</Text>
71
<Divider />
···
73
selectedItem={subsection}
74
onItemSelect={setSubsection}
75
type="top-pill"
76
-
className={TabBarClasses.tabBar}
77
>
78
{pages.map((page, i) => (
79
-
<TabBar.Item key={page.id} id={i} className={TabBarClasses.item}>
80
{page.name}
81
</TabBar.Item>
82
))}
83
</TabBar>
84
</div>
85
86
<Update />
87
88
{React.createElement(pages[subsection].element)}
89
</>
90
);
91
}
···
1
import React from "@moonlight-mod/wp/react";
2
+
import { Text, TabBar } from "@moonlight-mod/wp/discord/components/common/index";
3
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
4
import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores";
5
6
import ExtensionsPage from "./extensions";
7
import ConfigPage from "./config";
8
+
import AboutPage from "./about";
9
import Update from "./update";
10
+
import RestartAdviceMessage from "./RestartAdvice";
11
+
import { Divider } from "@moonlight-mod/wp/discord/components/common/BaseHeaderBar";
12
+
import HeaderBarClasses from "@moonlight-mod/wp/discord/components/common/HeaderBar.css";
13
+
import PeoplePageClasses from "@moonlight-mod/wp/discord/modules/people/web/PeoplePage.css";
14
+
import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators";
15
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
16
17
export const pages: {
18
id: string;
···
28
id: "config",
29
name: "Config",
30
element: ConfigPage
31
+
},
32
+
{
33
+
id: "about",
34
+
name: "About",
35
+
element: AboutPage
36
}
37
];
38
39
export function Moonbase(props: { initialTab?: number } = {}) {
40
+
const subsection = useStateFromStores([UserSettingsModalStore], () => UserSettingsModalStore.getSubsection() ?? 0);
41
const setSubsection = React.useCallback(
42
(to: string) => {
43
+
if (subsection !== to) UserSettingsModalActionCreators.setSection("moonbase", to);
44
},
45
[subsection]
46
);
···
48
React.useEffect(
49
() => () => {
50
// Normally there's an onSettingsClose prop you can set but we don't expose it and I don't care enough to add support for it right now
51
+
UserSettingsModalActionCreators.clearSubsection("moonbase");
52
},
53
[]
54
);
55
56
return (
57
<>
58
+
<div className={`${HeaderBarClasses.children} ${Margins.marginBottom20}`}>
59
+
<Text className={HeaderBarClasses.titleWrapper} variant="heading-lg/semibold" tag="h2">
60
Moonbase
61
</Text>
62
<Divider />
···
64
selectedItem={subsection}
65
onItemSelect={setSubsection}
66
type="top-pill"
67
+
className={PeoplePageClasses.tabBar}
68
>
69
{pages.map((page, i) => (
70
+
<TabBar.Item key={page.id} id={i} className={PeoplePageClasses.item}>
71
{page.name}
72
</TabBar.Item>
73
))}
74
</TabBar>
75
</div>
76
77
+
<RestartAdviceMessage />
78
<Update />
79
80
{React.createElement(pages[subsection].element)}
81
</>
82
);
83
}
84
+
85
+
export { RestartAdviceMessage, Update };
+104
-67
packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx
+104
-67
packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx
···
1
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
2
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
3
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
4
import React from "@moonlight-mod/wp/react";
5
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
6
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
7
-
8
-
enum UpdateState {
9
-
Ready,
10
-
Working,
11
-
Installed,
12
-
Failed
13
-
}
14
-
15
-
const { ThemeDarkIcon, Text, Button } = Components;
16
-
const Margins = spacepack.require("discord/styles/shared/Margins.css");
17
-
const HelpMessageClasses = spacepack.findByExports("positive", "iconDiv")[0]
18
-
.exports;
19
-
20
-
const logger = moonlight.getLogger("moonbase/ui/update");
21
22
const strings: Record<UpdateState, string> = {
23
[UpdateState.Ready]: "A new version of moonlight is available.",
24
[UpdateState.Working]: "Updating moonlight...",
25
[UpdateState.Installed]: "Updated. Restart Discord to apply changes.",
26
-
[UpdateState.Failed]:
27
-
"Failed to update moonlight. Please use the installer instead."
28
};
29
30
-
export default function Update() {
31
-
const [state, setState] = React.useState(UpdateState.Ready);
32
-
const newVersion = useStateFromStores(
33
-
[MoonbaseSettingsStore],
34
-
() => MoonbaseSettingsStore.newVersion
35
);
36
37
if (newVersion == null) return null;
38
39
-
// reimpl of HelpMessage but with a custom icon
40
return (
41
-
<div
42
-
className={`${Margins.marginBottom20} ${HelpMessageClasses.info} ${HelpMessageClasses.container} moonbase-update-section`}
43
-
>
44
-
<Flex direction={Flex.Direction.HORIZONTAL}>
45
-
<div
46
-
className={HelpMessageClasses.iconDiv}
47
-
style={{
48
-
alignItems: "center"
49
}}
50
>
51
-
<ThemeDarkIcon
52
-
size="sm"
53
-
color="currentColor"
54
-
className={HelpMessageClasses.icon}
55
-
/>
56
-
</div>
57
-
58
-
<Text
59
-
variant="text-sm/medium"
60
-
color="currentColor"
61
-
className={HelpMessageClasses.text}
62
-
>
63
-
{strings[state]}
64
-
</Text>
65
-
</Flex>
66
-
67
-
<Button
68
-
look={Button.Looks.OUTLINED}
69
-
color={Button.Colors.CUSTOM}
70
-
size={Button.Sizes.TINY}
71
-
disabled={state !== UpdateState.Ready}
72
-
onClick={() => {
73
-
setState(UpdateState.Working);
74
-
75
-
MoonbaseSettingsStore.updateMoonlight()
76
-
.then(() => setState(UpdateState.Installed))
77
-
.catch((e) => {
78
-
logger.error(e);
79
-
setState(UpdateState.Failed);
80
-
});
81
-
}}
82
-
>
83
-
Update
84
-
</Button>
85
-
</div>
86
);
87
}
···
1
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
2
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
3
import React from "@moonlight-mod/wp/react";
4
+
import { UpdateState } from "../../types";
5
+
import HelpMessage from "./HelpMessage";
6
+
import { MoonlightBranch } from "@moonlight-mod/types";
7
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
8
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
9
+
import {
10
+
Button,
11
+
Text,
12
+
ModalRoot,
13
+
ModalSize,
14
+
ModalContent,
15
+
ModalHeader,
16
+
Heading,
17
+
ModalCloseButton,
18
+
openModal
19
+
} from "@moonlight-mod/wp/discord/components/common/index";
20
+
import MarkupClasses from "@moonlight-mod/wp/discord/modules/messages/web/Markup.css";
21
+
import ThemeDarkIcon from "@moonlight-mod/wp/moonbase_ThemeDarkIcon";
22
23
const strings: Record<UpdateState, string> = {
24
[UpdateState.Ready]: "A new version of moonlight is available.",
25
[UpdateState.Working]: "Updating moonlight...",
26
[UpdateState.Installed]: "Updated. Restart Discord to apply changes.",
27
+
[UpdateState.Failed]: "Failed to update moonlight. Please use the installer instead."
28
};
29
30
+
function MoonlightChangelog({
31
+
changelog,
32
+
version,
33
+
transitionState,
34
+
onClose
35
+
}: {
36
+
changelog: string;
37
+
version: string;
38
+
transitionState: number | null;
39
+
onClose: () => void;
40
+
}) {
41
+
return (
42
+
<ModalRoot transitionState={transitionState} size={ModalSize.DYNAMIC}>
43
+
<ModalHeader>
44
+
<Flex.Child grow={1} shrink={1}>
45
+
<Heading variant="heading-lg/semibold">moonlight</Heading>
46
+
<Text variant="text-xs/normal">{version}</Text>
47
+
</Flex.Child>
48
+
49
+
<Flex.Child grow={0}>
50
+
<ModalCloseButton onClick={onClose} />
51
+
</Flex.Child>
52
+
</ModalHeader>
53
+
54
+
<ModalContent>
55
+
<Text variant="text-md/normal" className={MarkupClasses.markup} style={{ padding: "1rem" }}>
56
+
{MarkupUtils.parse(changelog, true, {
57
+
allowHeading: true,
58
+
allowList: true,
59
+
allowLinks: true
60
+
})}
61
+
</Text>
62
+
</ModalContent>
63
+
</ModalRoot>
64
);
65
+
}
66
+
67
+
export default function Update() {
68
+
const [newVersion, state] = useStateFromStores([MoonbaseSettingsStore], () => [
69
+
MoonbaseSettingsStore.newVersion,
70
+
MoonbaseSettingsStore.updateState
71
+
]);
72
73
if (newVersion == null) return null;
74
75
return (
76
+
<HelpMessage text={strings[state]} className="moonbase-update-section" icon={ThemeDarkIcon}>
77
+
<div className="moonbase-help-message-buttons">
78
+
{moonlight.branch === MoonlightBranch.STABLE && (
79
+
<Button
80
+
look={Button.Looks.OUTLINED}
81
+
color={Button.Colors.CUSTOM}
82
+
size={Button.Sizes.TINY}
83
+
onClick={() => {
84
+
fetch(`https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/tags/${newVersion}/CHANGELOG.md`)
85
+
.then((r) => r.text())
86
+
.then((changelog) =>
87
+
openModal((modalProps) => {
88
+
return <MoonlightChangelog {...modalProps} changelog={changelog} version={newVersion} />;
89
+
})
90
+
);
91
+
}}
92
+
>
93
+
View changelog
94
+
</Button>
95
+
)}
96
+
97
+
{state === UpdateState.Installed && (
98
+
<Button
99
+
look={Button.Looks.OUTLINED}
100
+
color={Button.Colors.CUSTOM}
101
+
size={Button.Sizes.TINY}
102
+
onClick={() => {
103
+
MoonbaseSettingsStore.restartDiscord();
104
+
}}
105
+
>
106
+
Restart Discord
107
+
</Button>
108
+
)}
109
+
110
+
<Button
111
+
look={Button.Looks.OUTLINED}
112
+
color={Button.Colors.CUSTOM}
113
+
size={Button.Sizes.TINY}
114
+
disabled={state !== UpdateState.Ready}
115
+
onClick={() => {
116
+
MoonbaseSettingsStore.updateMoonlight();
117
}}
118
>
119
+
Update
120
+
</Button>
121
+
</div>
122
+
</HelpMessage>
123
);
124
}
+9
-42
packages/core-extensions/src/moonbase/webpackModules/updates.tsx
+9
-42
packages/core-extensions/src/moonbase/webpackModules/updates.tsx
···
3
import Notices from "@moonlight-mod/wp/notices_notices";
4
import { MoonlightBranch } from "@moonlight-mod/types";
5
import React from "@moonlight-mod/wp/react";
6
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
7
-
8
-
// FIXME: not indexed as importable
9
-
const Constants = spacepack.require("discord/Constants");
10
-
const UserSettingsSections = spacepack.findObjectFromKey(
11
-
Constants,
12
-
"APPEARANCE_THEME_PICKER"
13
-
);
14
-
15
-
const { ThemeDarkIcon } = Components;
16
17
function plural(str: string, num: number) {
18
return `${str}${num > 1 ? "s" : ""}`;
···
21
function listener() {
22
if (
23
MoonbaseSettingsStore.shouldShowNotice &&
24
-
MoonbaseSettingsStore.getExtensionConfigRaw(
25
-
"moonbase",
26
-
"updateBanner",
27
-
true
28
-
)
29
) {
30
-
// @ts-expect-error epic type fail
31
MoonbaseSettingsStore.removeChangeListener(listener);
32
33
const version = MoonbaseSettingsStore.newVersion;
34
-
const extensionUpdateCount = Object.keys(
35
-
MoonbaseSettingsStore.updates
36
-
).length;
37
const hasExtensionUpdates = extensionUpdateCount > 0;
38
39
let message;
···
73
{
74
name: "Open Moonbase",
75
onClick: () => {
76
-
const { open } = spacepack.findByExports(
77
-
"setSection",
78
-
"clearSubsection"
79
-
)[0].exports.Z;
80
-
81
-
// settings is lazy loaded thus lazily patched
82
-
// FIXME: figure out a way to detect if settings has been opened
83
-
// alreadyjust so the transition isnt as jarring
84
-
open(UserSettingsSections.ACCOUNT);
85
-
setTimeout(() => {
86
-
if (
87
-
MoonbaseSettingsStore.getExtensionConfigRaw<boolean>(
88
-
"moonbase",
89
-
"sections",
90
-
false
91
-
)
92
-
) {
93
-
open("moonbase-extensions");
94
-
} else {
95
-
open("moonbase", 0);
96
-
}
97
-
}, 0);
98
return true;
99
}
100
}
···
103
}
104
}
105
106
-
// @ts-expect-error epic type fail
107
MoonbaseSettingsStore.addChangeListener(listener);
···
3
import Notices from "@moonlight-mod/wp/notices_notices";
4
import { MoonlightBranch } from "@moonlight-mod/types";
5
import React from "@moonlight-mod/wp/react";
6
+
import ThemeDarkIcon from "@moonlight-mod/wp/moonbase_ThemeDarkIcon";
7
8
function plural(str: string, num: number) {
9
return `${str}${num > 1 ? "s" : ""}`;
···
12
function listener() {
13
if (
14
MoonbaseSettingsStore.shouldShowNotice &&
15
+
MoonbaseSettingsStore.getExtensionConfigRaw("moonbase", "updateBanner", true)
16
) {
17
MoonbaseSettingsStore.removeChangeListener(listener);
18
19
const version = MoonbaseSettingsStore.newVersion;
20
+
const extensionUpdateCount = Object.keys(MoonbaseSettingsStore.updates).length;
21
const hasExtensionUpdates = extensionUpdateCount > 0;
22
23
let message;
···
57
{
58
name: "Open Moonbase",
59
onClick: () => {
60
+
const { open } = spacepack.require("discord/actions/UserSettingsModalActionCreators").default;
61
+
if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "sections", false)) {
62
+
open("moonbase-extensions");
63
+
} else {
64
+
open("moonbase", "0");
65
+
}
66
return true;
67
}
68
}
···
71
}
72
}
73
74
MoonbaseSettingsStore.addChangeListener(listener);
+5
packages/core-extensions/src/moonbase/wp.d.ts
+5
packages/core-extensions/src/moonbase/wp.d.ts
···
5
declare module "@moonlight-mod/wp/moonbase_stores" {
6
export * from "core-extensions/src/moonbase/webpackModules/stores";
7
}
8
+
9
+
declare module "@moonlight-mod/wp/moonbase_ThemeDarkIcon" {
10
+
import ThemeDarkIcon from "core-extensions/src/moonbase/webpackModules/ThemeDarkIcon";
11
+
export = ThemeDarkIcon;
12
+
}
+150
-43
packages/core-extensions/src/nativeFixes/host.ts
+150
-43
packages/core-extensions/src/nativeFixes/host.ts
···
1
import { app, nativeTheme } from "electron";
2
3
-
const enabledFeatures = app.commandLine
4
-
.getSwitchValue("enable-features")
5
-
.split(",");
6
7
moonlightHost.events.on("window-created", function (browserWindow) {
8
-
if (
9
-
moonlightHost.getConfigOption<boolean>("nativeFixes", "devtoolsThemeFix") ??
10
-
true
11
-
) {
12
browserWindow.webContents.on("devtools-opened", () => {
13
if (!nativeTheme.shouldUseDarkColors) return;
14
nativeTheme.themeSource = "light";
···
19
}
20
});
21
22
-
if (
23
-
moonlightHost.getConfigOption<boolean>(
24
-
"nativeFixes",
25
-
"disableRendererBackgrounding"
26
-
) ??
27
-
true
28
-
) {
29
// Discord already disables UseEcoQoSForBackgroundProcess and some other
30
// related features
31
app.commandLine.appendSwitch("disable-renderer-backgrounding");
···
35
app.commandLine.appendSwitch("disable-background-timer-throttling");
36
}
37
38
if (process.platform === "linux") {
39
-
if (
40
-
moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxAutoscroll") ??
41
-
false
42
-
) {
43
-
app.commandLine.appendSwitch(
44
-
"enable-blink-features",
45
-
"MiddleClickAutoscroll"
46
-
);
47
}
48
49
-
if (
50
-
moonlightHost.getConfigOption<boolean>(
51
-
"nativeFixes",
52
-
"linuxSpeechDispatcher"
53
-
) ??
54
-
true
55
-
) {
56
app.commandLine.appendSwitch("enable-speech-dispatcher");
57
}
58
}
59
60
// NOTE: Only tested if this appears on Windows, it should appear on all when
61
// hardware acceleration is disabled
62
const noAccel = app.commandLine.hasSwitch("disable-gpu-compositing");
63
-
if (
64
-
(moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapi") ?? true) &&
65
-
!noAccel
66
-
) {
67
-
if (process.platform === "linux")
68
// These will eventually be renamed https://source.chromium.org/chromium/chromium/src/+/5482210941a94d70406b8da962426e4faca7fce4
69
-
enabledFeatures.push(
70
-
"VaapiVideoEncoder",
71
-
"VaapiVideoDecoder",
72
-
"VaapiVideoDecodeLinuxGL"
73
-
);
74
}
75
76
-
app.commandLine.appendSwitch(
77
-
"enable-features",
78
-
[...new Set(enabledFeatures)].join(",")
79
-
);
···
1
import { app, nativeTheme } from "electron";
2
+
import * as path from "node:path";
3
+
import * as fs from "node:fs/promises";
4
+
import * as fsSync from "node:fs";
5
+
import { parseTarGzip } from "nanotar";
6
7
+
const logger = moonlightHost.getLogger("nativeFixes/host");
8
+
const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");
9
10
moonlightHost.events.on("window-created", function (browserWindow) {
11
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "devtoolsThemeFix") ?? true) {
12
browserWindow.webContents.on("devtools-opened", () => {
13
if (!nativeTheme.shouldUseDarkColors) return;
14
nativeTheme.themeSource = "light";
···
19
}
20
});
21
22
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "disableRendererBackgrounding") ?? true) {
23
// Discord already disables UseEcoQoSForBackgroundProcess and some other
24
// related features
25
app.commandLine.appendSwitch("disable-renderer-backgrounding");
···
29
app.commandLine.appendSwitch("disable-background-timer-throttling");
30
}
31
32
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vulkan") ?? false) {
33
+
enabledFeatures.push("Vulkan", "DefaultANGLEVulkan", "VulkanFromANGLE");
34
+
}
35
+
36
if (process.platform === "linux") {
37
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxAutoscroll") ?? false) {
38
+
app.commandLine.appendSwitch("enable-blink-features", "MiddleClickAutoscroll");
39
}
40
41
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxSpeechDispatcher") ?? true) {
42
app.commandLine.appendSwitch("enable-speech-dispatcher");
43
}
44
+
45
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxHevcSupport") ?? true) {
46
+
enabledFeatures.push("PlatformHEVCDecoderSupport");
47
+
}
48
}
49
50
// NOTE: Only tested if this appears on Windows, it should appear on all when
51
// hardware acceleration is disabled
52
const noAccel = app.commandLine.hasSwitch("disable-gpu-compositing");
53
+
if ((moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapi") ?? true) && !noAccel) {
54
+
if (process.platform === "linux") {
55
// These will eventually be renamed https://source.chromium.org/chromium/chromium/src/+/5482210941a94d70406b8da962426e4faca7fce4
56
+
enabledFeatures.push("VaapiVideoEncoder", "VaapiVideoDecoder", "VaapiVideoDecodeLinuxGL");
57
+
58
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapiIgnoreDriverChecks") ?? false)
59
+
enabledFeatures.push("VaapiIgnoreDriverChecks");
60
+
}
61
}
62
63
+
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].join(","));
64
+
65
+
if (process.platform === "linux" && moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxUpdater")) {
66
+
const exePath = app.getPath("exe");
67
+
const appName = path.basename(exePath);
68
+
const targetDir = path.dirname(exePath);
69
+
const { releaseChannel }: { releaseChannel: string } = JSON.parse(
70
+
fsSync.readFileSync(path.join(targetDir, "resources", "build_info.json"), "utf8")
71
+
);
72
+
73
+
const updaterModule = require(path.join(moonlightHost.asarPath, "app_bootstrap", "hostUpdater.js"));
74
+
const updater = updaterModule.constructor;
75
+
76
+
async function doUpdate(cb: (percent: number) => void) {
77
+
logger.debug("Extracting to", targetDir);
78
+
79
+
const exists = (path: string) =>
80
+
fs
81
+
.stat(path)
82
+
.then(() => true)
83
+
.catch(() => false);
84
+
85
+
const url = `https://discord.com/api/download/${releaseChannel}?platform=linux&format=tar.gz`;
86
+
const resp = await fetch(url, {
87
+
cache: "no-store"
88
+
});
89
+
90
+
const reader = resp.body!.getReader();
91
+
const contentLength = parseInt(resp.headers.get("Content-Length") ?? "0");
92
+
logger.info(`Expecting ${contentLength} bytes for the update`);
93
+
const bytes = new Uint8Array(contentLength);
94
+
let pos = 0;
95
+
let lastPercent = 0;
96
+
97
+
while (true) {
98
+
const { done, value } = await reader.read();
99
+
if (done) {
100
+
break;
101
+
} else {
102
+
bytes.set(value, pos);
103
+
pos += value.length;
104
+
105
+
const newPercent = Math.floor((pos / contentLength) * 100);
106
+
if (lastPercent !== newPercent) {
107
+
lastPercent = newPercent;
108
+
cb(newPercent);
109
+
}
110
+
}
111
+
}
112
+
113
+
const files = await parseTarGzip(bytes);
114
+
115
+
for (const file of files) {
116
+
if (!file.data) continue;
117
+
// @ts-expect-error What do you mean their own types are wrong
118
+
if (file.type !== "file") continue;
119
+
120
+
// Discord update files are inside of a main "Discord(PTB|Canary)" folder
121
+
const filePath = file.name.replace(`${appName}/`, "");
122
+
logger.info("Extracting", filePath);
123
+
124
+
let targetFilePath = path.join(targetDir, filePath);
125
+
if (filePath === "resources/app.asar") {
126
+
// You tried
127
+
targetFilePath = path.join(targetDir, "resources", "_app.asar");
128
+
} else if (filePath === appName || filePath === "chrome_crashpad_handler") {
129
+
// Can't write over the executable? Just move it! 4head
130
+
if (await exists(targetFilePath)) {
131
+
await fs.rename(targetFilePath, targetFilePath + ".bak");
132
+
await fs.unlink(targetFilePath + ".bak");
133
+
}
134
+
}
135
+
const targetFileDir = path.dirname(targetFilePath);
136
+
137
+
if (!(await exists(targetFileDir))) await fs.mkdir(targetFileDir, { recursive: true });
138
+
await fs.writeFile(targetFilePath, file.data);
139
+
140
+
const mode = file.attrs?.mode;
141
+
if (mode != null) {
142
+
// Not sure why this slice is needed
143
+
await fs.chmod(targetFilePath, mode.slice(-3));
144
+
}
145
+
}
146
+
147
+
logger.debug("Done updating");
148
+
}
149
+
150
+
const realEmit = updater.prototype.emit;
151
+
updater.prototype.emit = function (event: string, ...args: any[]) {
152
+
// Arrow functions don't bind `this` :D
153
+
const call = (event: string, ...args: any[]) => realEmit.call(this, event, ...args);
154
+
155
+
if (event === "update-manually") {
156
+
const latestVerStr: string = args[0];
157
+
logger.debug("update-manually called, intercepting", latestVerStr);
158
+
call("update-available");
159
+
160
+
(async () => {
161
+
try {
162
+
await doUpdate((progress) => {
163
+
call("update-progress", progress);
164
+
});
165
+
// Copied from the win32 updater
166
+
this.updateVersion = latestVerStr;
167
+
call(
168
+
"update-downloaded",
169
+
{},
170
+
releaseChannel,
171
+
latestVerStr,
172
+
new Date(),
173
+
this.updateUrl,
174
+
this.quitAndInstall.bind(this)
175
+
);
176
+
} catch (e) {
177
+
logger.error("Error updating", e);
178
+
}
179
+
})();
180
+
181
+
return this;
182
+
} else {
183
+
return realEmit.call(this, event, ...args);
184
+
}
185
+
};
186
+
}
+36
-1
packages/core-extensions/src/nativeFixes/manifest.json
+36
-1
packages/core-extensions/src/nativeFixes/manifest.json
···
1
{
2
"id": "nativeFixes",
3
"meta": {
4
"name": "Native Fixes",
5
"tagline": "Various configurable fixes for Discord and Electron",
6
-
"authors": ["Cynosphere", "adryd"],
7
"tags": ["fixes"]
8
},
9
"settings": {
10
"devtoolsThemeFix": {
11
"displayName": "Devtools Theme Fix",
12
"description": "Temporary workaround for devtools defaulting to light theme on Electron 32",
13
"type": "boolean",
14
"default": true
15
},
16
"disableRendererBackgrounding": {
17
"displayName": "Disable Renderer Backgrounding",
18
"description": "This is enabled by default as a power saving measure, but it breaks screensharing and websocket connections fairly often",
19
"type": "boolean",
20
"default": true
21
},
22
"linuxAutoscroll": {
23
"displayName": "Enable middle click autoscroll on Linux",
24
"description": "Requires manual configuration of your system to disable middle click paste, has no effect on other operating systems",
25
"type": "boolean",
26
"default": false
27
},
28
"linuxSpeechDispatcher": {
29
"displayName": "Enable speech-dispatcher for TTS on Linux",
30
"description": "Fixes text-to-speech. Has no effect on other operating systems",
31
"type": "boolean",
32
"default": true
33
},
34
"vaapi": {
35
"displayName": "Enable VAAPI features on Linux",
36
"description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems",
37
"type": "boolean",
38
"default": true
39
}
···
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
"id": "nativeFixes",
4
"meta": {
5
"name": "Native Fixes",
6
"tagline": "Various configurable fixes for Discord and Electron",
7
+
"authors": ["Cynosphere", "adryd", "NotNite"],
8
"tags": ["fixes"]
9
},
10
+
"environment": "desktop",
11
"settings": {
12
"devtoolsThemeFix": {
13
+
"advice": "restart",
14
"displayName": "Devtools Theme Fix",
15
"description": "Temporary workaround for devtools defaulting to light theme on Electron 32",
16
"type": "boolean",
17
"default": true
18
},
19
"disableRendererBackgrounding": {
20
+
"advice": "restart",
21
"displayName": "Disable Renderer Backgrounding",
22
"description": "This is enabled by default as a power saving measure, but it breaks screensharing and websocket connections fairly often",
23
"type": "boolean",
24
"default": true
25
},
26
+
"vulkan": {
27
+
"advice": "restart",
28
+
"displayName": "Enable Vulkan renderer",
29
+
"description": "Uses the Vulkan backend for rendering",
30
+
"type": "boolean",
31
+
"default": false
32
+
},
33
"linuxAutoscroll": {
34
+
"advice": "restart",
35
"displayName": "Enable middle click autoscroll on Linux",
36
"description": "Requires manual configuration of your system to disable middle click paste, has no effect on other operating systems",
37
"type": "boolean",
38
"default": false
39
},
40
"linuxSpeechDispatcher": {
41
+
"advice": "restart",
42
"displayName": "Enable speech-dispatcher for TTS on Linux",
43
"description": "Fixes text-to-speech. Has no effect on other operating systems",
44
"type": "boolean",
45
"default": true
46
},
47
"vaapi": {
48
+
"advice": "restart",
49
"displayName": "Enable VAAPI features on Linux",
50
"description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems",
51
+
"type": "boolean",
52
+
"default": true
53
+
},
54
+
"vaapiIgnoreDriverChecks": {
55
+
"advice": "restart",
56
+
"displayName": "Ignore VAAPI driver checks on Linux",
57
+
"description": "Forces hardware video acceleration on some graphics drivers at the cost of stability. Has no effect on other operating systems",
58
+
"type": "boolean",
59
+
"default": false
60
+
},
61
+
"linuxUpdater": {
62
+
"advice": "restart",
63
+
"displayName": "Linux Updater",
64
+
"description": "Actually implements updating Discord on Linux. Has no effect on other operating systems",
65
+
"type": "boolean",
66
+
"default": false
67
+
},
68
+
"linuxHevcSupport": {
69
+
"advice": "restart",
70
+
"displayName": "HEVC support on Linux",
71
+
"description": "You might also need to enable Vulkan renderer. Has no effect on other operating systems",
72
"type": "boolean",
73
"default": true
74
}
+3
-3
packages/core-extensions/src/noHideToken/index.ts
+3
-3
packages/core-extensions/src/noHideToken/index.ts
+1
packages/core-extensions/src/noHideToken/manifest.json
+1
packages/core-extensions/src/noHideToken/manifest.json
+3
-3
packages/core-extensions/src/noTrack/index.ts
+3
-3
packages/core-extensions/src/noTrack/index.ts
+4
-1
packages/core-extensions/src/noTrack/manifest.json
+4
-1
packages/core-extensions/src/noTrack/manifest.json
···
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
"id": "noTrack",
4
"apiLevel": 2,
5
"meta": {
···
10
},
11
"blocked": [
12
"https://*.discord.com/api/v*/science",
13
+
"https://*.discord.com/api/v*/metrics",
14
+
"https://*.discordapp.com/api/v*/science",
15
+
"https://*.discordapp.com/api/v*/metrics"
16
]
17
}
+2
-6
packages/core-extensions/src/notices/index.ts
+2
-6
packages/core-extensions/src/notices/index.ts
···
4
{
5
find: ".GUILD_RAID_NOTIFICATION:",
6
replace: {
7
-
match:
8
-
/(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/,
9
replacement: (orig, createElement) =>
10
`case "__moonlight_notice":return${createElement}(require("notices_component").default,{});${orig}`
11
}
···
28
29
export const webpackModules: Record<string, ExtensionWebpackModule> = {
30
notices: {
31
-
dependencies: [
32
-
{ id: "discord/packages/flux" },
33
-
{ id: "discord/Dispatcher" }
34
-
]
35
},
36
37
component: {
···
4
{
5
find: ".GUILD_RAID_NOTIFICATION:",
6
replace: {
7
+
match: /(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/,
8
replacement: (orig, createElement) =>
9
`case "__moonlight_notice":return${createElement}(require("notices_component").default,{});${orig}`
10
}
···
27
28
export const webpackModules: Record<string, ExtensionWebpackModule> = {
29
notices: {
30
+
dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }]
31
},
32
33
component: {
+1
packages/core-extensions/src/notices/manifest.json
+1
packages/core-extensions/src/notices/manifest.json
+4
-10
packages/core-extensions/src/notices/webpackModules/component.tsx
+4
-10
packages/core-extensions/src/notices/webpackModules/component.tsx
···
1
import React from "@moonlight-mod/wp/react";
2
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
3
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
4
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
5
import NoticesStore from "@moonlight-mod/wp/notices_notices";
6
-
import type { Notice } from "@moonlight-mod/types/coreExtensions/notices";
7
8
-
// FIXME: types
9
-
const { Notice, NoticeCloseButton, PrimaryCTANoticeButton } = Components;
10
-
11
-
function popAndDismiss(notice: Notice) {
12
NoticesStore.popNotice();
13
if (notice?.onDismiss) {
14
notice.onDismiss();
···
32
{notice.element}
33
34
{(notice.showClose ?? true) && (
35
-
<NoticeCloseButton
36
-
onClick={() => popAndDismiss(notice)}
37
-
noticeType="__moonlight_notice"
38
-
/>
39
)}
40
41
{(notice.buttons ?? []).map((button) => (
···
1
import React from "@moonlight-mod/wp/react";
2
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
3
+
import { Notice, NoticeCloseButton, PrimaryCTANoticeButton } from "@moonlight-mod/wp/discord/components/common/index";
4
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
5
import NoticesStore from "@moonlight-mod/wp/notices_notices";
6
+
import type { Notice as NoticeType } from "@moonlight-mod/types/coreExtensions/notices";
7
8
+
function popAndDismiss(notice: NoticeType) {
9
NoticesStore.popNotice();
10
if (notice?.onDismiss) {
11
notice.onDismiss();
···
29
{notice.element}
30
31
{(notice.showClose ?? true) && (
32
+
<NoticeCloseButton onClick={() => popAndDismiss(notice)} noticeType="__moonlight_notice" />
33
)}
34
35
{(notice.buttons ?? []).map((button) => (
+1
-4
packages/core-extensions/src/notices/webpackModules/notices.ts
+1
-4
packages/core-extensions/src/notices/webpackModules/notices.ts
+31
-41
packages/core-extensions/src/quietLoggers/index.ts
+31
-41
packages/core-extensions/src/quietLoggers/index.ts
···
1
import { Patch } from "@moonlight-mod/types";
2
3
const notXssDefensesOnly = () =>
4
-
(moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ??
5
-
false) === false;
6
7
// These patches MUST run before the simple patches, these are to remove loggers
8
// that end up causing syntax errors by the normal patch
···
29
// Patches to simply remove a logger call
30
const stubPatches = [
31
// "sh" is not a valid locale.
32
-
[
33
-
"is not a valid locale",
34
-
/(.)\.error\(""\.concat\((.)," is not a valid locale\."\)\)/g
35
-
],
36
-
['="RunningGameStore"', /.\.info\("games",{.+?}\),/],
37
-
[
38
-
'"[BUILD INFO] Release Channel: "',
39
-
/new .{1,2}\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?"\)\),/
40
-
],
41
-
[
42
-
'.APP_NATIVE_CRASH,"Storage"',
43
-
/console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/
44
-
],
45
-
[
46
-
'.APP_NATIVE_CRASH,"Storage"',
47
-
'console.log("AppCrashedFatalReport: getLastCrash not supported.");'
48
-
],
49
['"[NATIVE INFO] ', /new .{1,2}\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/],
50
['"Spellchecker"', /.\.info\("Switching to ".+?"\(unavailable\)"\);?/g],
51
-
[
52
-
'throw Error("Messages are still loading.");',
53
-
/console\.warn\("Unsupported Locale",.\),/
54
-
],
55
-
["}_dispatchWithDevtools(", /.\.totalTime>100&&.\.verbose\(.+?\);/],
56
-
[
57
-
'"NativeDispatchUtils"',
58
-
/null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/
59
-
],
60
['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/],
61
[
62
'"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "',
63
/.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/
64
],
65
-
[
66
-
'console.warn("Window state not initialized"',
67
-
/console\.warn\("Window state not initialized",.\),/
68
-
]
69
];
70
71
const simplePatches = [
72
// Moment.js deprecation warnings
73
-
["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"],
74
-
75
-
// Zustand related
76
-
[
77
-
/console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\)/g,
78
-
"/*$&*/"
79
-
],
80
-
["this.getDebugLogging()", "false"]
81
] as { [0]: string | RegExp; [1]: string }[];
82
83
export const patches: Patch[] = [
84
{
85
-
find: ".Messages.XSSDefenses",
86
replace: {
87
match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/,
88
replacement: "(true)"
89
}
90
},
91
...loggerFixes,
92
...stubPatches.map((patch) => ({
···
1
import { Patch } from "@moonlight-mod/types";
2
3
const notXssDefensesOnly = () =>
4
+
(moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ?? false) === false;
5
+
6
+
const silenceDiscordLogger = moonlight.getConfigOption<boolean>("quietLoggers", "silenceDiscordLogger") ?? false;
7
8
// These patches MUST run before the simple patches, these are to remove loggers
9
// that end up causing syntax errors by the normal patch
···
30
// Patches to simply remove a logger call
31
const stubPatches = [
32
// "sh" is not a valid locale.
33
+
["is not a valid locale", /void (.)\.error\(""\.concat\((.)," is not a valid locale\."\)\)/g],
34
+
['"[BUILD INFO] Release Channel: "', /new .{1,2}\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?\)\),/],
35
+
['.APP_NATIVE_CRASH,"Storage"', /console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/],
36
+
['.APP_NATIVE_CRASH,"Storage"', 'void console.log("AppCrashedFatalReport: getLastCrash not supported.")'],
37
['"[NATIVE INFO] ', /new .{1,2}\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/],
38
['"Spellchecker"', /.\.info\("Switching to ".+?"\(unavailable\)"\);?/g],
39
+
['throw Error("Messages are still loading.");', /console\.warn\("Unsupported Locale",.\),/],
40
+
["}_dispatchWithDevtools(", /.\.totalTime>.{1,2}&&.\.verbose\(.+?\);/],
41
+
['"NativeDispatchUtils"', /null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/],
42
['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/],
43
[
44
'"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "',
45
/.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/
46
],
47
+
['console.warn("Window state not initialized"', /console\.warn\("Window state not initialized",.\),/]
48
];
49
50
const simplePatches = [
51
// Moment.js deprecation warnings
52
+
["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"]
53
] as { [0]: string | RegExp; [1]: string }[];
54
55
export const patches: Patch[] = [
56
{
57
+
find: ".Messages.SELF_XSS_HEADER",
58
replace: {
59
match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/,
60
replacement: "(true)"
61
}
62
+
},
63
+
// Highlight.js deprecation warnings
64
+
{
65
+
find: "Deprecated as of",
66
+
replace: {
67
+
match: /console\./g,
68
+
replacement: "false&&console."
69
+
},
70
+
prerequisite: notXssDefensesOnly
71
+
},
72
+
// Discord's logger
73
+
{
74
+
find: "ฮฃ:",
75
+
replace: {
76
+
match: "for",
77
+
replacement: "return;for"
78
+
},
79
+
prerequisite: () => silenceDiscordLogger && notXssDefensesOnly()
80
},
81
...loggerFixes,
82
...stubPatches.map((patch) => ({
+9
packages/core-extensions/src/quietLoggers/manifest.json
+9
packages/core-extensions/src/quietLoggers/manifest.json
···
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
"id": "quietLoggers",
4
"apiLevel": 2,
5
"meta": {
···
10
},
11
"settings": {
12
"xssDefensesOnly": {
13
+
"advice": "reload",
14
"displayName": "Only hide self-XSS",
15
"description": "Only disable self XSS prevention log",
16
+
"type": "boolean",
17
+
"default": false
18
+
},
19
+
"silenceDiscordLogger": {
20
+
"advice": "reload",
21
+
"displayName": "Silence Discord logger",
22
+
"description": "Hides all messages from Discord's logger (the logs that start with purple text in brackets)",
23
"type": "boolean",
24
"default": false
25
}
+46
-67
packages/core-extensions/src/rocketship/host/permissions.ts
+46
-67
packages/core-extensions/src/rocketship/host/permissions.ts
···
14
details: Electron.PermissionCheckHandlerHandlerDetails
15
) => boolean;
16
17
-
moonlightHost.events.on(
18
-
"window-created",
19
-
(window: BrowserWindow, isMainWindow: boolean) => {
20
-
if (!isMainWindow) return;
21
-
const windowSession = window.webContents.session;
22
23
-
// setPermissionRequestHandler
24
-
windowSession.setPermissionRequestHandler(
25
-
(webcontents, permission, callback, details) => {
26
-
let cbResult = false;
27
-
function fakeCallback(result: boolean) {
28
-
cbResult = result;
29
-
}
30
31
-
if (caughtPermissionRequestHandler) {
32
-
caughtPermissionRequestHandler(
33
-
webcontents,
34
-
permission,
35
-
fakeCallback,
36
-
details
37
-
);
38
-
}
39
40
-
if (permission === "media" || permission === "display-capture") {
41
-
cbResult = true;
42
-
}
43
44
-
callback(cbResult);
45
-
}
46
-
);
47
48
-
let caughtPermissionRequestHandler: PermissionRequestHandler | undefined;
49
50
-
windowSession.setPermissionRequestHandler =
51
-
function catchSetPermissionRequestHandler(
52
-
handler: (
53
-
webcontents: Electron.WebContents,
54
-
permission: string,
55
-
callback: (permissionGranted: boolean) => void
56
-
) => void
57
-
) {
58
-
caughtPermissionRequestHandler = handler;
59
-
};
60
61
-
// setPermissionCheckHandler
62
-
windowSession.setPermissionCheckHandler(
63
-
(webcontents, permission, requestingOrigin, details) => {
64
-
return false;
65
-
}
66
-
);
67
68
-
let caughtPermissionCheckHandler: PermissionCheckHandler | undefined;
69
70
-
windowSession.setPermissionCheckHandler(
71
-
(webcontents, permission, requestingOrigin, details) => {
72
-
let result = false;
73
74
-
if (caughtPermissionCheckHandler) {
75
-
result = caughtPermissionCheckHandler(
76
-
webcontents,
77
-
permission,
78
-
requestingOrigin,
79
-
details
80
-
);
81
-
}
82
83
-
if (permission === "media" || permission === "display-capture") {
84
-
result = true;
85
-
}
86
87
-
return result;
88
-
}
89
-
);
90
91
-
windowSession.setPermissionCheckHandler =
92
-
function catchSetPermissionCheckHandler(handler: PermissionCheckHandler) {
93
-
caughtPermissionCheckHandler = handler;
94
-
};
95
-
}
96
-
);
···
14
details: Electron.PermissionCheckHandlerHandlerDetails
15
) => boolean;
16
17
+
moonlightHost.events.on("window-created", (window: BrowserWindow, isMainWindow: boolean) => {
18
+
if (!isMainWindow) return;
19
+
const windowSession = window.webContents.session;
20
21
+
// setPermissionRequestHandler
22
+
windowSession.setPermissionRequestHandler((webcontents, permission, callback, details) => {
23
+
let cbResult = false;
24
+
function fakeCallback(result: boolean) {
25
+
cbResult = result;
26
+
}
27
28
+
if (caughtPermissionRequestHandler) {
29
+
caughtPermissionRequestHandler(webcontents, permission, fakeCallback, details);
30
+
}
31
32
+
if (permission === "media" || permission === "display-capture") {
33
+
cbResult = true;
34
+
}
35
36
+
callback(cbResult);
37
+
});
38
39
+
let caughtPermissionRequestHandler: PermissionRequestHandler | undefined;
40
41
+
windowSession.setPermissionRequestHandler = function catchSetPermissionRequestHandler(
42
+
handler: (
43
+
webcontents: Electron.WebContents,
44
+
permission: string,
45
+
callback: (permissionGranted: boolean) => void
46
+
) => void
47
+
) {
48
+
caughtPermissionRequestHandler = handler;
49
+
};
50
51
+
// setPermissionCheckHandler
52
+
windowSession.setPermissionCheckHandler((webcontents, permission, requestingOrigin, details) => {
53
+
return false;
54
+
});
55
56
+
let caughtPermissionCheckHandler: PermissionCheckHandler | undefined;
57
58
+
windowSession.setPermissionCheckHandler((webcontents, permission, requestingOrigin, details) => {
59
+
let result = false;
60
61
+
if (caughtPermissionCheckHandler) {
62
+
result = caughtPermissionCheckHandler(webcontents, permission, requestingOrigin, details);
63
+
}
64
65
+
if (permission === "media" || permission === "display-capture") {
66
+
result = true;
67
+
}
68
69
+
return result;
70
+
});
71
72
+
windowSession.setPermissionCheckHandler = function catchSetPermissionCheckHandler(handler: PermissionCheckHandler) {
73
+
caughtPermissionCheckHandler = handler;
74
+
};
75
+
});
+4
-12
packages/core-extensions/src/rocketship/host/types.ts
+4
-12
packages/core-extensions/src/rocketship/host/types.ts
···
1
// https://github.com/Vencord/venmic/blob/d737ef33eaae7a73d03ec02673e008cf0243434d/lib/module.d.ts
2
type DefaultProps = "node.name" | "application.name";
3
4
-
type LiteralUnion<LiteralType, BaseType extends string> =
5
-
| LiteralType
6
-
| (BaseType & Record<never, never>);
7
8
-
type Optional<Type, Key extends keyof Type> = Partial<Pick<Type, Key>> &
9
-
Omit<Type, Key>;
10
11
-
export type Node<T extends string = never> = Record<
12
-
LiteralUnion<T, string>,
13
-
string
14
-
>;
15
16
export interface LinkData {
17
include: Node[];
···
29
unlink(): void;
30
31
list<T extends string = DefaultProps>(props?: T[]): Node<T>[];
32
-
link(
33
-
data: Optional<LinkData, "exclude"> | Optional<LinkData, "include">
34
-
): boolean;
35
}
···
1
// https://github.com/Vencord/venmic/blob/d737ef33eaae7a73d03ec02673e008cf0243434d/lib/module.d.ts
2
type DefaultProps = "node.name" | "application.name";
3
4
+
type LiteralUnion<LiteralType, BaseType extends string> = LiteralType | (BaseType & Record<never, never>);
5
6
+
type Optional<Type, Key extends keyof Type> = Partial<Pick<Type, Key>> & Omit<Type, Key>;
7
8
+
export type Node<T extends string = never> = Record<LiteralUnion<T, string>, string>;
9
10
export interface LinkData {
11
include: Node[];
···
23
unlink(): void;
24
25
list<T extends string = DefaultProps>(props?: T[]): Node<T>[];
26
+
link(data: Optional<LinkData, "exclude"> | Optional<LinkData, "include">): boolean;
27
}
+23
-31
packages/core-extensions/src/rocketship/host/venmic.ts
+23
-31
packages/core-extensions/src/rocketship/host/venmic.ts
···
7
8
function getPatchbay() {
9
try {
10
-
const venmic = require(
11
-
path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node")
12
-
) as { PatchBay: new () => PatchBay };
13
const patchbay = new venmic.PatchBay();
14
return patchbay;
15
} catch (error) {
···
35
36
patchbay.unlink();
37
return patchbay.link({
38
-
exclude: [
39
-
{ "application.process.id": pid },
40
-
{ "media.class": "Stream/Input/Audio" }
41
-
],
42
ignore_devices: true,
43
only_speakers: true,
44
only_default_speakers: true
···
49
}
50
}
51
52
-
moonlightHost.events.on(
53
-
"window-created",
54
-
(window: BrowserWindow, isMainWindow: boolean) => {
55
-
if (!isMainWindow) return;
56
-
const windowSession = window.webContents.session;
57
58
-
// @ts-expect-error these types ancient
59
-
windowSession.setDisplayMediaRequestHandler(
60
-
(request: any, callback: any) => {
61
-
const linked = linkVenmic();
62
-
desktopCapturer
63
-
.getSources({ types: ["screen", "window"] })
64
-
.then((sources) => {
65
-
//logger.debug("desktopCapturer.getSources", sources);
66
-
logger.debug("Linked to venmic:", linked);
67
68
-
callback({
69
-
video: sources[0],
70
-
audio: "loopback"
71
-
});
72
-
});
73
-
},
74
-
{ useSystemPicker: true }
75
-
);
76
-
}
77
-
);
···
7
8
function getPatchbay() {
9
try {
10
+
const venmic = require(path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node")) as {
11
+
PatchBay: new () => PatchBay;
12
+
};
13
const patchbay = new venmic.PatchBay();
14
return patchbay;
15
} catch (error) {
···
35
36
patchbay.unlink();
37
return patchbay.link({
38
+
exclude: [{ "application.process.id": pid }, { "media.class": "Stream/Input/Audio" }],
39
ignore_devices: true,
40
only_speakers: true,
41
only_default_speakers: true
···
46
}
47
}
48
49
+
moonlightHost.events.on("window-created", (window: BrowserWindow, isMainWindow: boolean) => {
50
+
if (!isMainWindow) return;
51
+
const windowSession = window.webContents.session;
52
53
+
// @ts-expect-error these types ancient
54
+
windowSession.setDisplayMediaRequestHandler(
55
+
(request: any, callback: any) => {
56
+
const linked = linkVenmic();
57
+
desktopCapturer.getSources({ types: ["screen", "window"] }).then((sources) => {
58
+
//logger.debug("desktopCapturer.getSources", sources);
59
+
logger.debug("Linked to venmic:", linked);
60
61
+
callback({
62
+
video: sources[0],
63
+
audio: "loopback"
64
+
});
65
+
});
66
+
},
67
+
{ useSystemPicker: true }
68
+
);
69
+
});
+5
-11
packages/core-extensions/src/rocketship/index.ts
+5
-11
packages/core-extensions/src/rocketship/index.ts
···
9
logger.debug("Devices:", devices);
10
11
// This isn't vencord :(
12
-
const id = devices.find((device) => device.label === "vencord-screen-share")
13
-
?.deviceId;
14
if (!id) return null;
15
logger.debug("Got venmic device ID:", id);
16
···
32
}
33
}
34
35
-
navigator.mediaDevices.getDisplayMedia = async function getDisplayMediaRedirect(
36
-
options
37
-
) {
38
const orig = await getDisplayMediaOrig.call(this, options);
39
40
const venmic = await getVenmicStream();
···
114
replace: [
115
// Prevent loading of krisp native module by stubbing out desktop checks
116
{
117
-
match:
118
-
/\(\(0,.\.isWindows\)\(\)\|\|\(0,.\.isLinux\)\(\)\|\|.+?&&!__OVERLAY__/,
119
replacement: (orig, macosPlatformCheck) => `false&&!__OVERLAY__`
120
},
121
// Enable loading of web krisp equivelant by replacing isWeb with true
122
{
123
-
match:
124
-
/\(0,.\.isWeb\)\(\)&&(.{1,2}\.supports\(.{1,2}\..{1,2}.NOISE_CANCELLATION)/,
125
-
replacement: (orig, supportsNoiseCancellation) =>
126
-
`true&&${supportsNoiseCancellation}`
127
}
128
]
129
}
···
9
logger.debug("Devices:", devices);
10
11
// This isn't vencord :(
12
+
const id = devices.find((device) => device.label === "vencord-screen-share")?.deviceId;
13
if (!id) return null;
14
logger.debug("Got venmic device ID:", id);
15
···
31
}
32
}
33
34
+
navigator.mediaDevices.getDisplayMedia = async function getDisplayMediaRedirect(options) {
35
const orig = await getDisplayMediaOrig.call(this, options);
36
37
const venmic = await getVenmicStream();
···
111
replace: [
112
// Prevent loading of krisp native module by stubbing out desktop checks
113
{
114
+
match: /\(\(0,.\.isWindows\)\(\)\|\|\(0,.\.isLinux\)\(\)\|\|.+?&&!__OVERLAY__/,
115
replacement: (orig, macosPlatformCheck) => `false&&!__OVERLAY__`
116
},
117
// Enable loading of web krisp equivelant by replacing isWeb with true
118
{
119
+
match: /\(0,.\.isWeb\)\(\)&&(.{1,2}\.supports\(.{1,2}\..{1,2}.NOISE_CANCELLATION)/,
120
+
replacement: (orig, supportsNoiseCancellation) => `true&&${supportsNoiseCancellation}`
121
}
122
]
123
}
+4
-1
packages/core-extensions/src/rocketship/manifest.json
+4
-1
packages/core-extensions/src/rocketship/manifest.json
···
1
{
2
"id": "rocketship",
3
"apiLevel": 2,
4
"meta": {
5
"name": "Rocketship",
6
"tagline": "Adds new features when using rocketship",
7
"description": "**This extension only works on Linux when using rocketship:**\nhttps://github.com/moonlight-mod/rocketship\n\nAdds new features to the Discord Linux client with rocketship, such as a better screensharing experience.",
8
-
"authors": ["NotNite", "Cynosphere", "adryd"]
9
}
10
}
···
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
"id": "rocketship",
4
"apiLevel": 2,
5
+
"environment": "desktop",
6
"meta": {
7
"name": "Rocketship",
8
"tagline": "Adds new features when using rocketship",
9
"description": "**This extension only works on Linux when using rocketship:**\nhttps://github.com/moonlight-mod/rocketship\n\nAdds new features to the Discord Linux client with rocketship, such as a better screensharing experience.",
10
+
"authors": ["NotNite", "Cynosphere", "adryd"],
11
+
"deprecated": true
12
}
13
}
+3
-5
packages/core-extensions/src/settings/index.ts
+3
-5
packages/core-extensions/src/settings/index.ts
···
6
find: '"useGenerateUserSettingsSections"',
7
replace: {
8
match: /(?<=\.push\(.+?\)}\)\)}\),)(.+?)}/,
9
-
replacement: (_, sections: string) =>
10
-
`require("settings_settings").Settings._mutateSections(${sections})}`
11
}
12
},
13
{
14
find: 'navId:"user-settings-cog",',
15
replace: {
16
-
match: /children:\[(.)\.map\(.+?\),children:.\((.)\)/,
17
replacement: (orig, sections, section) =>
18
`${orig.replace(
19
/Object\.values\(.\..+?\)/,
20
-
(orig) =>
21
-
`[...require("settings_settings").Settings.sectionNames,...${orig}]`
22
)}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()`
23
}
24
}
···
6
find: '"useGenerateUserSettingsSections"',
7
replace: {
8
match: /(?<=\.push\(.+?\)}\)\)}\),)(.+?)}/,
9
+
replacement: (_, sections: string) => `require("settings_settings").Settings._mutateSections(${sections})}`
10
}
11
},
12
{
13
find: 'navId:"user-settings-cog",',
14
replace: {
15
+
match: /children:\[(\i)\.map\(.+?\),.*?children:\i\((\i)\)/,
16
replacement: (orig, sections, section) =>
17
`${orig.replace(
18
/Object\.values\(.\..+?\)/,
19
+
(orig) => `[...require("settings_settings").Settings.sectionNames,...${orig}]`
20
)}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()`
21
}
22
}
+1
packages/core-extensions/src/settings/manifest.json
+1
packages/core-extensions/src/settings/manifest.json
+11
-13
packages/core-extensions/src/settings/webpackModules/settings.ts
+11
-13
packages/core-extensions/src/settings/webpackModules/settings.ts
···
1
-
import {
2
-
SettingsSection,
3
-
Settings as SettingsType
4
-
} from "@moonlight-mod/types/coreExtensions/settings";
5
6
export const Settings: SettingsType = {
7
ourSections: [],
8
sectionNames: [],
9
sectionMenuItems: {},
10
11
-
addSection: (section, label, element, color = null, pos, notice) => {
12
const data: SettingsSection = {
13
section,
14
label,
15
color,
16
element,
17
pos: pos ?? -4,
18
-
notice: notice
19
};
20
21
Settings.ourSections.push(data);
···
24
},
25
addSectionMenuItems(section, ...newItems) {
26
const data = Settings.ourSections.find((x) => x.section === section);
27
-
if (!data || !("element" in data))
28
-
throw new Error(`Could not find section "${section}"`);
29
(Settings.sectionMenuItems[section] ??= []).push(...newItems);
30
data._moonlight_submenu ??= () => Settings.sectionMenuItems[section];
31
},
···
47
48
_mutateSections: (sections) => {
49
for (const section of Settings.ourSections) {
50
-
sections.splice(
51
-
section.pos < 0 ? sections.length + section.pos : section.pos,
52
-
0,
53
-
section
54
-
);
55
}
56
57
return sections;
···
1
+
import { SettingsSection, Settings as SettingsType } from "@moonlight-mod/types/coreExtensions/settings";
2
+
import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators";
3
4
export const Settings: SettingsType = {
5
ourSections: [],
6
sectionNames: [],
7
sectionMenuItems: {},
8
9
+
addSection: (section, label, element, color = null, pos, notice, onClick) => {
10
const data: SettingsSection = {
11
section,
12
label,
13
color,
14
element,
15
pos: pos ?? -4,
16
+
notice: notice,
17
+
onClick: onClick ?? (() => UserSettingsModalActionCreators.open(section))
18
};
19
20
Settings.ourSections.push(data);
···
23
},
24
addSectionMenuItems(section, ...newItems) {
25
const data = Settings.ourSections.find((x) => x.section === section);
26
+
if (!data || !("element" in data)) throw new Error(`Could not find section "${section}"`);
27
(Settings.sectionMenuItems[section] ??= []).push(...newItems);
28
data._moonlight_submenu ??= () => Settings.sectionMenuItems[section];
29
},
···
45
46
_mutateSections: (sections) => {
47
for (const section of Settings.ourSections) {
48
+
// Discord's `pos` only supports numbers, so lets call the function to get the position.
49
+
if (typeof section.pos === "function") {
50
+
section.pos = section.pos(sections);
51
+
}
52
+
sections.splice(section.pos < 0 ? sections.length + section.pos : section.pos, 0, section);
53
}
54
55
return sections;
+2
packages/core-extensions/src/spacepack/manifest.json
+2
packages/core-extensions/src/spacepack/manifest.json
···
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
"id": "spacepack",
4
"apiLevel": 2,
5
"meta": {
···
10
},
11
"settings": {
12
"addToGlobalScope": {
13
+
"advice": "reload",
14
"displayName": "Add to global scope",
15
"description": "Populates window.spacepack for easier usage in DevTools",
16
"type": "boolean",
+84
-67
packages/core-extensions/src/spacepack/webpackModules/spacepack.ts
+84
-67
packages/core-extensions/src/spacepack/webpackModules/spacepack.ts
···
1
-
import {
2
-
WebpackModule,
3
-
WebpackModuleFunc,
4
-
WebpackRequireType
5
-
} from "@moonlight-mod/types";
6
import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack";
7
8
const webpackRequire = require as unknown as WebpackRequireType;
9
const cache = webpackRequire.c;
···
40
"module",
41
"exports",
42
"require",
43
-
`(${funcStr}).apply(this, arguments)\n` +
44
-
`//# sourceURL=Webpack-Module-${module}`
45
) as WebpackModuleFunc;
46
},
47
48
findByCode: (...args: (string | RegExp)[]) => {
49
-
return Object.entries(modules)
50
-
.filter(
51
-
([id, mod]) =>
52
-
!args.some(
53
-
(item) =>
54
-
!(item instanceof RegExp
55
-
? item.test(mod.toString())
56
-
: mod.toString().indexOf(item) !== -1)
57
-
)
58
-
)
59
.map(([id]) => {
60
//if (!(id in cache)) require(id);
61
//return cache[id];
···
64
try {
65
exports = require(id);
66
} catch (e) {
67
-
logger.error(`Error requiring module "${id}": `, e);
68
}
69
70
return {
···
73
};
74
})
75
.filter((item) => item !== null);
76
},
77
78
findByExports: (...args: string[]) => {
···
84
!(
85
exports !== undefined &&
86
exports !== window &&
87
-
(exports?.[item] ||
88
-
exports?.default?.[item] ||
89
-
exports?.Z?.[item] ||
90
-
exports?.ZP?.[item])
91
)
92
)
93
)
···
99
},
100
101
findObjectFromKey: (exports: Record<string, any>, key: string) => {
102
let subKey;
103
if (key.indexOf(".") > -1) {
104
const splitKey = key.split(".");
···
109
const obj = exports[exportKey];
110
if (obj && obj[key] !== undefined) {
111
if (subKey) {
112
-
if (obj[key][subKey]) return obj;
113
} else {
114
-
return obj;
115
}
116
}
117
}
118
-
return null;
119
},
120
121
findObjectFromValue: (exports: Record<string, any>, value: any) => {
122
for (const exportKey in exports) {
123
const obj = exports[exportKey];
124
// eslint-disable-next-line eqeqeq
125
-
if (obj == value) return obj;
126
for (const subKey in obj) {
127
// eslint-disable-next-line eqeqeq
128
if (obj && obj[subKey] == value) {
129
-
return obj;
130
}
131
}
132
}
133
-
return null;
134
},
135
136
-
findObjectFromKeyValuePair: (
137
-
exports: Record<string, any>,
138
-
key: string,
139
-
value: any
140
-
) => {
141
for (const exportKey in exports) {
142
const obj = exports[exportKey];
143
// eslint-disable-next-line eqeqeq
144
if (obj && obj[key] == value) {
145
-
return obj;
146
}
147
}
148
return null;
149
},
150
151
-
findFunctionByStrings: (
152
-
exports: Record<string, any>,
153
-
...strings: (string | RegExp)[]
154
-
) => {
155
-
return (
156
Object.entries(exports).filter(
157
([index, func]) =>
158
-
typeof func === "function" &&
159
-
!strings.some(
160
-
(query) =>
161
-
!(query instanceof RegExp
162
-
? func.toString().match(query)
163
-
: func.toString().includes(query))
164
-
)
165
-
)?.[0]?.[1] ?? null
166
-
);
167
},
168
169
-
lazyLoad: (
170
-
find: string | RegExp | (string | RegExp)[],
171
-
chunk: RegExp,
172
-
module: RegExp
173
-
) => {
174
-
const mod = Array.isArray(find)
175
-
? spacepack.findByCode(...find)
176
-
: spacepack.findByCode(find);
177
-
if (mod.length < 1) return Promise.reject("Module find failed");
178
179
const findId = mod[0].id;
180
const findCode = webpackRequire.m[findId].toString().replace(/\n/g, "");
···
184
chunkIds = [...findCode.matchAll(chunk)].map(([, id]) => id);
185
} else {
186
const match = findCode.match(chunk);
187
-
if (match)
188
-
chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id);
189
}
190
191
-
if (!chunkIds || chunkIds.length === 0)
192
return Promise.reject("Chunk ID match failed");
193
194
const moduleId = findCode.match(module)?.[1];
195
-
if (!moduleId) return Promise.reject("Module ID match failed");
196
197
-
return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() =>
198
-
webpackRequire(moduleId)
199
-
);
200
},
201
202
filterReal: (modules: WebpackModule[]) => {
···
204
}
205
};
206
207
-
if (
208
-
moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true
209
-
) {
210
window.spacepack = spacepack;
211
}
212
···
1
+
import { WebpackModule, WebpackModuleFunc, WebpackRequireType } from "@moonlight-mod/types";
2
import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack";
3
+
import { processFind, testFind } from "@moonlight-mod/core/util/patch";
4
5
const webpackRequire = require as unknown as WebpackRequireType;
6
const cache = webpackRequire.c;
···
37
"module",
38
"exports",
39
"require",
40
+
`(${funcStr}).apply(this, arguments)\n` + `//# sourceURL=Webpack-Module/${module.slice(0, 3)}/${module}`
41
) as WebpackModuleFunc;
42
},
43
44
findByCode: (...args: (string | RegExp)[]) => {
45
+
const ret = Object.entries(modules)
46
+
.filter(([id, mod]) => !args.some((item) => !testFind(mod.toString(), processFind(item))))
47
.map(([id]) => {
48
//if (!(id in cache)) require(id);
49
//return cache[id];
···
52
try {
53
exports = require(id);
54
} catch (e) {
55
+
logger.error(`findByCode: Error requiring module "${id}": `, args, e);
56
}
57
58
return {
···
61
};
62
})
63
.filter((item) => item !== null);
64
+
65
+
if (ret.length === 0) {
66
+
logger.warn("findByCode: Got zero results for", args, new Error().stack!.substring(5));
67
+
}
68
+
69
+
return ret;
70
},
71
72
findByExports: (...args: string[]) => {
···
78
!(
79
exports !== undefined &&
80
exports !== window &&
81
+
(exports?.[item] || exports?.default?.[item] || exports?.Z?.[item] || exports?.ZP?.[item])
82
)
83
)
84
)
···
90
},
91
92
findObjectFromKey: (exports: Record<string, any>, key: string) => {
93
+
let ret = null;
94
let subKey;
95
if (key.indexOf(".") > -1) {
96
const splitKey = key.split(".");
···
101
const obj = exports[exportKey];
102
if (obj && obj[key] !== undefined) {
103
if (subKey) {
104
+
if (obj[key][subKey]) {
105
+
ret = obj;
106
+
break;
107
+
}
108
} else {
109
+
ret = obj;
110
+
break;
111
}
112
}
113
}
114
+
115
+
if (ret == null) {
116
+
logger.warn("Failed to find object by key", key, "in", exports, new Error().stack!.substring(5));
117
+
}
118
+
119
+
return ret;
120
},
121
122
findObjectFromValue: (exports: Record<string, any>, value: any) => {
123
+
let ret = null;
124
for (const exportKey in exports) {
125
const obj = exports[exportKey];
126
// eslint-disable-next-line eqeqeq
127
+
if (obj == value) {
128
+
ret = obj;
129
+
break;
130
+
}
131
for (const subKey in obj) {
132
// eslint-disable-next-line eqeqeq
133
if (obj && obj[subKey] == value) {
134
+
ret = obj;
135
+
break;
136
}
137
}
138
}
139
+
140
+
if (ret == null) {
141
+
logger.warn("Failed to find object by value", value, "in", exports, new Error().stack!.substring(5));
142
+
}
143
+
144
+
return ret;
145
},
146
147
+
findObjectFromKeyValuePair: (exports: Record<string, any>, key: string, value: any) => {
148
+
let ret = null;
149
for (const exportKey in exports) {
150
const obj = exports[exportKey];
151
// eslint-disable-next-line eqeqeq
152
if (obj && obj[key] == value) {
153
+
ret = obj;
154
+
break;
155
}
156
}
157
+
158
+
if (ret == null) {
159
+
logger.warn(
160
+
"Failed to find object by key value pair",
161
+
key,
162
+
value,
163
+
"in",
164
+
exports,
165
+
new Error().stack!.substring(5)
166
+
);
167
+
}
168
+
169
return null;
170
},
171
172
+
findFunctionByStrings: (exports: Record<string, any>, ...strings: (string | RegExp)[]) => {
173
+
const ret =
174
Object.entries(exports).filter(
175
([index, func]) =>
176
+
typeof func === "function" && !strings.some((query) => !testFind(func.toString(), processFind(query)))
177
+
)?.[0]?.[1] ?? null;
178
+
179
+
if (ret == null) {
180
+
logger.warn("Failed to find function by strings", strings, "in", exports, new Error().stack!.substring(5));
181
+
}
182
+
183
+
return ret;
184
},
185
186
+
lazyLoad: (find: string | RegExp | (string | RegExp)[], chunk: RegExp, module: RegExp) => {
187
+
chunk = processFind(chunk);
188
+
module = processFind(module);
189
+
190
+
const mod = Array.isArray(find) ? spacepack.findByCode(...find) : spacepack.findByCode(find);
191
+
if (mod.length < 1) {
192
+
logger.warn("lazyLoad: Module find failed", find, chunk, module, new Error().stack!.substring(5));
193
+
return Promise.reject("Module find failed");
194
+
}
195
196
const findId = mod[0].id;
197
const findCode = webpackRequire.m[findId].toString().replace(/\n/g, "");
···
201
chunkIds = [...findCode.matchAll(chunk)].map(([, id]) => id);
202
} else {
203
const match = findCode.match(chunk);
204
+
if (match) chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id);
205
}
206
207
+
if (!chunkIds || chunkIds.length === 0) {
208
+
logger.warn("lazyLoad: Chunk ID match failed", find, chunk, module, new Error().stack!.substring(5));
209
return Promise.reject("Chunk ID match failed");
210
+
}
211
212
const moduleId = findCode.match(module)?.[1];
213
+
if (!moduleId) {
214
+
logger.warn("lazyLoad: Module ID match failed", find, chunk, module, new Error().stack!.substring(5));
215
+
return Promise.reject("Module ID match failed");
216
+
}
217
218
+
return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() => webpackRequire(moduleId));
219
},
220
221
filterReal: (modules: WebpackModule[]) => {
···
223
}
224
};
225
226
+
if (moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true) {
227
window.spacepack = spacepack;
228
}
229
+4
-1
packages/core-extensions/tsconfig.json
+4
-1
packages/core-extensions/tsconfig.json
+10
-3
packages/injector/package.json
+10
-3
packages/injector/package.json
···
1
{
2
"name": "@moonlight-mod/injector",
3
"private": true,
4
+
"engines": {
5
+
"node": ">=22",
6
+
"pnpm": ">=10",
7
+
"npm": "pnpm",
8
+
"yarn": "pnpm"
9
+
},
10
"dependencies": {
11
+
"@moonlight-mod/core": "workspace:*",
12
+
"@moonlight-mod/types": "workspace:*"
13
+
},
14
+
"engineStrict": true
15
}
+126
-119
packages/injector/src/index.ts
+126
-119
packages/injector/src/index.ts
···
6
} from "electron";
7
import Module from "node:module";
8
import { constants, MoonlightBranch } from "@moonlight-mod/types";
9
-
import { readConfig } from "@moonlight-mod/core/config";
10
import { getExtensions } from "@moonlight-mod/core/extension";
11
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
12
-
import {
13
-
loadExtensions,
14
-
loadProcessedExtensions
15
-
} from "@moonlight-mod/core/extension/loader";
16
import EventEmitter from "node:events";
17
-
import { join, resolve } from "node:path";
18
import persist from "@moonlight-mod/core/persist";
19
import createFS from "@moonlight-mod/core/fs";
20
21
const logger = new Logger("injector");
22
23
let oldPreloadPath: string | undefined;
24
let corsAllow: string[] = [];
25
let blockedUrls: RegExp[] = [];
26
-
let isMoonlightDesktop = false;
27
-
let hasOpenAsar = false;
28
-
let openAsarConfigPreload: string | undefined;
29
30
ipcMain.on(constants.ipcGetOldPreloadPath, (e) => {
31
e.returnValue = oldPreloadPath;
32
});
33
ipcMain.on(constants.ipcGetAppData, (e) => {
34
e.returnValue = app.getPath("appData");
35
});
36
-
ipcMain.on(constants.ipcGetIsMoonlightDesktop, (e) => {
37
-
e.returnValue = isMoonlightDesktop;
38
});
39
ipcMain.handle(constants.ipcMessageBox, (_, opts) => {
40
electron.dialog.showMessageBoxSync(opts);
···
44
});
45
46
const reEscapeRegExp = /[\\^$.*+?()[\]{}|]/g;
47
-
const reMatchPattern =
48
-
/^(?<scheme>\*|[a-z][a-z0-9+.-]*):\/\/(?<host>.+?)\/(?<path>.+)?$/;
49
50
const escapeRegExp = (s: string) => s.replace(reEscapeRegExp, "\\$&");
51
ipcMain.handle(constants.ipcSetBlockedList, (_, list: string[]) => {
···
76
blockedUrls = compiled;
77
});
78
79
-
function patchCsp(headers: Record<string, string[]>) {
80
-
const directives = [
81
-
"style-src",
82
-
"connect-src",
83
-
"img-src",
84
-
"font-src",
85
-
"media-src",
86
-
"worker-src",
87
-
"prefetch-src"
88
-
];
89
-
const values = ["*", "blob:", "data:", "'unsafe-inline'", "disclip:"];
90
91
const csp = "content-security-policy";
92
if (headers[csp] == null) return;
···
105
parts[directive] = values;
106
}
107
108
const stringified = Object.entries<string[]>(parts)
109
.map(([key, value]) => {
110
return `${key} ${value.join(" ")}`;
···
113
headers[csp] = [stringified];
114
}
115
116
-
function removeOpenAsarEventIfPresent(eventHandler: (...args: any[]) => void) {
117
-
const code = eventHandler.toString();
118
-
if (code.indexOf("bw.webContents.on('dom-ready'") > -1) {
119
-
electron.app.off("browser-window-created", eventHandler);
120
-
}
121
-
}
122
-
123
class BrowserWindow extends ElectronBrowserWindow {
124
constructor(opts: BrowserWindowConstructorOptions) {
125
-
oldPreloadPath = opts.webPreferences!.preload;
126
127
-
const isMainWindow =
128
-
opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1;
129
-
130
-
if (isMainWindow)
131
opts.webPreferences!.preload = require.resolve("./node-preload.js");
132
133
// Event for modifying window options
134
moonlightHost.events.emit("window-options", opts, isMainWindow);
···
138
// Event for when a window is created
139
moonlightHost.events.emit("window-created", this, isMainWindow);
140
141
this.webContents.session.webRequest.onHeadersReceived((details, cb) => {
142
if (details.responseHeaders != null) {
143
// Patch CSP so things can use externally hosted assets
144
if (details.resourceType === "mainFrame") {
145
-
patchCsp(details.responseHeaders);
146
}
147
148
// Allow plugins to bypass CORS for specific URLs
149
if (corsAllow.some((x) => details.url.startsWith(x))) {
150
-
details.responseHeaders["access-control-allow-origin"] = ["*"];
151
}
152
153
cb({ cancel: false, responseHeaders: details.responseHeaders });
154
}
155
});
156
157
-
// Allow plugins to block some URLs,
158
-
// this is needed because multiple webRequest handlers cannot be registered at once
159
this.webContents.session.webRequest.onBeforeRequest((details, cb) => {
160
-
cb({ cancel: blockedUrls.some((u) => u.test(details.url)) });
161
-
});
162
163
-
if (hasOpenAsar) {
164
-
// Remove DOM injections
165
-
// Settings can still be opened via:
166
-
// `DiscordNative.ipc.send("DISCORD_UPDATED_QUOTES","o")`
167
-
// @ts-expect-error Electron internals
168
-
const events = electron.app._events["browser-window-created"];
169
-
if (Array.isArray(events)) {
170
-
for (const event of events) {
171
-
removeOpenAsarEventIfPresent(event);
172
}
173
-
} else if (events != null) {
174
-
removeOpenAsarEventIfPresent(events);
175
-
}
176
177
-
// Config screen fails to context bridge properly
178
-
// Less than ideal, but better than disabling it everywhere
179
-
if (opts.webPreferences!.preload === openAsarConfigPreload) {
180
-
opts.webPreferences!.sandbox = false;
181
}
182
-
}
183
}
184
}
185
···
200
writable: false
201
});
202
203
-
export async function inject(asarPath: string) {
204
-
isMoonlightDesktop = asarPath === "moonlightDesktop";
205
-
global.moonlightFS = createFS();
206
207
try {
208
-
const config = await readConfig();
209
initLogger(config);
210
const extensions = await getExtensions();
211
212
// Duplicated in node-preload... oops
213
-
// eslint-disable-next-line no-inner-declarations
214
function getConfig(ext: string) {
215
const val = config.extensions[ext];
216
if (val == null || typeof val === "boolean") return undefined;
217
return val.config;
218
}
219
-
220
global.moonlightHost = {
221
asarPath,
222
-
config,
223
events: new EventEmitter(),
224
-
extensions,
225
-
processedExtensions: {
226
-
extensions: [],
227
-
dependencyGraph: new Map()
228
-
},
229
230
version: MOONLIGHT_VERSION,
231
branch: MOONLIGHT_BRANCH as MoonlightBranch,
232
233
getConfig,
234
-
getConfigOption: <T>(ext: string, name: string) => {
235
-
const config = getConfig(ext);
236
-
if (config == null) return undefined;
237
-
const option = config[name];
238
-
if (option == null) return undefined;
239
-
return option as T;
240
},
241
-
getLogger: (id: string) => {
242
return new Logger(id);
243
}
244
};
245
246
-
// Check if we're running with OpenAsar
247
-
try {
248
-
require.resolve(join(asarPath, "updater", "updater.js"));
249
-
hasOpenAsar = true;
250
-
openAsarConfigPreload = resolve(asarPath, "config", "preload.js");
251
-
// eslint-disable-next-line no-empty
252
-
} catch {}
253
-
254
-
if (hasOpenAsar) {
255
-
// Disable command line switch injection
256
-
// I personally think that the command line switches should be vetted by
257
-
// the user and not just "trust that these are sane defaults that work
258
-
// always". I'm not hating on Ducko or anything, I'm just opinionated.
259
-
// Someone can always make a command line modifier plugin, thats the point
260
-
// of having host modules.
261
-
try {
262
-
const cmdSwitchesPath = require.resolve(
263
-
join(asarPath, "cmdSwitches.js")
264
-
);
265
-
require.cache[cmdSwitchesPath] = new Module(
266
-
cmdSwitchesPath,
267
-
require.cache[require.resolve(asarPath)]
268
-
);
269
-
require.cache[cmdSwitchesPath]!.exports = () => {};
270
-
} catch (error) {
271
-
logger.error("Failed to disable OpenAsar's command line flags:", error);
272
-
}
273
-
}
274
-
275
patchElectron();
276
277
-
global.moonlightHost.processedExtensions = await loadExtensions(extensions);
278
await loadProcessedExtensions(global.moonlightHost.processedExtensions);
279
} catch (error) {
280
logger.error("Failed to inject:", error);
281
}
282
283
-
if (isMoonlightDesktop) return;
284
-
285
-
if (!hasOpenAsar && !isMoonlightDesktop) {
286
persist(asarPath);
287
}
288
289
-
// Need to do this instead of require() or it breaks require.main
290
-
// @ts-expect-error Module internals
291
-
Module._load(asarPath, Module, true);
292
}
293
294
function patchElectron() {
···
302
configurable: false
303
});
304
} else {
305
-
Object.defineProperty(
306
-
electronClone,
307
-
property,
308
-
Object.getOwnPropertyDescriptor(electron, property)!
309
-
);
310
}
311
}
312
···
6
} from "electron";
7
import Module from "node:module";
8
import { constants, MoonlightBranch } from "@moonlight-mod/types";
9
+
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
10
import { getExtensions } from "@moonlight-mod/core/extension";
11
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
12
+
import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
13
import EventEmitter from "node:events";
14
+
import path from "node:path";
15
import persist from "@moonlight-mod/core/persist";
16
import createFS from "@moonlight-mod/core/fs";
17
+
import { getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
18
+
import { getConfigPath, getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data";
19
20
const logger = new Logger("injector");
21
22
let oldPreloadPath: string | undefined;
23
let corsAllow: string[] = [];
24
let blockedUrls: RegExp[] = [];
25
+
let injectorConfig: InjectorConfig | undefined;
26
+
27
+
const scriptUrls = ["web.", "sentry."];
28
+
const blockedScripts = new Set<string>();
29
30
ipcMain.on(constants.ipcGetOldPreloadPath, (e) => {
31
e.returnValue = oldPreloadPath;
32
});
33
+
34
ipcMain.on(constants.ipcGetAppData, (e) => {
35
e.returnValue = app.getPath("appData");
36
});
37
+
ipcMain.on(constants.ipcGetInjectorConfig, (e) => {
38
+
e.returnValue = injectorConfig;
39
});
40
ipcMain.handle(constants.ipcMessageBox, (_, opts) => {
41
electron.dialog.showMessageBoxSync(opts);
···
45
});
46
47
const reEscapeRegExp = /[\\^$.*+?()[\]{}|]/g;
48
+
const reMatchPattern = /^(?<scheme>\*|[a-z][a-z0-9+.-]*):\/\/(?<host>.+?)\/(?<path>.+)?$/;
49
50
const escapeRegExp = (s: string) => s.replace(reEscapeRegExp, "\\$&");
51
ipcMain.handle(constants.ipcSetBlockedList, (_, list: string[]) => {
···
76
blockedUrls = compiled;
77
});
78
79
+
function patchCsp(headers: Record<string, string[]>, extensionCspOverrides: Record<string, string[]>) {
80
+
const directives = ["script-src", "style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"];
81
+
const values = ["*", "blob:", "data:", "'unsafe-inline'", "'unsafe-eval'", "disclip:"];
82
83
const csp = "content-security-policy";
84
if (headers[csp] == null) return;
···
97
parts[directive] = values;
98
}
99
100
+
for (const [directive, urls] of Object.entries(extensionCspOverrides)) {
101
+
parts[directive] ??= [];
102
+
parts[directive].push(...urls);
103
+
}
104
+
105
const stringified = Object.entries<string[]>(parts)
106
.map(([key, value]) => {
107
return `${key} ${value.join(" ")}`;
···
110
headers[csp] = [stringified];
111
}
112
113
class BrowserWindow extends ElectronBrowserWindow {
114
constructor(opts: BrowserWindowConstructorOptions) {
115
+
const isMainWindow = opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1;
116
117
+
if (isMainWindow) {
118
+
if (!oldPreloadPath) oldPreloadPath = opts.webPreferences!.preload;
119
opts.webPreferences!.preload = require.resolve("./node-preload.js");
120
+
}
121
122
// Event for modifying window options
123
moonlightHost.events.emit("window-options", opts, isMainWindow);
···
127
// Event for when a window is created
128
moonlightHost.events.emit("window-created", this, isMainWindow);
129
130
+
const extensionCspOverrides: Record<string, string[]> = {};
131
+
132
+
{
133
+
const extCsps = moonlightHost.processedExtensions.extensions.map((x) => x.manifest.csp ?? {});
134
+
for (const csp of extCsps) {
135
+
for (const [directive, urls] of Object.entries(csp)) {
136
+
extensionCspOverrides[directive] ??= [];
137
+
extensionCspOverrides[directive].push(...urls);
138
+
}
139
+
}
140
+
}
141
+
142
this.webContents.session.webRequest.onHeadersReceived((details, cb) => {
143
if (details.responseHeaders != null) {
144
// Patch CSP so things can use externally hosted assets
145
if (details.resourceType === "mainFrame") {
146
+
patchCsp(details.responseHeaders, extensionCspOverrides);
147
}
148
149
// Allow plugins to bypass CORS for specific URLs
150
if (corsAllow.some((x) => details.url.startsWith(x))) {
151
+
if (!details.responseHeaders) details.responseHeaders = {};
152
+
153
+
// Work around HTTP header case sensitivity by reusing the header name if it exists
154
+
// https://github.com/moonlight-mod/moonlight/issues/201
155
+
const fallback = "access-control-allow-origin";
156
+
const key = Object.keys(details.responseHeaders).find((h) => h.toLowerCase() === fallback) ?? fallback;
157
+
details.responseHeaders[key] = ["*"];
158
}
159
+
160
+
moonlightHost.events.emit("headers-received", details, isMainWindow);
161
162
cb({ cancel: false, responseHeaders: details.responseHeaders });
163
}
164
});
165
166
this.webContents.session.webRequest.onBeforeRequest((details, cb) => {
167
+
/*
168
+
In order to get moonlight loading to be truly async, we prevent Discord
169
+
from loading their scripts immediately. We block the requests, keep note
170
+
of their URLs, and then send them off to node-preload when we get all of
171
+
them. node-preload then loads node side, web side, and then recreates
172
+
the script elements to cause them to re-fetch.
173
174
+
The browser extension also does this, but in a background script (see
175
+
packages/browser/src/background.js - we should probably get this working
176
+
with esbuild someday).
177
+
*/
178
+
if (details.resourceType === "script" && isMainWindow) {
179
+
const url = new URL(details.url);
180
+
const hasUrl = scriptUrls.some((scriptUrl) => {
181
+
return (
182
+
details.url.includes(scriptUrl) &&
183
+
!url.searchParams.has("inj") &&
184
+
(url.host.endsWith("discord.com") || url.host.endsWith("discordapp.com"))
185
+
);
186
+
});
187
+
if (hasUrl) blockedScripts.add(details.url);
188
+
189
+
if (blockedScripts.size === scriptUrls.length) {
190
+
setTimeout(() => {
191
+
logger.debug("Kicking off node-preload");
192
+
this.webContents.send(constants.ipcNodePreloadKickoff, Array.from(blockedScripts));
193
+
blockedScripts.clear();
194
+
}, 0);
195
}
196
197
+
if (hasUrl) return cb({ cancel: true });
198
}
199
+
200
+
// Allow plugins to block some URLs,
201
+
// this is needed because multiple webRequest handlers cannot be registered at once
202
+
cb({ cancel: blockedUrls.some((u) => u.test(details.url)) });
203
+
});
204
}
205
}
206
···
221
writable: false
222
});
223
224
+
type InjectorConfig = { disablePersist?: boolean; disableLoad?: boolean };
225
+
export async function inject(asarPath: string, _injectorConfig?: InjectorConfig) {
226
+
injectorConfig = _injectorConfig;
227
+
228
+
global.moonlightNodeSandboxed = {
229
+
fs: createFS(),
230
+
// These aren't supposed to be used from host
231
+
addCors() {},
232
+
addBlocked() {}
233
+
};
234
235
try {
236
+
let config = await readConfig();
237
initLogger(config);
238
const extensions = await getExtensions();
239
+
const processedExtensions = await loadExtensions(extensions);
240
+
const moonlightDir = await getMoonlightDir();
241
+
const extensionsPath = await getExtensionsPath();
242
243
// Duplicated in node-preload... oops
244
function getConfig(ext: string) {
245
const val = config.extensions[ext];
246
if (val == null || typeof val === "boolean") return undefined;
247
return val.config;
248
}
249
global.moonlightHost = {
250
+
get config() {
251
+
return config;
252
+
},
253
+
extensions,
254
+
processedExtensions,
255
asarPath,
256
events: new EventEmitter(),
257
258
version: MOONLIGHT_VERSION,
259
branch: MOONLIGHT_BRANCH as MoonlightBranch,
260
261
getConfig,
262
+
getConfigPath,
263
+
getConfigOption(ext, name) {
264
+
const manifest = getManifest(extensions, ext);
265
+
return getConfigOption(ext, name, config, manifest?.settings);
266
},
267
+
setConfigOption(ext, name, value) {
268
+
setConfigOption(config, ext, name, value);
269
+
this.writeConfig(config);
270
+
},
271
+
async writeConfig(newConfig) {
272
+
await writeConfig(newConfig);
273
+
config = newConfig;
274
+
},
275
+
276
+
getLogger(id) {
277
return new Logger(id);
278
+
},
279
+
getMoonlightDir() {
280
+
return moonlightDir;
281
+
},
282
+
getExtensionDir: (ext: string) => {
283
+
return path.join(extensionsPath, ext);
284
}
285
};
286
287
patchElectron();
288
289
await loadProcessedExtensions(global.moonlightHost.processedExtensions);
290
} catch (error) {
291
logger.error("Failed to inject:", error);
292
}
293
294
+
if (injectorConfig?.disablePersist !== true) {
295
persist(asarPath);
296
}
297
298
+
if (injectorConfig?.disableLoad !== true) {
299
+
// Need to do this instead of require() or it breaks require.main
300
+
// @ts-expect-error Module internals
301
+
Module._load(asarPath, Module, true);
302
+
}
303
}
304
305
function patchElectron() {
···
313
configurable: false
314
});
315
} else {
316
+
Object.defineProperty(electronClone, property, Object.getOwnPropertyDescriptor(electron, property)!);
317
}
318
}
319
+8
-1
packages/node-preload/package.json
+8
-1
packages/node-preload/package.json
···
1
{
2
"name": "@moonlight-mod/node-preload",
3
"private": true,
4
+
"engines": {
5
+
"node": ">=22",
6
+
"pnpm": ">=10",
7
+
"npm": "pnpm",
8
+
"yarn": "pnpm"
9
+
},
10
"dependencies": {
11
"@moonlight-mod/core": "workspace:*",
12
"@moonlight-mod/types": "workspace:*"
13
+
},
14
+
"engineStrict": true
15
}
+121
-44
packages/node-preload/src/index.ts
+121
-44
packages/node-preload/src/index.ts
···
5
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
6
import { constants, MoonlightBranch } from "@moonlight-mod/types";
7
import { getExtensions } from "@moonlight-mod/core/extension";
8
-
import {
9
-
getExtensionsPath,
10
-
getMoonlightDir
11
-
} from "@moonlight-mod/core/util/data";
12
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
13
-
import {
14
-
loadExtensions,
15
-
loadProcessedExtensions
16
-
} from "@moonlight-mod/core/extension/loader";
17
import createFS from "@moonlight-mod/core/fs";
18
19
async function injectGlobals() {
20
-
global.moonlightFS = createFS();
21
22
-
const config = await readConfig();
23
initLogger(config);
24
const extensions = await getExtensions();
25
const processedExtensions = await loadExtensions(extensions);
26
const moonlightDir = await getMoonlightDir();
27
const extensionsPath = await getExtensionsPath();
28
29
-
function getConfig(ext: string) {
30
-
const val = config.extensions[ext];
31
-
if (val == null || typeof val === "boolean") return undefined;
32
-
return val.config;
33
-
}
34
-
35
global.moonlightNode = {
36
-
config,
37
extensions,
38
processedExtensions,
39
nativesCache: {},
40
isBrowser: false,
41
42
version: MOONLIGHT_VERSION,
43
branch: MOONLIGHT_BRANCH as MoonlightBranch,
44
45
-
getConfig,
46
-
getConfigOption: <T>(ext: string, name: string) => {
47
-
const config = getConfig(ext);
48
-
if (config == null) return undefined;
49
-
const option = config[name];
50
-
if (option == null) return undefined;
51
-
return option as T;
52
},
53
getNatives: (ext: string) => global.moonlightNode.nativesCache[ext],
54
getLogger: (id: string) => {
55
return new Logger(id);
56
},
57
-
58
getMoonlightDir() {
59
return moonlightDir;
60
},
61
getExtensionDir: (ext: string) => {
62
return path.join(extensionsPath, ext);
63
-
},
64
-
writeConfig
65
};
66
67
await loadProcessedExtensions(processedExtensions);
68
contextBridge.exposeInMainWorld("moonlightNode", moonlightNode);
69
70
-
const extCors = moonlightNode.processedExtensions.extensions.flatMap(
71
-
(x) => x.manifest.cors ?? []
72
-
);
73
74
for (const repo of moonlightNode.config.repositories) {
75
const url = new URL(repo);
76
url.pathname = "/";
77
-
extCors.push(url.toString());
78
}
79
80
-
ipcRenderer.invoke(constants.ipcSetCorsList, extCors);
81
82
-
const extBlocked = moonlightNode.processedExtensions.extensions.flatMap(
83
-
(e) => e.manifest.blocked ?? []
84
-
);
85
-
ipcRenderer.invoke(constants.ipcSetBlockedList, extBlocked);
86
}
87
88
async function loadPreload() {
89
const webPreloadPath = path.join(__dirname, "web-preload.js");
90
const webPreload = fs.readFileSync(webPreloadPath, "utf8");
91
await webFrame.executeJavaScript(webPreload);
92
}
93
94
-
async function init(oldPreloadPath: string) {
95
try {
96
await injectGlobals();
97
await loadPreload();
···
102
message: message
103
});
104
}
105
106
-
// Let Discord start even if we fail
107
-
if (oldPreloadPath) require(oldPreloadPath);
108
-
}
109
110
-
const oldPreloadPath: string = ipcRenderer.sendSync(
111
-
constants.ipcGetOldPreloadPath
112
-
);
113
-
init(oldPreloadPath);
···
5
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
6
import { constants, MoonlightBranch } from "@moonlight-mod/types";
7
import { getExtensions } from "@moonlight-mod/core/extension";
8
+
import { getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data";
9
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
10
+
import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
11
import createFS from "@moonlight-mod/core/fs";
12
+
import { registerCors, registerBlocked, getDynamicCors } from "@moonlight-mod/core/cors";
13
+
import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
14
+
import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event";
15
+
import { createEventEmitter } from "@moonlight-mod/core/util/event";
16
+
17
+
let initialized = false;
18
+
let logger: Logger;
19
+
20
+
function setCors() {
21
+
const data = getDynamicCors();
22
+
ipcRenderer.invoke(constants.ipcSetCorsList, data.cors);
23
+
ipcRenderer.invoke(constants.ipcSetBlockedList, data.blocked);
24
+
}
25
26
async function injectGlobals() {
27
+
global.moonlightNodeSandboxed = {
28
+
fs: createFS(),
29
+
addCors(url) {
30
+
registerCors(url);
31
+
if (initialized) setCors();
32
+
},
33
+
addBlocked(url) {
34
+
registerBlocked(url);
35
+
if (initialized) setCors();
36
+
}
37
+
};
38
39
+
let config = await readConfig();
40
initLogger(config);
41
+
logger = new Logger("node-preload");
42
+
43
const extensions = await getExtensions();
44
const processedExtensions = await loadExtensions(extensions);
45
const moonlightDir = await getMoonlightDir();
46
const extensionsPath = await getExtensionsPath();
47
48
global.moonlightNode = {
49
+
get config() {
50
+
return config;
51
+
},
52
extensions,
53
processedExtensions,
54
nativesCache: {},
55
isBrowser: false,
56
+
events: createEventEmitter<NodeEventType, NodeEventPayloads>(),
57
58
version: MOONLIGHT_VERSION,
59
branch: MOONLIGHT_BRANCH as MoonlightBranch,
60
61
+
getConfig(ext) {
62
+
return getConfig(ext, config);
63
+
},
64
+
getConfigOption(ext, name) {
65
+
const manifest = getManifest(extensions, ext);
66
+
return getConfigOption(ext, name, config, manifest?.settings);
67
+
},
68
+
async setConfigOption(ext, name, value) {
69
+
setConfigOption(config, ext, name, value);
70
+
await this.writeConfig(config);
71
+
},
72
+
async writeConfig(newConfig) {
73
+
await writeConfig(newConfig);
74
+
config = newConfig;
75
+
this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig);
76
},
77
+
78
getNatives: (ext: string) => global.moonlightNode.nativesCache[ext],
79
getLogger: (id: string) => {
80
return new Logger(id);
81
},
82
getMoonlightDir() {
83
return moonlightDir;
84
},
85
getExtensionDir: (ext: string) => {
86
return path.join(extensionsPath, ext);
87
+
}
88
};
89
90
await loadProcessedExtensions(processedExtensions);
91
contextBridge.exposeInMainWorld("moonlightNode", moonlightNode);
92
93
+
const extCors = moonlightNode.processedExtensions.extensions.flatMap((x) => x.manifest.cors ?? []);
94
+
for (const cors of extCors) {
95
+
registerCors(cors);
96
+
}
97
98
for (const repo of moonlightNode.config.repositories) {
99
const url = new URL(repo);
100
url.pathname = "/";
101
+
registerCors(url.toString());
102
+
}
103
+
104
+
const extBlocked = moonlightNode.processedExtensions.extensions.flatMap((e) => e.manifest.blocked ?? []);
105
+
for (const blocked of extBlocked) {
106
+
registerBlocked(blocked);
107
}
108
109
+
setCors();
110
111
+
initialized = true;
112
}
113
114
async function loadPreload() {
115
const webPreloadPath = path.join(__dirname, "web-preload.js");
116
const webPreload = fs.readFileSync(webPreloadPath, "utf8");
117
await webFrame.executeJavaScript(webPreload);
118
+
119
+
const func = await webFrame.executeJavaScript("async () => { await window._moonlightWebLoad(); }");
120
+
await func();
121
}
122
123
+
async function init() {
124
try {
125
await injectGlobals();
126
await loadPreload();
···
131
message: message
132
});
133
}
134
+
}
135
136
+
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
137
+
const isOverlay = window.location.href.indexOf("discord_overlay") > -1;
138
139
+
if (isOverlay) {
140
+
// The overlay has an inline script tag to call to DiscordNative, so we'll
141
+
// just load it immediately. Somehow moonlight still loads in this env, I
142
+
// have no idea why - so I suspect it's just forwarding render calls or
143
+
// something from the original process
144
+
require(oldPreloadPath);
145
+
} else {
146
+
ipcRenderer.on(constants.ipcNodePreloadKickoff, (_, blockedScripts: string[]) => {
147
+
(async () => {
148
+
try {
149
+
await init();
150
+
logger.debug("Blocked scripts:", blockedScripts);
151
+
152
+
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
153
+
logger.debug("Old preload path:", oldPreloadPath);
154
+
if (oldPreloadPath) require(oldPreloadPath);
155
+
156
+
// Do this to get global.DiscordNative assigned
157
+
// @ts-expect-error Lying to discord_desktop_core
158
+
process.emit("loaded");
159
+
160
+
function replayScripts() {
161
+
const scripts = [...document.querySelectorAll("script")].filter(
162
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
163
+
);
164
+
165
+
blockedScripts.reverse();
166
+
for (const url of blockedScripts) {
167
+
if (url.includes("/sentry.")) continue;
168
+
169
+
const script = scripts.find((script) => url.includes(script.src))!;
170
+
const newScript = document.createElement("script");
171
+
for (const attr of script.attributes) {
172
+
if (attr.name === "src") attr.value += "?inj";
173
+
newScript.setAttribute(attr.name, attr.value);
174
+
}
175
+
script.remove();
176
+
document.documentElement.appendChild(newScript);
177
+
}
178
+
}
179
+
180
+
if (document.readyState === "complete") {
181
+
replayScripts();
182
+
} else {
183
+
window.addEventListener("load", replayScripts);
184
+
}
185
+
} catch (e) {
186
+
logger.error("Error restoring original scripts:", e);
187
+
}
188
+
})();
189
+
});
190
+
}
+4
-1
packages/node-preload/tsconfig.json
+4
-1
packages/node-preload/tsconfig.json
+14
-7
packages/types/package.json
+14
-7
packages/types/package.json
···
1
{
2
"name": "@moonlight-mod/types",
3
-
"version": "1.2.0",
4
-
"main": "./src/index.ts",
5
-
"types": "./src/index.ts",
6
"exports": {
7
".": "./src/index.ts",
8
"./import": "./src/import.d.ts",
9
"./*": "./src/*.ts"
10
},
11
"dependencies": {
12
-
"@moonlight-mod/lunast": "^1.0.0",
13
-
"@moonlight-mod/mappings": "^1.0.2",
14
-
"@moonlight-mod/moonmap": "^1.0.2",
15
"@types/react": "^18.3.10",
16
-
"csstype": "^3.1.2",
17
"standalone-electron-types": "^1.0.0"
18
}
19
}
···
1
{
2
"name": "@moonlight-mod/types",
3
+
"version": "1.3.17",
4
"exports": {
5
".": "./src/index.ts",
6
"./import": "./src/import.d.ts",
7
"./*": "./src/*.ts"
8
},
9
+
"main": "./src/index.ts",
10
+
"types": "./src/index.ts",
11
+
"engineStrict": false,
12
+
"engines": {
13
+
"node": ">=22",
14
+
"pnpm": ">=10",
15
+
"npm": "pnpm",
16
+
"yarn": "pnpm"
17
+
},
18
"dependencies": {
19
+
"@moonlight-mod/lunast": "^1.0.1",
20
+
"@moonlight-mod/mappings": "^1.1.25",
21
+
"@moonlight-mod/moonmap": "^1.0.5",
22
"@types/react": "^18.3.10",
23
+
"csstype": "^3.1.3",
24
"standalone-electron-types": "^1.0.0"
25
}
26
}
+49
-3
packages/types/src/config.ts
+49
-3
packages/types/src/config.ts
···
6
patchAll?: boolean;
7
};
8
9
-
export type ConfigExtensions =
10
-
| { [key: string]: boolean }
11
-
| { [key: string]: ConfigExtension };
12
13
export type ConfigExtension = {
14
enabled: boolean;
···
35
};
36
37
export type BooleanSettingType = {
38
type: ExtensionSettingType.Boolean;
39
default?: boolean;
40
};
41
42
export type NumberSettingType = {
43
type: ExtensionSettingType.Number;
44
default?: number;
45
min?: number;
···
47
};
48
49
export type StringSettingType = {
50
type: ExtensionSettingType.String;
51
default?: string;
52
};
53
54
export type MultilineTextInputSettingType = {
55
type: ExtensionSettingType.MultilineString;
56
default?: string;
57
};
58
59
export type SelectSettingType = {
60
type: ExtensionSettingType.Select;
61
options: SelectOption[];
62
default?: string;
63
};
64
65
export type MultiSelectSettingType = {
66
type: ExtensionSettingType.MultiSelect;
67
options: string[];
68
default?: string[];
69
};
70
71
export type ListSettingType = {
72
type: ExtensionSettingType.List;
73
default?: string[];
74
};
75
76
export type DictionarySettingType = {
77
type: ExtensionSettingType.Dictionary;
78
default?: Record<string, string>;
79
};
80
81
export type CustomSettingType = {
82
type: ExtensionSettingType.Custom;
83
default?: any;
84
};
85
86
export type ExtensionSettingsManifest = {
87
displayName?: string;
88
description?: string;
89
} & (
90
| BooleanSettingType
91
| NumberSettingType
···
6
patchAll?: boolean;
7
};
8
9
+
export type ConfigExtensions = { [key: string]: boolean } | { [key: string]: ConfigExtension };
10
11
export type ConfigExtension = {
12
enabled: boolean;
···
33
};
34
35
export type BooleanSettingType = {
36
+
/**
37
+
* Displays as a simple switch.
38
+
*/
39
type: ExtensionSettingType.Boolean;
40
default?: boolean;
41
};
42
43
export type NumberSettingType = {
44
+
/**
45
+
* Displays as a simple slider.
46
+
*/
47
type: ExtensionSettingType.Number;
48
default?: number;
49
min?: number;
···
51
};
52
53
export type StringSettingType = {
54
+
/**
55
+
* Displays as a single line string input.
56
+
*/
57
type: ExtensionSettingType.String;
58
default?: string;
59
};
60
61
export type MultilineTextInputSettingType = {
62
+
/**
63
+
* Displays as a multiple line string input.
64
+
*/
65
type: ExtensionSettingType.MultilineString;
66
default?: string;
67
};
68
69
export type SelectSettingType = {
70
+
/**
71
+
* A dropdown to pick between one of many values.
72
+
*/
73
type: ExtensionSettingType.Select;
74
options: SelectOption[];
75
default?: string;
76
};
77
78
export type MultiSelectSettingType = {
79
+
/**
80
+
* A dropdown to pick multiple values.
81
+
*/
82
type: ExtensionSettingType.MultiSelect;
83
options: string[];
84
default?: string[];
85
};
86
87
export type ListSettingType = {
88
+
/**
89
+
* A list of strings that the user can add or remove from.
90
+
*/
91
type: ExtensionSettingType.List;
92
default?: string[];
93
};
94
95
export type DictionarySettingType = {
96
+
/**
97
+
* A dictionary (key-value pair) that the user can add or remove from.
98
+
*/
99
type: ExtensionSettingType.Dictionary;
100
default?: Record<string, string>;
101
};
102
103
export type CustomSettingType = {
104
+
/**
105
+
* A custom component.
106
+
* You can use the registerConfigComponent function in the Moonbase API to register a React component to render here.
107
+
*/
108
type: ExtensionSettingType.Custom;
109
default?: any;
110
};
111
112
+
export enum ExtensionSettingsAdvice {
113
+
None = "none",
114
+
Reload = "reload",
115
+
Restart = "restart"
116
+
}
117
+
118
export type ExtensionSettingsManifest = {
119
+
/**
120
+
* A human friendly name for the setting.
121
+
*/
122
displayName?: string;
123
+
124
+
/**
125
+
* A longer description for the setting.
126
+
* Markdown is not supported.
127
+
*/
128
description?: string;
129
+
130
+
/**
131
+
* The "advice" to give upon changing this setting.
132
+
* Can be configured to reload the client, restart the client, or do nothing.
133
+
*/
134
+
advice?: ExtensionSettingsAdvice;
135
} & (
136
| BooleanSettingType
137
| NumberSettingType
+6
-3
packages/types/src/constants.ts
+6
-3
packages/types/src/constants.ts
···
4
export const repoUrlFile = ".moonlight-repo-url";
5
export const installedVersionFile = ".moonlight-installed-version";
6
7
export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath";
8
export const ipcGetAppData = "_moonlight_getAppData";
9
-
export const ipcGetIsMoonlightDesktop = "_moonlight_getIsMoonlightDesktop";
10
export const ipcMessageBox = "_moonlight_messageBox";
11
export const ipcSetCorsList = "_moonlight_setCorsList";
12
export const ipcSetBlockedList = "_moonlight_setBlockedList";
13
14
export const apiLevel = 2;
15
16
-
export const mainRepo =
17
-
"https://moonlight-mod.github.io/extensions-dist/repo.json";
···
4
export const repoUrlFile = ".moonlight-repo-url";
5
export const installedVersionFile = ".moonlight-installed-version";
6
7
+
export const ipcNodePreloadKickoff = "_moonlight_nodePreloadKickoff";
8
export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath";
9
+
10
export const ipcGetAppData = "_moonlight_getAppData";
11
+
export const ipcGetInjectorConfig = "_moonlight_getInjectorConfig";
12
export const ipcMessageBox = "_moonlight_messageBox";
13
export const ipcSetCorsList = "_moonlight_setCorsList";
14
export const ipcSetBlockedList = "_moonlight_setBlockedList";
15
16
export const apiLevel = 2;
17
18
+
export const mainRepo = "https://moonlight-mod.github.io/extensions-dist/repo.json";
19
+
// If you're updating this, update `defaultConfig` in core as well
20
+
export const builtinExtensions = ["moonbase", "disableSentry", "noTrack", "noHideToken"];
+17
-20
packages/types/src/core/event.ts
+17
-20
packages/types/src/core/event.ts
···
1
import { WebpackModuleFunc, WebpackRequireType } from "../discord";
2
3
-
export interface MoonlightEventEmitter<
4
-
EventId extends string = string,
5
-
EventData = Record<EventId, any>
6
-
> {
7
-
dispatchEvent: <Id extends keyof EventData>(
8
-
id: Id,
9
-
data: EventData[Id]
10
-
) => void;
11
-
addEventListener: <Id extends keyof EventData>(
12
-
id: Id,
13
-
cb: (data: EventData[Id]) => void
14
-
) => void;
15
-
removeEventListener: <Id extends keyof EventData>(
16
-
id: Id,
17
-
cb: (data: EventData[Id]) => void
18
-
) => void;
19
}
20
21
-
export enum EventType {
22
ChunkLoad = "chunkLoad",
23
ExtensionLoad = "extensionLoad"
24
}
25
26
-
export type EventPayloads = {
27
-
[EventType.ChunkLoad]: {
28
chunkId?: number[];
29
modules: { [id: string]: WebpackModuleFunc };
30
require?: (require: WebpackRequireType) => any;
31
};
32
-
[EventType.ExtensionLoad]: string;
33
};
···
1
+
import { Config } from "../config";
2
import { WebpackModuleFunc, WebpackRequireType } from "../discord";
3
4
+
export interface MoonlightEventEmitter<EventId extends string = string, EventData = Record<EventId, any>> {
5
+
dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => void;
6
+
addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => void;
7
+
removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => void;
8
}
9
10
+
export enum WebEventType {
11
ChunkLoad = "chunkLoad",
12
ExtensionLoad = "extensionLoad"
13
}
14
15
+
export type WebEventPayloads = {
16
+
[WebEventType.ChunkLoad]: {
17
chunkId?: number[];
18
modules: { [id: string]: WebpackModuleFunc };
19
require?: (require: WebpackRequireType) => any;
20
};
21
+
[WebEventType.ExtensionLoad]: string;
22
+
};
23
+
24
+
export enum NodeEventType {
25
+
ConfigSaved = "configSaved"
26
+
}
27
+
28
+
export type NodeEventPayloads = {
29
+
[NodeEventType.ConfigSaved]: Config;
30
};
+13
packages/types/src/coreExtensions/appPanels.ts
+13
packages/types/src/coreExtensions/appPanels.ts
···
···
1
+
export type AppPanels = {
2
+
/**
3
+
* Registers a new panel to be displayed around the user/voice controls.
4
+
* @param section A unique name for your section
5
+
* @param element A React component
6
+
*/
7
+
addPanel: (section: string, element: React.FC<any>) => void;
8
+
9
+
/**
10
+
* @private
11
+
*/
12
+
getPanels: (el: React.FC<any>) => React.ReactNode;
13
+
};
+204
packages/types/src/coreExtensions/commands.ts
+204
packages/types/src/coreExtensions/commands.ts
···
···
1
+
export const APPLICATION_ID = "-3";
2
+
3
+
export enum CommandType {
4
+
CHAT = 1,
5
+
MESSAGE = 3,
6
+
PRIMARY_ENTRY_POINT = 4,
7
+
USER = 2
8
+
}
9
+
10
+
export enum InputType {
11
+
BOT = 3,
12
+
BUILT_IN = 0,
13
+
BUILT_IN_INTEGRATION = 2,
14
+
BUILT_IN_TEXT = 1,
15
+
PLACEHOLDER = 4
16
+
}
17
+
18
+
export enum OptionType {
19
+
SUB_COMMAND = 1,
20
+
SUB_COMMAND_GROUP = 2,
21
+
STRING = 3,
22
+
INTEGER = 4,
23
+
BOOLEAN = 5,
24
+
USER = 6,
25
+
CHANNEL = 7,
26
+
ROLE = 8,
27
+
MENTIONABLE = 9,
28
+
NUMBER = 10,
29
+
ATTACHMENT = 11
30
+
}
31
+
32
+
export enum ChannelType {
33
+
GUILD_TEXT = 0,
34
+
DM = 1,
35
+
GUILD_VOICE = 2,
36
+
GROUP_DM = 3,
37
+
GUILD_CATEGORY = 4,
38
+
GUILD_ANNOUNCEMENT = 5,
39
+
GUILD_STORE = 6,
40
+
ANNOUNCEMENT_THREAD = 10,
41
+
PUBLIC_THREAD = 11,
42
+
PRIVATE_THREAD = 12,
43
+
GUILD_STAGE_VOICE = 13,
44
+
GUILD_DIRECTORY = 14,
45
+
GUILD_FORUM = 15,
46
+
GUILD_MEDIA = 16,
47
+
LOBBY = 17,
48
+
DM_SDK = 18
49
+
}
50
+
51
+
export type RegisteredCommandOption = MoonlightCommandOption & {
52
+
displayName: string;
53
+
displayDescription: string;
54
+
};
55
+
56
+
export type CommandOptionChoice<T> = {
57
+
name: string;
58
+
value: T;
59
+
};
60
+
61
+
type CommandOptionBase<T> = {
62
+
type: T;
63
+
name: string;
64
+
description: string;
65
+
required?: T extends OptionType.SUB_COMMAND
66
+
? never
67
+
: T extends OptionType.SUB_COMMAND_GROUP
68
+
? never
69
+
: boolean | undefined;
70
+
choices?: T extends OptionType.STRING
71
+
? CommandOptionChoice<string>[]
72
+
: T extends OptionType.INTEGER
73
+
? CommandOptionChoice<number>[]
74
+
: T extends OptionType.NUMBER
75
+
? CommandOptionChoice<number>[]
76
+
: never;
77
+
options?: T extends OptionType.SUB_COMMAND
78
+
? MoonlightCommandOption[]
79
+
: T extends OptionType.SUB_COMMAND_GROUP
80
+
? MoonlightCommandOption[]
81
+
: never;
82
+
channelTypes?: T extends OptionType.CHANNEL ? ChannelType[] : never;
83
+
minValue?: T extends OptionType.INTEGER ? number : T extends OptionType.NUMBER ? number : never;
84
+
maxValue?: T extends OptionType.INTEGER ? number : T extends OptionType.NUMBER ? number : never;
85
+
minLength?: T extends OptionType.STRING ? number : never;
86
+
maxLength?: T extends OptionType.STRING ? number : never;
87
+
};
88
+
89
+
// This is bad lol
90
+
export type MoonlightCommandOption =
91
+
| CommandOptionBase<OptionType.SUB_COMMAND>
92
+
| CommandOptionBase<OptionType.SUB_COMMAND_GROUP>
93
+
| CommandOptionBase<OptionType.STRING>
94
+
| CommandOptionBase<OptionType.INTEGER>
95
+
| CommandOptionBase<OptionType.BOOLEAN>
96
+
| CommandOptionBase<OptionType.USER>
97
+
| CommandOptionBase<OptionType.CHANNEL>
98
+
| CommandOptionBase<OptionType.ROLE>
99
+
| CommandOptionBase<OptionType.MENTIONABLE>
100
+
| CommandOptionBase<OptionType.NUMBER>
101
+
| CommandOptionBase<OptionType.ATTACHMENT>;
102
+
103
+
// TODO: types
104
+
export type CommandPredicateState = {
105
+
channel: any;
106
+
guild: any;
107
+
};
108
+
109
+
export type RegisteredCommand = {
110
+
id: string;
111
+
untranslatedName: string;
112
+
displayName: string;
113
+
type: CommandType;
114
+
inputType: InputType;
115
+
applicationId: string; // set to -3!
116
+
untranslatedDescription: string;
117
+
displayDescription: string;
118
+
options?: RegisteredCommandOption[];
119
+
predicate?: (state: CommandPredicateState) => boolean;
120
+
execute: (options: CommandOption[]) => void;
121
+
};
122
+
123
+
export type MoonlightCommand = {
124
+
id: string;
125
+
description: string;
126
+
127
+
/**
128
+
* You likely want CHAT
129
+
*/
130
+
type: CommandType;
131
+
132
+
/**
133
+
* You likely want BUILT_IN (or BUILT_IN_TEXT if usable with replies)
134
+
*/
135
+
inputType: InputType;
136
+
options?: MoonlightCommandOption[];
137
+
predicate?: (state: CommandPredicateState) => boolean;
138
+
execute: (options: CommandOption[]) => void;
139
+
};
140
+
141
+
export type CommandOption = {
142
+
name: string;
143
+
} & ( // TODO: more of these
144
+
| {
145
+
type: Exclude<OptionType, OptionType.STRING>;
146
+
value: any;
147
+
}
148
+
| {
149
+
type: OptionType.STRING;
150
+
value: string;
151
+
}
152
+
| {
153
+
type: OptionType.NUMBER | OptionType.INTEGER;
154
+
value: number;
155
+
}
156
+
| {
157
+
type: OptionType.BOOLEAN;
158
+
value: boolean;
159
+
}
160
+
| {
161
+
type: OptionType.SUB_COMMAND | OptionType.SUB_COMMAND_GROUP;
162
+
options: CommandOption[];
163
+
}
164
+
);
165
+
166
+
export type AnyScopeRegex = RegExp["exec"] & {
167
+
regex: RegExp;
168
+
};
169
+
170
+
export type Commands = {
171
+
/**
172
+
* Register a command in the internal slash command system
173
+
*/
174
+
registerCommand: (command: MoonlightCommand) => void;
175
+
176
+
/**
177
+
* Register a legacy command that works via regex
178
+
*/
179
+
registerLegacyCommand: (id: string, command: LegacyCommand) => void;
180
+
181
+
/**
182
+
* Creates a regular expression that legacy commands can understand
183
+
*/
184
+
anyScopeRegex: (regex: RegExp) => AnyScopeRegex;
185
+
186
+
/**
187
+
* @private
188
+
*/
189
+
_getCommands: () => RegisteredCommand[];
190
+
};
191
+
192
+
export type LegacyContext = {
193
+
channel: any;
194
+
isEdit: boolean;
195
+
};
196
+
197
+
export type LegacyReturn = {
198
+
content: string;
199
+
};
200
+
201
+
export type LegacyCommand = {
202
+
match?: RegExp | { regex: RegExp } | AnyScopeRegex;
203
+
action: (content: string, context: LegacyContext) => LegacyReturn;
204
+
};
+33
packages/types/src/coreExtensions/common.ts
+33
packages/types/src/coreExtensions/common.ts
···
···
1
+
import type { IconProps, IconSize } from "@moonlight-mod/mappings/discord/components/common/index";
2
+
3
+
export type ErrorBoundaryProps = React.PropsWithChildren<{
4
+
noop?: boolean;
5
+
fallback?: React.FC<any>;
6
+
message?: string;
7
+
}>;
8
+
9
+
export type ErrorBoundaryState = {
10
+
errored: boolean;
11
+
error?: Error;
12
+
componentStack?: string;
13
+
};
14
+
15
+
export type ErrorBoundary = React.ComponentClass<ErrorBoundaryProps, ErrorBoundaryState>;
16
+
17
+
export type ParsedIconProps = {
18
+
width: number;
19
+
height: number;
20
+
fill: string;
21
+
className: string;
22
+
};
23
+
24
+
export interface Icons {
25
+
/**
26
+
* Parse icon props into their actual width/height.
27
+
* @param props The icon props
28
+
*/
29
+
parseProps(props?: IconProps): ParsedIconProps;
30
+
}
31
+
32
+
// Re-export so extension developers don't need to depend on mappings
33
+
export type { IconProps, IconSize };
+162
packages/types/src/coreExtensions/componentEditor.ts
+162
packages/types/src/coreExtensions/componentEditor.ts
···
···
1
+
type Patcher<T> = (elements: React.ReactNode[], props: T) => React.ReactNode[];
2
+
3
+
//#region DM List
4
+
export type DMListAnchors =
5
+
| "content"
6
+
| "favorite-server-indicator"
7
+
| "ignored-indicator"
8
+
| "blocked-indicator"
9
+
| "close-button"
10
+
| undefined;
11
+
export type DMListDecoratorAnchors = "system-tag" | undefined;
12
+
13
+
export enum DMListAnchorIndicies {
14
+
content = 0,
15
+
"favorite-server-indicator",
16
+
"ignored-indicator",
17
+
"blocked-indicator",
18
+
"close-button"
19
+
}
20
+
export enum DMListDecoratorAnchorIndicies {
21
+
"system-tag" = 0
22
+
}
23
+
24
+
export type DMListItem = {
25
+
component: React.FC<any>;
26
+
anchor: DMListAnchors;
27
+
before: boolean;
28
+
};
29
+
export type DMListDecorator = {
30
+
component: React.FC<any>;
31
+
anchor: DMListDecoratorAnchors;
32
+
before: boolean;
33
+
};
34
+
35
+
export type DMList = {
36
+
addItem: (id: string, component: React.FC<any>, anchor?: DMListAnchors, before?: boolean) => void;
37
+
addDecorator: (id: string, component: React.FC<any>, anchor?: DMListDecoratorAnchors, before?: boolean) => void;
38
+
//TODO: fix props type
39
+
/**
40
+
* @private
41
+
*/
42
+
_patchItems: Patcher<any>;
43
+
/**
44
+
* @private
45
+
*/
46
+
_patchDecorators: Patcher<any>;
47
+
};
48
+
//#endregion
49
+
50
+
//#region Member List
51
+
export type MemberListDecoratorAnchors = "bot-tag" | "owner-crown" | "boost-icon" | undefined;
52
+
53
+
export enum MemberListDecoratorAnchorIndicies {
54
+
"bot-tag" = 0,
55
+
"owner-crown",
56
+
"boost-icon"
57
+
}
58
+
59
+
export type MemberListDecorator = {
60
+
component: React.FC<any>;
61
+
anchor: MemberListDecoratorAnchors;
62
+
before: boolean;
63
+
};
64
+
65
+
export type MemberList = {
66
+
addItem: (id: string, component: React.FC<any>) => void;
67
+
addDecorator: (id: string, component: React.FC<any>, anchor?: MemberListDecoratorAnchors, before?: boolean) => void;
68
+
//TODO: fix props type
69
+
/**
70
+
* @private
71
+
*/
72
+
_patchItems: Patcher<any>;
73
+
/**
74
+
* @private
75
+
*/
76
+
_patchDecorators: Patcher<any>;
77
+
};
78
+
//#endregion
79
+
80
+
//#region Messages
81
+
export type MessageUsernameAnchors = "communication-disabled" | "username" | undefined;
82
+
export type MessageUsernameBadgeAnchors =
83
+
| "nitro-author"
84
+
| "role-icon"
85
+
| "new-member"
86
+
| "leaderboard-champion"
87
+
| "connections"
88
+
| undefined;
89
+
export type MessageBadgeAnchors = "silent" | "potion" | undefined;
90
+
91
+
export type MessageUsername = {
92
+
component: React.FC<any>;
93
+
anchor: MessageUsernameAnchors;
94
+
before: boolean;
95
+
};
96
+
export type MessageUsernameBadge = {
97
+
component: React.FC<any>;
98
+
anchor: MessageUsernameBadgeAnchors;
99
+
before: boolean;
100
+
};
101
+
export type MessageBadge = {
102
+
component: React.FC<any>;
103
+
anchor: MessageBadgeAnchors;
104
+
before: boolean;
105
+
};
106
+
107
+
export enum MessageUsernameIndicies {
108
+
"communication-disabled" = 0,
109
+
username
110
+
}
111
+
export enum MessageUsernameBadgeIndicies {
112
+
"nitro-author" = 0,
113
+
"role-icon",
114
+
"new-member",
115
+
"leaderboard-champion",
116
+
connections
117
+
}
118
+
export enum MessageBadgeIndicies {
119
+
silent = 0,
120
+
potion
121
+
}
122
+
123
+
export type Messages = {
124
+
/**
125
+
* Adds a component to the username of a message
126
+
*/
127
+
addToUsername: (id: string, component: React.FC<any>, anchor?: MessageUsernameAnchors, before?: boolean) => void;
128
+
/**
129
+
* Adds a component to the username badge area of a message (e.g. where role icons/new member badge is)
130
+
*/
131
+
addUsernameBadge: (
132
+
id: string,
133
+
component: React.FC<any>,
134
+
anchor?: MessageUsernameBadgeAnchors,
135
+
before?: boolean
136
+
) => void;
137
+
/**
138
+
* Adds a component to the end of a message header (e.g. silent indicator)
139
+
*/
140
+
addBadge: (id: string, component: React.FC<any>, anchor?: MessageBadgeAnchors, before?: boolean) => void;
141
+
/**
142
+
* Adds a component to message accessories (e.g. embeds)
143
+
*/
144
+
addAccessory: (id: string, component: React.FC<any>) => void;
145
+
/**
146
+
* @private
147
+
*/
148
+
_patchUsername: Patcher<any>;
149
+
/**
150
+
* @private
151
+
*/
152
+
_patchUsernameBadges: Patcher<any>;
153
+
/**
154
+
* @private
155
+
*/
156
+
_patchBadges: Patcher<any>;
157
+
/**
158
+
* @private
159
+
*/
160
+
_patchAccessories: Patcher<any>;
161
+
};
162
+
//#endregion
+21
-121
packages/types/src/coreExtensions/contextMenu.ts
+21
-121
packages/types/src/coreExtensions/contextMenu.ts
···
1
-
// TODO: Deduplicate common props
2
-
3
-
export type Menu = React.FunctionComponent<{
4
-
navId: string;
5
-
variant?: string;
6
-
hideScrollbar?: boolean;
7
-
className?: string;
8
-
children: React.ReactComponentElement<MenuElement>[];
9
-
onClose?: () => void;
10
-
onSelect?: () => void;
11
-
}>;
12
-
export type MenuProps = React.ComponentProps<Menu>;
13
-
14
-
export type MenuElement =
15
-
| MenuSeparator
16
-
| MenuGroup
17
-
| MenuItem
18
-
| MenuCheckboxItem
19
-
| MenuRadioItem
20
-
| MenuControlItem;
21
-
22
-
/* eslint-disable prettier/prettier */
23
-
export type MenuSeparator = React.FunctionComponent;
24
-
export type MenuGroup = React.FunctionComponent<{
25
-
label?: string;
26
-
className?: string;
27
-
color?: string;
28
-
children: React.ReactComponentElement<MenuElement>[];
29
-
}>;
30
-
export type MenuItem = React.FunctionComponent<
31
-
{
32
-
id: any;
33
-
dontCloseOnActionIfHoldingShiftKey?: boolean;
34
-
} & (
35
-
| {
36
-
label: string;
37
-
subtext?: string;
38
-
color?: string;
39
-
hint?: string;
40
-
disabled?: boolean;
41
-
icon?: any;
42
-
showIconFirst?: boolean;
43
-
imageUrl?: string;
44
-
45
-
className?: string;
46
-
focusedClassName?: string;
47
-
subMenuIconClassName?: string;
48
-
49
-
action?: () => void;
50
-
onFocus?: () => void;
51
-
52
-
iconProps?: any;
53
-
sparkle?: any;
54
-
55
-
children?: React.ReactComponentElement<MenuElement>[];
56
-
onChildrenScroll?: any;
57
-
childRowHeight?: any;
58
-
listClassName?: string;
59
-
subMenuClassName?: string;
60
-
}
61
-
| {
62
-
color?: string;
63
-
disabled?: boolean;
64
-
keepItemStyles?: boolean;
65
-
66
-
action?: () => void;
67
-
68
-
render: any;
69
-
navigable?: boolean;
70
-
}
71
-
)
72
-
>;
73
-
export type MenuCheckboxItem = React.FunctionComponent<{
74
-
id: any;
75
-
label: string;
76
-
subtext?: string;
77
-
color?: string;
78
-
className?: string;
79
-
focusedClassName?: string;
80
-
disabled?: boolean;
81
-
checked: boolean;
82
-
action?: () => void;
83
-
}>;
84
-
export type MenuRadioItem = React.FunctionComponent<{
85
-
id: any;
86
-
label: string;
87
-
subtext?: string;
88
-
color?: string;
89
-
disabled?: boolean;
90
-
action?: () => void;
91
-
}>;
92
-
export type MenuControlItem = React.FunctionComponent<
93
-
{
94
-
id: any;
95
-
label: string;
96
-
color?: string;
97
-
disabled?: boolean;
98
-
showDefaultFocus?: boolean;
99
-
} & (
100
-
| {
101
-
control: any;
102
-
}
103
-
| {
104
-
control?: undefined;
105
-
interactive?: boolean;
106
-
children?: React.ReactComponentElement<MenuElement>[];
107
-
}
108
-
)
109
-
>;
110
-
/* eslint-disable prettier/prettier */
111
112
export type ContextMenu = {
113
-
addItem: (
114
-
navId: string,
115
-
item: (props: any) => React.ReactComponentElement<MenuElement>,
116
-
anchorId: string,
117
-
before?: boolean
118
-
) => void;
119
120
MenuCheckboxItem: MenuCheckboxItem;
121
MenuControlItem: MenuControlItem;
···
157
label: string;
158
};
159
160
-
export type EvilItemParser = (
161
-
el:
162
-
| React.ReactComponentElement<MenuElement>
163
-
| React.ReactComponentElement<MenuElement>[]
164
-
) => InternalItem[];
···
1
+
import {
2
+
Menu,
3
+
MenuCheckboxItem,
4
+
MenuControlItem,
5
+
MenuGroup,
6
+
MenuRadioItem,
7
+
MenuSeparator,
8
+
MenuItem,
9
+
MenuElement
10
+
} from "@moonlight-mod/mappings/discord/components/common/index";
11
12
export type ContextMenu = {
13
+
/**
14
+
* Registers a new context menu item for a given context menu type.
15
+
* @param navId The navigation ID for the target context menu (e.g. "user-context", "message")
16
+
* @param item A React component
17
+
* @param anchor An existing item's ID to anchor the new item to
18
+
* @param before Whether to insert the new item before the anchor item
19
+
*/
20
+
addItem: (navId: string, item: React.FC<any>, anchor: string | RegExp, before?: boolean) => void;
21
22
MenuCheckboxItem: MenuCheckboxItem;
23
MenuControlItem: MenuControlItem;
···
59
label: string;
60
};
61
62
+
export type EvilItemParser = (el: MenuElement | MenuElement[]) => InternalItem[];
63
+
64
+
export type { Menu, MenuElement };
+20
-23
packages/types/src/coreExtensions/markdown.ts
+20
-23
packages/types/src/coreExtensions/markdown.ts
···
11
12
export type ASTNode = SingleASTNode | Array<SingleASTNode>;
13
14
-
export type Parser = (
15
-
source: string,
16
-
state?: State | null | undefined
17
-
) => Array<SingleASTNode>;
18
19
-
export type ParseFunction = (
20
-
capture: Capture,
21
-
nestedParse: Parser,
22
-
state: State
23
-
) => UntypedASTNode | ASTNode;
24
25
export type Capture =
26
| (Array<string> & {
···
38
39
export type MatchFunction = {
40
regex?: RegExp;
41
-
} & ((
42
-
source: string,
43
-
state: State,
44
-
prevCapture: string
45
-
) => Capture | null | undefined);
46
47
-
export type Output<Result> = (
48
-
node: ASTNode,
49
-
state?: State | null | undefined
50
-
) => Result;
51
52
-
export type SingleNodeOutput<Result> = (
53
-
node: SingleASTNode,
54
-
nestedOutput: Output<Result>,
55
-
state: State
56
-
) => Result;
57
58
// }}}
59
···
100
slateDecorators: Record<string, string>;
101
ruleBlacklists: Record<Ruleset, Record<string, boolean>>;
102
103
addRule: (
104
name: string,
105
markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule,
106
slate: (rules: Record<string, SlateRule>) => SlateRule,
107
decorator?: string | undefined
108
) => void;
109
blacklistFromRuleset: (ruleset: Ruleset, name: string) => void;
110
};
···
11
12
export type ASTNode = SingleASTNode | Array<SingleASTNode>;
13
14
+
export type Parser = (source: string, state?: State | null | undefined) => Array<SingleASTNode>;
15
16
+
export type ParseFunction = (capture: Capture, nestedParse: Parser, state: State) => UntypedASTNode | ASTNode;
17
18
export type Capture =
19
| (Array<string> & {
···
31
32
export type MatchFunction = {
33
regex?: RegExp;
34
+
} & ((source: string, state: State, prevCapture: string) => Capture | null | undefined);
35
36
+
export type Output<Result> = (node: ASTNode, state?: State | null | undefined) => Result;
37
38
+
export type SingleNodeOutput<Result> = (node: SingleASTNode, nestedOutput: Output<Result>, state: State) => Result;
39
40
// }}}
41
···
82
slateDecorators: Record<string, string>;
83
ruleBlacklists: Record<Ruleset, Record<string, boolean>>;
84
85
+
/**
86
+
* Registers a new Markdown rule with simple-markdown.
87
+
* @param name The name of the rule
88
+
* @param markdown A function that returns simple-markdown rules
89
+
* @param slate A function that returns Slate rules
90
+
* @param decorator A decorator name for Slate
91
+
* @see https://www.npmjs.com/package/simple-markdown#adding-a-simple-extension
92
+
* @see https://docs.slatejs.org/
93
+
*/
94
addRule: (
95
name: string,
96
markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule,
97
slate: (rules: Record<string, SlateRule>) => SlateRule,
98
decorator?: string | undefined
99
) => void;
100
+
101
+
/**
102
+
* Blacklist a rule from a ruleset.
103
+
* @param ruleset The ruleset name
104
+
* @param name The rule name
105
+
*/
106
blacklistFromRuleset: (ruleset: Ruleset, name: string) => void;
107
};
+12
-7
packages/types/src/coreExtensions/moonbase.ts
+12
-7
packages/types/src/coreExtensions/moonbase.ts
···
1
+
export type CustomComponentProps = {
2
value: any;
3
setValue: (value: any) => void;
4
+
};
5
+
6
+
export type CustomComponent = React.FC<CustomComponentProps>;
7
8
export type Moonbase = {
9
+
/**
10
+
* Registers a custom component for an extension setting.
11
+
* The extension setting must be of type "custom".
12
+
* @param ext The extension ID
13
+
* @param option The setting ID
14
+
* @param component A React component
15
+
*/
16
+
registerConfigComponent: (ext: string, option: string, component: CustomComponent) => void;
17
};
+16
-1
packages/types/src/coreExtensions/notices.ts
+16
-1
packages/types/src/coreExtensions/notices.ts
···
1
-
import type { Store } from "@moonlight-mod/mappings/discord/packages/flux";
2
3
export type NoticeButton = {
4
name: string;
···
14
};
15
16
export type Notices = Store<any> & {
17
addNotice: (notice: Notice) => void;
18
popNotice: () => void;
19
getCurrentNotice: () => Notice | null;
20
shouldShowNotice: () => boolean;
21
};
···
1
+
import type { Store } from "@moonlight-mod/mappings/discord/packages/flux/Store";
2
3
export type NoticeButton = {
4
name: string;
···
14
};
15
16
export type Notices = Store<any> & {
17
+
/**
18
+
* Adds a custom notice to the top of the screen.
19
+
*/
20
addNotice: (notice: Notice) => void;
21
+
22
+
/**
23
+
* Removes the current notice from the top of the screen.
24
+
*/
25
popNotice: () => void;
26
+
27
+
/**
28
+
* @private
29
+
*/
30
getCurrentNotice: () => Notice | null;
31
+
32
+
/**
33
+
* @private
34
+
*/
35
shouldShowNotice: () => boolean;
36
};
+39
-8
packages/types/src/coreExtensions/settings.ts
+39
-8
packages/types/src/coreExtensions/settings.ts
···
1
import React, { ReactElement } from "react";
2
-
import type { Store } from "@moonlight-mod/mappings/discord/packages/flux";
3
4
export type NoticeProps = {
5
stores: Store<any>[];
···
7
};
8
9
export type SettingsSection =
10
-
| { section: "DIVIDER"; pos: number }
11
-
| { section: "HEADER"; label: string; pos: number }
12
| {
13
section: string;
14
label: string;
15
color: string | null;
16
element: React.FunctionComponent;
17
-
pos: number;
18
notice?: NoticeProps;
19
_moonlight_submenu?: () => ReactElement | ReactElement[];
20
};
21
···
24
sectionNames: string[];
25
sectionMenuItems: Record<string, ReactElement[]>;
26
27
addSection: (
28
section: string,
29
label: string,
30
element: React.FunctionComponent,
31
color?: string | null,
32
-
pos?: number,
33
-
notice?: NoticeProps
34
) => void;
35
addSectionMenuItems: (section: string, ...items: ReactElement[]) => void;
36
37
-
addDivider: (pos: number | null) => void;
38
-
addHeader: (label: string, pos: number | null) => void;
39
_mutateSections: (sections: SettingsSection[]) => SettingsSection[];
40
};
···
1
import React, { ReactElement } from "react";
2
+
import type { Store } from "@moonlight-mod/mappings/discord/packages/flux/Store";
3
4
export type NoticeProps = {
5
stores: Store<any>[];
···
7
};
8
9
export type SettingsSection =
10
+
| { section: "DIVIDER"; pos: number | ((sections: SettingsSection[]) => number) }
11
+
| { section: "HEADER"; label: string; pos: number | ((sections: SettingsSection[]) => number) }
12
| {
13
section: string;
14
label: string;
15
color: string | null;
16
element: React.FunctionComponent;
17
+
pos: number | ((sections: SettingsSection[]) => number);
18
notice?: NoticeProps;
19
+
onClick?: () => void;
20
_moonlight_submenu?: () => ReactElement | ReactElement[];
21
};
22
···
25
sectionNames: string[];
26
sectionMenuItems: Record<string, ReactElement[]>;
27
28
+
/**
29
+
* Registers a new section in the settings menu.
30
+
* @param section The section ID
31
+
* @param label The label for the section
32
+
* @param element The React component to render
33
+
* @param color A color to use for the section
34
+
* @param pos The position in the settings menu to place the section
35
+
* @param notice A notice to display when in the section
36
+
* @param onClick A custom action to execute when clicked from the context menu
37
+
*/
38
addSection: (
39
section: string,
40
label: string,
41
element: React.FunctionComponent,
42
color?: string | null,
43
+
pos?: number | ((sections: SettingsSection[]) => number),
44
+
notice?: NoticeProps,
45
+
onClick?: () => void
46
) => void;
47
+
48
+
/**
49
+
* Adds new items to a section in the settings menu.
50
+
* @param section The section ID
51
+
* @param items The React components to render
52
+
*/
53
addSectionMenuItems: (section: string, ...items: ReactElement[]) => void;
54
55
+
/**
56
+
* Places a divider in the settings menu.
57
+
* @param pos The position in the settings menu to place the divider
58
+
*/
59
+
addDivider: (pos: number | ((sections: SettingsSection[]) => number) | null) => void;
60
+
61
+
/**
62
+
* Places a header in the settings menu.
63
+
* @param pos The position in the settings menu to place the header
64
+
*/
65
+
addHeader: (label: string, pos: number | ((sections: SettingsSection[]) => number) | null) => void;
66
+
67
+
/**
68
+
* @private
69
+
*/
70
_mutateSections: (sections: SettingsSection[]) => SettingsSection[];
71
};
+83
-18
packages/types/src/coreExtensions/spacepack.ts
+83
-18
packages/types/src/coreExtensions/spacepack.ts
···
1
-
import {
2
-
WebpackModule,
3
-
WebpackModuleFunc,
4
-
WebpackRequireType
5
-
} from "../discord";
6
7
export type Spacepack = {
8
inspect: (module: number | string) => WebpackModuleFunc | null;
9
-
findByCode: (...args: (string | RegExp)[]) => any[];
10
-
findByExports: (...args: string[]) => any[];
11
require: WebpackRequireType;
12
modules: Record<string, WebpackModuleFunc>;
13
cache: Record<string, any>;
14
findObjectFromKey: (exports: Record<string, any>, key: string) => any | null;
15
findObjectFromValue: (exports: Record<string, any>, value: any) => any | null;
16
-
findObjectFromKeyValuePair: (
17
-
exports: Record<string, any>,
18
-
key: string,
19
-
value: any
20
-
) => any | null;
21
findFunctionByStrings: (
22
exports: Record<string, any>,
23
...strings: (string | RegExp)[]
24
-
// eslint-disable-next-line @typescript-eslint/ban-types
25
) => Function | null;
26
-
lazyLoad: (
27
-
find: string | RegExp | (string | RegExp)[],
28
-
chunk: RegExp,
29
-
module: RegExp
30
-
) => Promise<any>;
31
filterReal: (modules: WebpackModule[]) => WebpackModule[];
32
};
···
1
+
import { WebpackModule, WebpackModuleFunc, WebpackRequireType } from "../discord";
2
3
export type Spacepack = {
4
+
/**
5
+
* Given a Webpack module ID, returns the function for the Webpack module.
6
+
* Can be double clicked to inspect in DevTools.
7
+
* @param module The module ID
8
+
* @returns The Webpack module, if found
9
+
*/
10
inspect: (module: number | string) => WebpackModuleFunc | null;
11
+
12
+
/**
13
+
* Find Webpack modules based on matches in code.
14
+
* @param args A list of finds to match against
15
+
* @returns The Webpack modules, if found
16
+
*/
17
+
findByCode: (...args: (string | RegExp)[]) => WebpackModule[];
18
+
19
+
/**
20
+
* Find Webpack modules based on their exports.
21
+
* @deprecated This has race conditions. Consider using findByCode instead.
22
+
* @param args A list of finds to match exports against
23
+
* @returns The Webpack modules, if found
24
+
*/
25
+
findByExports: (...args: string[]) => WebpackModule[];
26
+
27
+
/**
28
+
* The Webpack require function.
29
+
*/
30
require: WebpackRequireType;
31
+
32
+
/**
33
+
* The Webpack module list.
34
+
* Re-export of require.m.
35
+
*/
36
modules: Record<string, WebpackModuleFunc>;
37
+
38
+
/**
39
+
* The Webpack module cache.
40
+
* Re-export of require.c.
41
+
*/
42
cache: Record<string, any>;
43
+
44
+
/**
45
+
* Finds an object from a module's exports using the given key.
46
+
* @param exports Exports from a Webpack module
47
+
* @param key The key to find with
48
+
* @returns The object, if found
49
+
*/
50
findObjectFromKey: (exports: Record<string, any>, key: string) => any | null;
51
+
52
+
/**
53
+
* Finds an object from a module's exports using the given value.
54
+
* @param exports Exports from a Webpack module
55
+
* @param value The value to find with
56
+
* @returns The object, if found
57
+
*/
58
findObjectFromValue: (exports: Record<string, any>, value: any) => any | null;
59
+
60
+
/**
61
+
* Finds an object from a module's exports using the given key-value pair.
62
+
* @param exports Exports from a Webpack module
63
+
* @param key The key to find with
64
+
* @param value The value to find with
65
+
* @returns The object, if found
66
+
*/
67
+
findObjectFromKeyValuePair: (exports: Record<string, any>, key: string, value: any) => any | null;
68
+
69
+
/**
70
+
* Finds a function from a module's exports using the given source find.
71
+
* This behaves like findByCode but localized to the exported function.
72
+
* @param exports A module's exports
73
+
* @param strings A list of finds to use
74
+
* @returns The function, if found
75
+
*/
76
findFunctionByStrings: (
77
exports: Record<string, any>,
78
...strings: (string | RegExp)[]
79
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
80
) => Function | null;
81
+
82
+
/**
83
+
* Lazy load a Webpack module.
84
+
* @param find A list of finds to discover a target module with
85
+
* @param chunk A RegExp to match chunks to load
86
+
* @param module A RegExp to match the target Webpack module
87
+
* @returns The target Webpack module
88
+
*/
89
+
lazyLoad: (find: string | RegExp | (string | RegExp)[], chunk: RegExp, module: RegExp) => Promise<any>;
90
+
91
+
/**
92
+
* Filter a list of Webpack modules to "real" ones from the Discord client.
93
+
* @param modules A list of Webpack modules
94
+
* @returns A filtered list of Webpack modules
95
+
*/
96
filterReal: (modules: WebpackModule[]) => WebpackModule[];
97
};
+4
packages/types/src/coreExtensions.ts
+4
packages/types/src/coreExtensions.ts
···
4
export * as ContextMenu from "./coreExtensions/contextMenu";
5
export * as Notices from "./coreExtensions/notices";
6
export * as Moonbase from "./coreExtensions/moonbase";
7
+
export * as AppPanels from "./coreExtensions/appPanels";
8
+
export * as Commands from "./coreExtensions/commands";
9
+
export * as ComponentEditor from "./coreExtensions/componentEditor";
10
+
export * as Common from "./coreExtensions/common";
+17
-2
packages/types/src/discord/require.ts
+17
-2
packages/types/src/discord/require.ts
···
1
import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu";
2
import { Markdown } from "../coreExtensions/markdown";
3
import { Settings } from "../coreExtensions/settings";
4
import { Spacepack } from "../coreExtensions/spacepack";
5
-
import { Notices } from "../coreExtensions/notices";
6
-
import { Moonbase } from "../coreExtensions/moonbase";
7
8
declare function WebpackRequire(id: string): any;
9
10
declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser;
11
declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;
···
1
+
import { AppPanels } from "../coreExtensions/appPanels";
2
+
import { Commands } from "../coreExtensions/commands";
3
+
import { ErrorBoundary, Icons } from "../coreExtensions/common";
4
+
import { DMList, MemberList, Messages } from "../coreExtensions/componentEditor";
5
import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu";
6
import { Markdown } from "../coreExtensions/markdown";
7
+
import { Moonbase } from "../coreExtensions/moonbase";
8
+
import { Notices } from "../coreExtensions/notices";
9
import { Settings } from "../coreExtensions/settings";
10
import { Spacepack } from "../coreExtensions/spacepack";
11
12
declare function WebpackRequire(id: string): any;
13
+
14
+
declare function WebpackRequire(id: "appPanels_appPanels"): AppPanels;
15
+
16
+
declare function WebpackRequire(id: "commands_commands"): Commands;
17
+
18
+
declare function WebpackRequire(id: "common_ErrorBoundary"): ErrorBoundary;
19
+
declare function WebpackRequire(id: "common_icons"): Icons;
20
+
21
+
declare function WebpackRequire(id: "componentEditor_dmList"): DMList;
22
+
declare function WebpackRequire(id: "componentEditor_memberList"): MemberList;
23
+
declare function WebpackRequire(id: "componentEditor_messages"): Messages;
24
25
declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser;
26
declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;
+3
-11
packages/types/src/discord/webpack.ts
+3
-11
packages/types/src/discord/webpack.ts
···
10
11
export type WebpackModule = {
12
id: string | number;
13
-
loaded: boolean;
14
exports: any;
15
};
16
17
-
export type WebpackModuleFunc = ((
18
-
module: any,
19
-
exports: any,
20
-
require: WebpackRequireType
21
-
) => void) & {
22
__moonlight?: boolean;
23
};
24
25
-
export type WebpackJsonpEntry = [
26
-
number[],
27
-
{ [id: string]: WebpackModuleFunc },
28
-
(require: WebpackRequireType) => any
29
-
];
30
31
export type WebpackJsonp = WebpackJsonpEntry[] & {
32
push: {
···
10
11
export type WebpackModule = {
12
id: string | number;
13
+
loaded?: boolean;
14
exports: any;
15
};
16
17
+
export type WebpackModuleFunc = ((module: any, exports: any, require: WebpackRequireType) => void) & {
18
__moonlight?: boolean;
19
};
20
21
+
export type WebpackJsonpEntry = [number[], { [id: string]: WebpackModuleFunc }, (require: WebpackRequireType) => any];
22
23
export type WebpackJsonp = WebpackJsonpEntry[] & {
24
push: {
+105
-3
packages/types/src/extension.ts
+105
-3
packages/types/src/extension.ts
···
28
};
29
30
export type ExtensionManifest = {
31
id: string;
32
version?: string;
33
apiLevel?: number;
34
environment?: ExtensionEnvironment;
35
36
meta?: {
37
name?: string;
38
tagline?: string;
39
description?: string;
40
authors?: ExtensionAuthor[];
41
-
deprecated?: boolean;
42
tags?: ExtensionTag[];
43
source?: string;
44
};
45
46
dependencies?: string[];
47
suggested?: string[];
48
incompatible?: string[];
49
50
settings?: Record<string, ExtensionSettingsManifest>;
51
52
cors?: string[];
53
blocked?: string[];
54
};
55
56
export enum ExtensionEnvironment {
57
Both = "both",
58
Desktop = "desktop",
59
Web = "web"
60
}
61
···
75
webpackModules?: Record<string, string>;
76
nodePath?: string;
77
hostPath?: string;
78
};
79
};
80
···
106
export type Patch = {
107
find: PatchMatch;
108
replace: PatchReplace | PatchReplace[];
109
prerequisite?: () => boolean;
110
};
111
···
133
id: number;
134
};
135
136
-
export type IdentifiedWebpackModule = ExtensionWebpackModule &
137
-
ExplicitExtensionDependency;
···
28
};
29
30
export type ExtensionManifest = {
31
+
$schema?: string;
32
+
33
+
/**
34
+
* A unique identifier for your extension.
35
+
*/
36
id: string;
37
+
38
+
/**
39
+
* A version string for your extension - doesn't need to follow a specific format. Required for publishing.
40
+
*/
41
version?: string;
42
+
43
+
/**
44
+
* The API level this extension targets. If it does not match the current version, the extension will not be loaded.
45
+
*/
46
apiLevel?: number;
47
+
48
+
/**
49
+
* Which environment this extension is capable of running in.
50
+
*/
51
environment?: ExtensionEnvironment;
52
53
+
/**
54
+
* Metadata about your extension for use in Moonbase.
55
+
*/
56
meta?: {
57
+
/**
58
+
* A human friendly name for your extension as a proper noun.
59
+
*/
60
name?: string;
61
+
62
+
/**
63
+
* A short tagline that appears below the name.
64
+
*/
65
tagline?: string;
66
+
67
+
/**
68
+
* A longer description that can use Markdown.
69
+
*/
70
description?: string;
71
+
72
+
/**
73
+
* List of authors that worked on this extension - accepts string or object with ID.
74
+
*/
75
authors?: ExtensionAuthor[];
76
+
77
+
/**
78
+
* A list of tags that are relevant to the extension.
79
+
*/
80
tags?: ExtensionTag[];
81
+
82
+
/**
83
+
* The URL to the source repository.
84
+
*/
85
source?: string;
86
+
87
+
/**
88
+
* A donation link (or other method of support). If you don't want financial contributions, consider putting your favorite charity here!
89
+
*/
90
+
donate?: string;
91
+
92
+
/**
93
+
* A changelog to show in Moonbase.
94
+
* Moonbase will show the changelog for the latest version, even if it is not installed.
95
+
*/
96
+
changelog?: string;
97
+
98
+
/**
99
+
* Whether the extension is deprecated and no longer receiving updates.
100
+
*/
101
+
deprecated?: boolean;
102
};
103
104
+
/**
105
+
* A list of extension IDs that are required for the extension to load.
106
+
*/
107
dependencies?: string[];
108
+
109
+
/**
110
+
* A list of extension IDs that the user may want to install.
111
+
*/
112
suggested?: string[];
113
+
114
+
/**
115
+
* A list of extension IDs that the extension is incompatible with.
116
+
* If two incompatible extensions are enabled, one of them will not load.
117
+
*/
118
incompatible?: string[];
119
120
+
/**
121
+
* A list of settings for your extension, where the key is the settings ID.
122
+
*/
123
settings?: Record<string, ExtensionSettingsManifest>;
124
125
+
/**
126
+
* A list of URLs to bypass CORS for.
127
+
* This is implemented by checking if the start of the URL matches.
128
+
* @example https://moonlight-mod.github.io/
129
+
*/
130
cors?: string[];
131
+
132
+
/**
133
+
* A list of URLs to block all requests to.
134
+
* This is implemented by checking if the start of the URL matches.
135
+
* @example https://moonlight-mod.github.io/
136
+
*/
137
blocked?: string[];
138
+
139
+
/**
140
+
* A mapping from CSP directives to URLs to allow.
141
+
* @example { "script-src": ["https://example.com"] }
142
+
*/
143
+
csp?: Record<string, string[]>;
144
};
145
146
export enum ExtensionEnvironment {
147
+
/**
148
+
* The extension will run on both platforms, the host/native modules MAY be loaded
149
+
*/
150
Both = "both",
151
+
152
+
/**
153
+
* Extension will run on desktop only, the host/native modules are guaranteed to load
154
+
*/
155
Desktop = "desktop",
156
+
157
+
/**
158
+
* Currently equivalent to Both
159
+
*/
160
Web = "web"
161
}
162
···
176
webpackModules?: Record<string, string>;
177
nodePath?: string;
178
hostPath?: string;
179
+
style?: string;
180
};
181
};
182
···
208
export type Patch = {
209
find: PatchMatch;
210
replace: PatchReplace | PatchReplace[];
211
+
hardFail?: boolean; // if any patches fail, all fail
212
prerequisite?: () => boolean;
213
};
214
···
236
id: number;
237
};
238
239
+
export type IdentifiedWebpackModule = ExtensionWebpackModule & ExplicitExtensionDependency;
+1
packages/types/src/fs.ts
+1
packages/types/src/fs.ts
+37
-21
packages/types/src/globals.ts
+37
-21
packages/types/src/globals.ts
···
1
import type { Logger } from "./logger";
2
import type { Config, ConfigExtension } from "./config";
3
-
import type {
4
-
DetectedExtension,
5
-
IdentifiedPatch,
6
-
IdentifiedWebpackModule,
7
-
ProcessedExtensions
8
-
} from "./extension";
9
import type EventEmitter from "events";
10
import type LunAST from "@moonlight-mod/lunast";
11
import type Moonmap from "@moonlight-mod/moonmap";
12
import type {
13
-
EventPayloads,
14
-
EventType,
15
-
MoonlightEventEmitter
16
} from "./core/event";
17
18
export type MoonlightHost = {
19
-
asarPath: string;
20
config: Config;
21
-
events: EventEmitter;
22
extensions: DetectedExtension[];
23
processedExtensions: ProcessedExtensions;
24
25
version: string;
26
branch: MoonlightBranch;
27
28
getConfig: (ext: string) => ConfigExtension["config"];
29
getConfigOption: <T>(ext: string, name: string) => T | undefined;
30
getLogger: (id: string) => Logger;
31
};
32
33
export type MoonlightNode = {
···
36
processedExtensions: ProcessedExtensions;
37
nativesCache: Record<string, any>;
38
isBrowser: boolean;
39
40
version: string;
41
branch: MoonlightBranch;
42
43
getConfig: (ext: string) => ConfigExtension["config"];
44
getConfigOption: <T>(ext: string, name: string) => T | undefined;
45
getNatives: (ext: string) => any | undefined;
46
getLogger: (id: string) => Logger;
47
-
48
getMoonlightDir: () => string;
49
getExtensionDir: (ext: string) => string;
50
-
writeConfig: (config: Config) => Promise<void>;
51
};
52
53
export type MoonlightWeb = {
54
unpatched: Set<IdentifiedPatch>;
55
pendingModules: Set<IdentifiedWebpackModule>;
56
enabledExtensions: Set<string>;
57
-
apiLevel: number;
58
-
events: MoonlightEventEmitter<EventType, EventPayloads>;
59
patchingInternals: {
60
-
onModuleLoad: (
61
-
moduleId: string | string[],
62
-
callback: (moduleId: string) => void
63
-
) => void;
64
registerPatch: (patch: IdentifiedPatch) => void;
65
registerWebpackModule: (module: IdentifiedWebpackModule) => void;
66
};
67
68
version: string;
69
branch: MoonlightBranch;
70
71
-
getConfig: (ext: string) => ConfigExtension["config"];
72
-
getConfigOption: <T>(ext: string, name: string) => T | undefined;
73
getNatives: (ext: string) => any | undefined;
74
getLogger: (id: string) => Logger;
75
lunast: LunAST;
76
moonmap: Moonmap;
77
};
···
1
import type { Logger } from "./logger";
2
import type { Config, ConfigExtension } from "./config";
3
+
import type { DetectedExtension, IdentifiedPatch, IdentifiedWebpackModule, ProcessedExtensions } from "./extension";
4
import type EventEmitter from "events";
5
import type LunAST from "@moonlight-mod/lunast";
6
import type Moonmap from "@moonlight-mod/moonmap";
7
import type {
8
+
WebEventPayloads,
9
+
WebEventType,
10
+
MoonlightEventEmitter,
11
+
NodeEventType,
12
+
NodeEventPayloads
13
} from "./core/event";
14
+
import type { MoonlightFS } from "./fs";
15
16
export type MoonlightHost = {
17
config: Config;
18
extensions: DetectedExtension[];
19
processedExtensions: ProcessedExtensions;
20
+
asarPath: string;
21
+
events: EventEmitter;
22
23
version: string;
24
branch: MoonlightBranch;
25
26
getConfig: (ext: string) => ConfigExtension["config"];
27
+
getConfigPath: () => Promise<string>;
28
getConfigOption: <T>(ext: string, name: string) => T | undefined;
29
+
setConfigOption: <T>(ext: string, name: string, value: T) => void;
30
+
writeConfig: (config: Config) => Promise<void>;
31
+
32
getLogger: (id: string) => Logger;
33
+
getMoonlightDir: () => string;
34
+
getExtensionDir: (ext: string) => string;
35
};
36
37
export type MoonlightNode = {
···
40
processedExtensions: ProcessedExtensions;
41
nativesCache: Record<string, any>;
42
isBrowser: boolean;
43
+
events: MoonlightEventEmitter<NodeEventType, NodeEventPayloads>;
44
45
version: string;
46
branch: MoonlightBranch;
47
48
getConfig: (ext: string) => ConfigExtension["config"];
49
getConfigOption: <T>(ext: string, name: string) => T | undefined;
50
+
setConfigOption: <T>(ext: string, name: string, value: T) => Promise<void>;
51
+
writeConfig: (config: Config) => Promise<void>;
52
+
53
getNatives: (ext: string) => any | undefined;
54
getLogger: (id: string) => Logger;
55
getMoonlightDir: () => string;
56
getExtensionDir: (ext: string) => string;
57
+
};
58
+
59
+
export type MoonlightNodeSandboxed = {
60
+
fs: MoonlightFS;
61
+
addCors: (url: string) => void;
62
+
addBlocked: (url: string) => void;
63
};
64
65
export type MoonlightWeb = {
66
+
patched: Map<string, Set<string>>;
67
unpatched: Set<IdentifiedPatch>;
68
pendingModules: Set<IdentifiedWebpackModule>;
69
enabledExtensions: Set<string>;
70
+
events: MoonlightEventEmitter<WebEventType, WebEventPayloads>;
71
patchingInternals: {
72
+
onModuleLoad: (moduleId: string | string[], callback: (moduleId: string) => void) => void;
73
registerPatch: (patch: IdentifiedPatch) => void;
74
registerWebpackModule: (module: IdentifiedWebpackModule) => void;
75
};
76
+
localStorage: Storage;
77
78
version: string;
79
branch: MoonlightBranch;
80
+
apiLevel: number;
81
82
+
// Re-exports for ease of use
83
+
getConfig: MoonlightNode["getConfig"];
84
+
getConfigOption: MoonlightNode["getConfigOption"];
85
+
setConfigOption: MoonlightNode["setConfigOption"];
86
+
writeConfig: MoonlightNode["writeConfig"];
87
+
88
getNatives: (ext: string) => any | undefined;
89
getLogger: (id: string) => Logger;
90
+
91
lunast: LunAST;
92
moonmap: Moonmap;
93
};
+38
packages/types/src/import.d.ts
+38
packages/types/src/import.d.ts
···
1
+
declare module "@moonlight-mod/wp/appPanels_appPanels" {
2
+
import { CoreExtensions } from "@moonlight-mod/types";
3
+
const AppPanels: CoreExtensions.AppPanels.AppPanels;
4
+
export = AppPanels;
5
+
}
6
+
7
+
declare module "@moonlight-mod/wp/commands_commands" {
8
+
import { CoreExtensions } from "@moonlight-mod/types";
9
+
export const commands: CoreExtensions.Commands.Commands;
10
+
export default commands;
11
+
}
12
+
13
+
declare module "@moonlight-mod/wp/common_ErrorBoundary" {
14
+
import { CoreExtensions } from "@moonlight-mod/types";
15
+
const ErrorBoundary: CoreExtensions.Common.ErrorBoundary;
16
+
export = ErrorBoundary;
17
+
}
18
+
declare module "@moonlight-mod/wp/common_icons" {
19
+
import { CoreExtensions } from "@moonlight-mod/types";
20
+
export const icons: CoreExtensions.Common.Icons;
21
+
export default icons;
22
+
}
23
declare module "@moonlight-mod/wp/common_stores";
24
+
25
+
declare module "@moonlight-mod/wp/componentEditor_dmList" {
26
+
import { CoreExtensions } from "@moonlight-mod/types";
27
+
export const dmList: CoreExtensions.ComponentEditor.DMList;
28
+
export default dmList;
29
+
}
30
+
declare module "@moonlight-mod/wp/componentEditor_memberList" {
31
+
import { CoreExtensions } from "@moonlight-mod/types";
32
+
export const memberList: CoreExtensions.ComponentEditor.MemberList;
33
+
export default memberList;
34
+
}
35
+
declare module "@moonlight-mod/wp/componentEditor_messages" {
36
+
import { CoreExtensions } from "@moonlight-mod/types";
37
+
export const message: CoreExtensions.ComponentEditor.Messages;
38
+
export default message;
39
+
}
40
41
declare module "@moonlight-mod/wp/contextMenu_evilMenu" {
42
import { CoreExtensions } from "@moonlight-mod/types";
+5
-10
packages/types/src/index.ts
+5
-10
packages/types/src/index.ts
···
4
/// <reference types="./mappings" />
5
/* eslint-disable no-var */
6
7
-
import { MoonlightFS } from "./fs";
8
-
import {
9
-
MoonlightEnv,
10
-
MoonlightHost,
11
-
MoonlightNode,
12
-
MoonlightWeb
13
-
} from "./globals";
14
15
export * from "./discord";
16
export * from "./config";
···
36
37
var moonlightHost: MoonlightHost;
38
var moonlightNode: MoonlightNode;
39
var moonlight: MoonlightWeb;
40
-
var moonlightFS: MoonlightFS;
41
42
-
var _moonlightBrowserInit: () => Promise<void>;
43
-
var _moonlightBrowserLoad: () => Promise<void>;
44
}
···
4
/// <reference types="./mappings" />
5
/* eslint-disable no-var */
6
7
+
import { MoonlightEnv, MoonlightHost, MoonlightNode, MoonlightNodeSandboxed, MoonlightWeb } from "./globals";
8
9
export * from "./discord";
10
export * from "./config";
···
30
31
var moonlightHost: MoonlightHost;
32
var moonlightNode: MoonlightNode;
33
+
var moonlightNodeSandboxed: MoonlightNodeSandboxed;
34
var moonlight: MoonlightWeb;
35
+
var _moonlight_coreExtensionsStr: string;
36
37
+
var _moonlightBrowserInit: undefined | (() => Promise<void>);
38
+
var _moonlightWebLoad: undefined | (() => Promise<void>);
39
}
+860
-14
packages/types/src/mappings.d.ts
+860
-14
packages/types/src/mappings.d.ts
···
1
// auto-generated
2
declare module "@moonlight-mod/wp/discord/Dispatcher" {
3
import { MappedModules } from "@moonlight-mod/mappings";
4
-
const _: MappedModules["discord/Dispatcher"];
5
-
export = _;
6
}
7
8
declare module "@moonlight-mod/wp/discord/components/common/index" {
9
import { MappedModules } from "@moonlight-mod/mappings";
10
-
const _: MappedModules["discord/components/common/index"];
11
-
export = _;
12
}
13
14
-
declare module "@moonlight-mod/wp/discord/modules/guild_settings/IntegrationCard.css" {
15
import { MappedModules } from "@moonlight-mod/mappings";
16
-
const _: MappedModules["discord/modules/guild_settings/IntegrationCard.css"];
17
-
export = _;
18
}
19
20
declare module "@moonlight-mod/wp/discord/modules/markup/MarkupUtils" {
21
import { MappedModules } from "@moonlight-mod/mappings";
22
-
const _: MappedModules["discord/modules/markup/MarkupUtils"];
23
-
export = _;
24
}
25
26
declare module "@moonlight-mod/wp/discord/packages/flux" {
27
import { MappedModules } from "@moonlight-mod/mappings";
28
-
const _: MappedModules["discord/packages/flux"];
29
-
export = _;
30
}
31
32
declare module "@moonlight-mod/wp/discord/uikit/Flex" {
33
import { MappedModules } from "@moonlight-mod/mappings";
34
-
const _: MappedModules["discord/uikit/Flex"];
35
-
export = _;
36
}
37
38
declare module "@moonlight-mod/wp/react" {
39
import { MappedModules } from "@moonlight-mod/mappings";
40
-
const _: MappedModules["react"];
41
export = _;
42
}
···
1
// auto-generated
2
+
declare module "@moonlight-mod/wp/chroma-js" {}
3
+
4
+
declare module "@moonlight-mod/wp/classnames" {
5
+
import { MappedModules } from "@moonlight-mod/mappings";
6
+
const _default: MappedModules["classnames"]["default"];
7
+
export default _default;
8
+
}
9
+
10
+
declare module "@moonlight-mod/wp/dependency-graph" {
11
+
import { MappedModules } from "@moonlight-mod/mappings";
12
+
export const DepGraph: MappedModules["dependency-graph"]["DepGraph"];
13
+
}
14
+
15
+
declare module "@moonlight-mod/wp/discord/Constants" {
16
+
import { MappedModules } from "@moonlight-mod/mappings";
17
+
export const ActivityFlags: MappedModules["discord/Constants"]["ActivityFlags"];
18
+
export const ActivityTypes: MappedModules["discord/Constants"]["ActivityTypes"];
19
+
export const AnalyticsLocations: MappedModules["discord/Constants"]["AnalyticsLocations"];
20
+
export const ChannelLayouts: MappedModules["discord/Constants"]["ChannelLayouts"];
21
+
export const ChannelModes: MappedModules["discord/Constants"]["ChannelModes"];
22
+
export const ChannelTypes: MappedModules["discord/Constants"]["ChannelTypes"];
23
+
export const ChannelStreamTypes: MappedModules["discord/Constants"]["ChannelStreamTypes"];
24
+
export const ComponentActions: MappedModules["discord/Constants"]["ComponentActions"];
25
+
export const DEFAULT_ROLE_COLOR: MappedModules["discord/Constants"]["DEFAULT_ROLE_COLOR"];
26
+
export const Endpoints: MappedModules["discord/Constants"]["Endpoints"];
27
+
export const MessageFlags: MappedModules["discord/Constants"]["MessageFlags"];
28
+
export const MessageTypes: MappedModules["discord/Constants"]["MessageTypes"];
29
+
export const Permissions: MappedModules["discord/Constants"]["Permissions"];
30
+
export const PlatformTypes: MappedModules["discord/Constants"]["PlatformTypes"];
31
+
export const RelationshipTypes: MappedModules["discord/Constants"]["RelationshipTypes"];
32
+
export const Routes: MappedModules["discord/Constants"]["Routes"];
33
+
export const StatusTypes: MappedModules["discord/Constants"]["StatusTypes"];
34
+
export const Themes: MappedModules["discord/Constants"]["Themes"];
35
+
export const UserSettingsSections: MappedModules["discord/Constants"]["UserSettingsSections"];
36
+
export const UserFlags: MappedModules["discord/Constants"]["UserFlags"];
37
+
}
38
+
39
declare module "@moonlight-mod/wp/discord/Dispatcher" {
40
import { MappedModules } from "@moonlight-mod/mappings";
41
+
const _default: MappedModules["discord/Dispatcher"]["default"];
42
+
export default _default;
43
+
}
44
+
45
+
declare module "@moonlight-mod/wp/discord/actions/ContextMenuActionCreators" {
46
+
import { MappedModules } from "@moonlight-mod/mappings";
47
+
export const closeContextMenu: MappedModules["discord/actions/ContextMenuActionCreators"]["closeContextMenu"];
48
+
export const openContextMenu: MappedModules["discord/actions/ContextMenuActionCreators"]["openContextMenu"];
49
+
export const openContextMenuLazy: MappedModules["discord/actions/ContextMenuActionCreators"]["openContextMenuLazy"];
50
+
}
51
+
52
+
declare module "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators" {
53
+
import { MappedModules } from "@moonlight-mod/mappings";
54
+
const _default: MappedModules["discord/actions/UserSettingsModalActionCreators"]["default"];
55
+
export default _default;
56
+
}
57
+
58
+
declare module "@moonlight-mod/wp/discord/common/AppStartPerformance" {
59
+
import { MappedModules } from "@moonlight-mod/mappings";
60
+
const _default: MappedModules["discord/common/AppStartPerformance"]["default"];
61
+
export default _default;
62
+
}
63
+
64
+
declare module "@moonlight-mod/wp/discord/components/common/Alerts" {
65
+
import { MappedModules } from "@moonlight-mod/mappings";
66
+
const _default: MappedModules["discord/components/common/Alerts"]["default"];
67
+
export default _default;
68
+
}
69
+
70
+
declare module "@moonlight-mod/wp/discord/components/common/BaseHeaderBar" {
71
+
import { MappedModules } from "@moonlight-mod/mappings";
72
+
export const Icon: MappedModules["discord/components/common/BaseHeaderBar"]["Icon"];
73
+
export const Divider: MappedModules["discord/components/common/BaseHeaderBar"]["Divider"];
74
+
const _default: MappedModules["discord/components/common/BaseHeaderBar"]["default"];
75
+
export default _default;
76
+
}
77
+
78
+
declare module "@moonlight-mod/wp/discord/components/common/Card" {
79
+
import { MappedModules } from "@moonlight-mod/mappings";
80
+
const _default: MappedModules["discord/components/common/Card"]["default"];
81
+
export default _default;
82
+
export const Types: MappedModules["discord/components/common/Card"]["Types"];
83
+
}
84
+
85
+
declare module "@moonlight-mod/wp/discord/components/common/FileUpload" {
86
+
import { MappedModules } from "@moonlight-mod/mappings";
87
+
const _default: MappedModules["discord/components/common/FileUpload"]["default"];
88
+
export default _default;
89
+
}
90
+
91
+
declare module "@moonlight-mod/wp/discord/components/common/FormSwitch.css" {
92
+
import { MappedModules } from "@moonlight-mod/mappings";
93
+
export const container: MappedModules["discord/components/common/FormSwitch.css"]["container"];
94
+
export const labelRow: MappedModules["discord/components/common/FormSwitch.css"]["labelRow"];
95
+
export const control: MappedModules["discord/components/common/FormSwitch.css"]["control"];
96
+
export const disabled: MappedModules["discord/components/common/FormSwitch.css"]["disabled"];
97
+
export const title: MappedModules["discord/components/common/FormSwitch.css"]["title"];
98
+
export const note: MappedModules["discord/components/common/FormSwitch.css"]["note"];
99
+
export const disabledText: MappedModules["discord/components/common/FormSwitch.css"]["disabledText"];
100
+
export const dividerDefault: MappedModules["discord/components/common/FormSwitch.css"]["dividerDefault"];
101
+
}
102
+
103
+
declare module "@moonlight-mod/wp/discord/components/common/HeaderBar.css" {
104
+
import { MappedModules } from "@moonlight-mod/mappings";
105
+
export const caret: MappedModules["discord/components/common/HeaderBar.css"]["caret"];
106
+
export const children: MappedModules["discord/components/common/HeaderBar.css"]["children"];
107
+
export const clickable: MappedModules["discord/components/common/HeaderBar.css"]["clickable"];
108
+
export const container: MappedModules["discord/components/common/HeaderBar.css"]["container"];
109
+
export const divider: MappedModules["discord/components/common/HeaderBar.css"]["divider"];
110
+
export const dot: MappedModules["discord/components/common/HeaderBar.css"]["dot"];
111
+
export const hamburger: MappedModules["discord/components/common/HeaderBar.css"]["hamburger"];
112
+
export const icon: MappedModules["discord/components/common/HeaderBar.css"]["icon"];
113
+
export const iconBadge: MappedModules["discord/components/common/HeaderBar.css"]["iconBadge"];
114
+
export const iconBadgeBottom: MappedModules["discord/components/common/HeaderBar.css"]["iconBadgeBottom"];
115
+
export const iconBadgeTop: MappedModules["discord/components/common/HeaderBar.css"]["iconBadgeTop"];
116
+
export const iconWrapper: MappedModules["discord/components/common/HeaderBar.css"]["iconWrapper"];
117
+
export const scrollable: MappedModules["discord/components/common/HeaderBar.css"]["scrollable"];
118
+
export const selected: MappedModules["discord/components/common/HeaderBar.css"]["selected"];
119
+
export const themed: MappedModules["discord/components/common/HeaderBar.css"]["themed"];
120
+
export const themedMobile: MappedModules["discord/components/common/HeaderBar.css"]["themedMobile"];
121
+
export const title: MappedModules["discord/components/common/HeaderBar.css"]["title"];
122
+
export const titleWrapper: MappedModules["discord/components/common/HeaderBar.css"]["titleWrapper"];
123
+
export const toolbar: MappedModules["discord/components/common/HeaderBar.css"]["toolbar"];
124
+
export const transparent: MappedModules["discord/components/common/HeaderBar.css"]["transparent"];
125
+
export const upperContainer: MappedModules["discord/components/common/HeaderBar.css"]["upperContainer"];
126
+
}
127
+
128
+
declare module "@moonlight-mod/wp/discord/components/common/HelpMessage.css" {
129
+
import { MappedModules } from "@moonlight-mod/mappings";
130
+
export const container: MappedModules["discord/components/common/HelpMessage.css"]["container"];
131
+
export const icon: MappedModules["discord/components/common/HelpMessage.css"]["icon"];
132
+
export const iconDiv: MappedModules["discord/components/common/HelpMessage.css"]["iconDiv"];
133
+
export const text: MappedModules["discord/components/common/HelpMessage.css"]["text"];
134
+
export const positive: MappedModules["discord/components/common/HelpMessage.css"]["positive"];
135
+
export const warning: MappedModules["discord/components/common/HelpMessage.css"]["warning"];
136
+
export const info: MappedModules["discord/components/common/HelpMessage.css"]["info"];
137
+
export const error: MappedModules["discord/components/common/HelpMessage.css"]["error"];
138
+
}
139
+
140
+
declare module "@moonlight-mod/wp/discord/components/common/Image" {}
141
+
142
+
declare module "@moonlight-mod/wp/discord/components/common/PanelButton" {
143
+
import { MappedModules } from "@moonlight-mod/mappings";
144
+
const _default: MappedModules["discord/components/common/PanelButton"]["default"];
145
+
export default _default;
146
+
}
147
+
148
+
declare module "@moonlight-mod/wp/discord/components/common/Scroller.css" {
149
+
import { MappedModules } from "@moonlight-mod/mappings";
150
+
export const auto: MappedModules["discord/components/common/Scroller.css"]["auto"];
151
+
export const content: MappedModules["discord/components/common/Scroller.css"]["content"];
152
+
export const customTheme: MappedModules["discord/components/common/Scroller.css"]["customTheme"];
153
+
export const disableScrollAnchor: MappedModules["discord/components/common/Scroller.css"]["disableScrollAnchor"];
154
+
export const fade: MappedModules["discord/components/common/Scroller.css"]["fade"];
155
+
export const managedReactiveScroller: MappedModules["discord/components/common/Scroller.css"]["managedReactiveScroller"];
156
+
export const none: MappedModules["discord/components/common/Scroller.css"]["none"];
157
+
export const pointerCover: MappedModules["discord/components/common/Scroller.css"]["pointerCover"];
158
+
export const scrolling: MappedModules["discord/components/common/Scroller.css"]["scrolling"];
159
+
export const thin: MappedModules["discord/components/common/Scroller.css"]["thin"];
160
}
161
162
declare module "@moonlight-mod/wp/discord/components/common/index" {
163
import { MappedModules } from "@moonlight-mod/mappings";
164
+
export const Clickable: MappedModules["discord/components/common/index"]["Clickable"];
165
+
export const TextInput: MappedModules["discord/components/common/index"]["TextInput"];
166
+
export const TextArea: MappedModules["discord/components/common/index"]["TextArea"];
167
+
export const FormDivider: MappedModules["discord/components/common/index"]["FormDivider"];
168
+
export const FormSection: MappedModules["discord/components/common/index"]["FormSection"];
169
+
export const FormText: MappedModules["discord/components/common/index"]["FormText"];
170
+
export const FormTitle: MappedModules["discord/components/common/index"]["FormTitle"];
171
+
export const FormSwitch: MappedModules["discord/components/common/index"]["FormSwitch"];
172
+
export const FormItem: MappedModules["discord/components/common/index"]["FormItem"];
173
+
export const Slider: MappedModules["discord/components/common/index"]["Slider"];
174
+
export const Switch: MappedModules["discord/components/common/index"]["Switch"];
175
+
export const Button: MappedModules["discord/components/common/index"]["Button"];
176
+
export const Tooltip: MappedModules["discord/components/common/index"]["Tooltip"];
177
+
export const Avatar: MappedModules["discord/components/common/index"]["Avatar"];
178
+
export const AvatarSizes: MappedModules["discord/components/common/index"]["AvatarSizes"];
179
+
export const AvatarSizeSpecs: MappedModules["discord/components/common/index"]["AvatarSizeSpecs"];
180
+
export const Scroller: MappedModules["discord/components/common/index"]["Scroller"];
181
+
export const Text: MappedModules["discord/components/common/index"]["Text"];
182
+
export const Heading: MappedModules["discord/components/common/index"]["Heading"];
183
+
export const Card: MappedModules["discord/components/common/index"]["Card"];
184
+
export const Popout: MappedModules["discord/components/common/index"]["Popout"];
185
+
export const Dialog: MappedModules["discord/components/common/index"]["Dialog"];
186
+
export const Menu: MappedModules["discord/components/common/index"]["Menu"];
187
+
export const TabBar: MappedModules["discord/components/common/index"]["TabBar"];
188
+
export const SingleSelect: MappedModules["discord/components/common/index"]["SingleSelect"];
189
+
export const Select: MappedModules["discord/components/common/index"]["Select"];
190
+
export const NoticeColors: MappedModules["discord/components/common/index"]["NoticeColors"];
191
+
export const Notice: MappedModules["discord/components/common/index"]["Notice"];
192
+
export const NoticeCloseButton: MappedModules["discord/components/common/index"]["NoticeCloseButton"];
193
+
export const PrimaryCTANoticeButton: MappedModules["discord/components/common/index"]["PrimaryCTANoticeButton"];
194
+
export const Breadcrumbs: MappedModules["discord/components/common/index"]["Breadcrumbs"];
195
+
export const Image: MappedModules["discord/components/common/index"]["Image"];
196
+
export const tokens: MappedModules["discord/components/common/index"]["tokens"];
197
+
export const useVariableSelect: MappedModules["discord/components/common/index"]["useVariableSelect"];
198
+
export const useMultiSelect: MappedModules["discord/components/common/index"]["useMultiSelect"];
199
+
export const multiSelect: MappedModules["discord/components/common/index"]["multiSelect"];
200
+
export const openModal: MappedModules["discord/components/common/index"]["openModal"];
201
+
export const openModalLazy: MappedModules["discord/components/common/index"]["openModalLazy"];
202
+
export const closeModal: MappedModules["discord/components/common/index"]["closeModal"];
203
+
export const AngleBracketsIcon: MappedModules["discord/components/common/index"]["AngleBracketsIcon"];
204
+
export const ArrowAngleLeftUpIcon: MappedModules["discord/components/common/index"]["ArrowAngleLeftUpIcon"];
205
+
export const ArrowAngleRightUpIcon: MappedModules["discord/components/common/index"]["ArrowAngleRightUpIcon"];
206
+
export const ArrowsUpDownIcon: MappedModules["discord/components/common/index"]["ArrowsUpDownIcon"];
207
+
export const BookCheckIcon: MappedModules["discord/components/common/index"]["BookCheckIcon"];
208
+
export const ChannelListIcon: MappedModules["discord/components/common/index"]["ChannelListIcon"];
209
+
export const ChevronSmallDownIcon: MappedModules["discord/components/common/index"]["ChevronSmallDownIcon"];
210
+
export const ChevronSmallUpIcon: MappedModules["discord/components/common/index"]["ChevronSmallUpIcon"];
211
+
export const CircleInformationIcon: MappedModules["discord/components/common/index"]["CircleInformationIcon"];
212
+
export const CircleWarningIcon: MappedModules["discord/components/common/index"]["CircleWarningIcon"];
213
+
export const CircleXIcon: MappedModules["discord/components/common/index"]["CircleXIcon"];
214
+
export const ClydeIcon: MappedModules["discord/components/common/index"]["ClydeIcon"];
215
+
export const CopyIcon: MappedModules["discord/components/common/index"]["CopyIcon"];
216
+
export const DownloadIcon: MappedModules["discord/components/common/index"]["DownloadIcon"];
217
+
export const FullscreenEnterIcon: MappedModules["discord/components/common/index"]["FullscreenEnterIcon"];
218
+
export const GameControllerIcon: MappedModules["discord/components/common/index"]["GameControllerIcon"];
219
+
export const GlobeEarthIcon: MappedModules["discord/components/common/index"]["GlobeEarthIcon"];
220
+
export const HeartIcon: MappedModules["discord/components/common/index"]["HeartIcon"];
221
+
export const LinkIcon: MappedModules["discord/components/common/index"]["LinkIcon"];
222
+
export const MaximizeIcon: MappedModules["discord/components/common/index"]["MaximizeIcon"];
223
+
export const MinusIcon: MappedModules["discord/components/common/index"]["MinusIcon"];
224
+
export const MobilePhoneIcon: MappedModules["discord/components/common/index"]["MobilePhoneIcon"];
225
+
export const PauseIcon: MappedModules["discord/components/common/index"]["PauseIcon"];
226
+
export const PlayIcon: MappedModules["discord/components/common/index"]["PlayIcon"];
227
+
export const PlusLargeIcon: MappedModules["discord/components/common/index"]["PlusLargeIcon"];
228
+
export const RetryIcon: MappedModules["discord/components/common/index"]["RetryIcon"];
229
+
export const ScienceIcon: MappedModules["discord/components/common/index"]["ScienceIcon"];
230
+
export const ScreenIcon: MappedModules["discord/components/common/index"]["ScreenIcon"];
231
+
export const StarIcon: MappedModules["discord/components/common/index"]["StarIcon"];
232
+
export const TrashIcon: MappedModules["discord/components/common/index"]["TrashIcon"];
233
+
export const WarningIcon: MappedModules["discord/components/common/index"]["WarningIcon"];
234
+
export const WindowLaunchIcon: MappedModules["discord/components/common/index"]["WindowLaunchIcon"];
235
+
export const WindowTopOutlineIcon: MappedModules["discord/components/common/index"]["WindowTopOutlineIcon"];
236
+
export const XLargeIcon: MappedModules["discord/components/common/index"]["XLargeIcon"];
237
+
export const XSmallIcon: MappedModules["discord/components/common/index"]["XSmallIcon"];
238
+
export const ConfirmModal: MappedModules["discord/components/common/index"]["ConfirmModal"];
239
+
export const H: MappedModules["discord/components/common/index"]["H"];
240
+
export const HelpMessage: MappedModules["discord/components/common/index"]["HelpMessage"];
241
+
export const ModalCloseButton: MappedModules["discord/components/common/index"]["ModalCloseButton"];
242
+
export const ModalContent: MappedModules["discord/components/common/index"]["ModalContent"];
243
+
export const ModalFooter: MappedModules["discord/components/common/index"]["ModalFooter"];
244
+
export const ModalHeader: MappedModules["discord/components/common/index"]["ModalHeader"];
245
+
export const ModalRoot: MappedModules["discord/components/common/index"]["ModalRoot"];
246
+
export const NumberInputStepper: MappedModules["discord/components/common/index"]["NumberInputStepper"];
247
+
export const SearchableSelect: MappedModules["discord/components/common/index"]["SearchableSelect"];
248
+
export const createToast: MappedModules["discord/components/common/index"]["createToast"];
249
+
export const popToast: MappedModules["discord/components/common/index"]["popToast"];
250
+
export const showToast: MappedModules["discord/components/common/index"]["showToast"];
251
+
export const useThemeContext: MappedModules["discord/components/common/index"]["useThemeContext"];
252
+
export const AccessibilityAnnouncer: MappedModules["discord/components/common/index"]["AccessibilityAnnouncer"];
253
+
export const BackdropStyles: MappedModules["discord/components/common/index"]["BackdropStyles"];
254
+
export const BadgeShapes: MappedModules["discord/components/common/index"]["BadgeShapes"];
255
+
export const CardTypes: MappedModules["discord/components/common/index"]["CardTypes"];
256
+
export const CircleIconButtonColors: MappedModules["discord/components/common/index"]["CircleIconButtonColors"];
257
+
export const CircleIconButtonSizes: MappedModules["discord/components/common/index"]["CircleIconButtonSizes"];
258
+
export const FormErrorBlockColors: MappedModules["discord/components/common/index"]["FormErrorBlockColors"];
259
+
export const FormNoticeImagePositions: MappedModules["discord/components/common/index"]["FormNoticeImagePositions"];
260
+
export const FormTitleTags: MappedModules["discord/components/common/index"]["FormTitleTags"];
261
+
export const HelpMessageTypes: MappedModules["discord/components/common/index"]["HelpMessageTypes"];
262
+
export const ModalSize: MappedModules["discord/components/common/index"]["ModalSize"];
263
+
export const ModalTransitionState: MappedModules["discord/components/common/index"]["ModalTransitionState"];
264
+
export const PRETTY_KEYS: MappedModules["discord/components/common/index"]["PRETTY_KEYS"];
265
+
export const SelectLooks: MappedModules["discord/components/common/index"]["SelectLooks"];
266
+
export const SpinnerTypes: MappedModules["discord/components/common/index"]["SpinnerTypes"];
267
+
export const StatusTypes: MappedModules["discord/components/common/index"]["StatusTypes"];
268
+
export const ToastPosition: MappedModules["discord/components/common/index"]["ToastPosition"];
269
+
export const ToastType: MappedModules["discord/components/common/index"]["ToastType"];
270
+
export const TransitionStates: MappedModules["discord/components/common/index"]["TransitionStates"];
271
+
export const DEFAULT_MODAL_CONTEXT: MappedModules["discord/components/common/index"]["DEFAULT_MODAL_CONTEXT"];
272
+
export const LOW_SATURATION_THRESHOLD: MappedModules["discord/components/common/index"]["LOW_SATURATION_THRESHOLD"];
273
+
export const LayerClassName: MappedModules["discord/components/common/index"]["LayerClassName"];
274
+
export const POPOUT_MODAL_CONTEXT: MappedModules["discord/components/common/index"]["POPOUT_MODAL_CONTEXT"];
275
+
}
276
+
277
+
declare module "@moonlight-mod/wp/discord/components/modals/ConfirmModal" {
278
+
import { MappedModules } from "@moonlight-mod/mappings";
279
+
const _default: MappedModules["discord/components/modals/ConfirmModal"]["default"];
280
+
export default _default;
281
}
282
283
+
declare module "@moonlight-mod/wp/discord/lib/BaseRecord" {
284
import { MappedModules } from "@moonlight-mod/mappings";
285
+
const _default: MappedModules["discord/lib/BaseRecord"]["default"];
286
+
export default _default;
287
+
}
288
+
289
+
declare module "@moonlight-mod/wp/discord/lib/web/Storage" {
290
+
import { MappedModules } from "@moonlight-mod/mappings";
291
+
export const ObjectStorage: MappedModules["discord/lib/web/Storage"]["ObjectStorage"];
292
+
export const impl: MappedModules["discord/lib/web/Storage"]["impl"];
293
+
}
294
+
295
+
declare module "@moonlight-mod/wp/discord/modules/build_overrides/web/BuildOverride.css" {
296
+
import { MappedModules } from "@moonlight-mod/mappings";
297
+
export const wrapper: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["wrapper"];
298
+
export const titleRegion: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["titleRegion"];
299
+
export const title: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["title"];
300
+
export const infoIcon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["infoIcon"];
301
+
export const copyLink: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copyLink"];
302
+
export const copied: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copied"];
303
+
export const copyLinkIcon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copyLinkIcon"];
304
+
export const content: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["content"];
305
+
export const infoLink: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["infoLink"];
306
+
export const buildInfo: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buildInfo"];
307
+
export const button: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["button"];
308
+
export const buttonSize: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buttonSize"];
309
+
export const subHead: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["subHead"];
310
+
export const icon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["icon"];
311
+
export const buildDetails: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buildDetails"];
312
+
export const barLoader: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["barLoader"];
313
+
export const barTitle: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["barTitle"];
314
+
export const buttonLoader: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buttonLoader"];
315
+
export const disabledButtonOverride: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["disabledButtonOverride"];
316
+
}
317
+
318
+
declare module "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css" {
319
+
import { MappedModules } from "@moonlight-mod/mappings";
320
+
export const header: MappedModules["discord/modules/discovery/web/Discovery.css"]["header"];
321
+
export const headerImage: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImage"];
322
+
export const headerImageSimple: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImageSimple"];
323
+
export const headerImageBG: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImageBG"];
324
+
export const searchTitle: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchTitle"];
325
+
export const searchSubtitle: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchSubtitle"];
326
+
export const headerContentWrapper: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContentWrapper"];
327
+
export const headerContent: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContent"];
328
+
export const headerContentSmall: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContentSmall"];
329
+
export const searchBox: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchBox"];
330
+
export const searchBoxInput: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchBoxInput"];
331
+
export const closeIcon: MappedModules["discord/modules/discovery/web/Discovery.css"]["closeIcon"];
332
+
export const searchIcon: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchIcon"];
333
+
export const tabBar: MappedModules["discord/modules/discovery/web/Discovery.css"]["tabBar"];
334
+
export const tabBarItem: MappedModules["discord/modules/discovery/web/Discovery.css"]["tabBarItem"];
335
+
export const sectionHeader: MappedModules["discord/modules/discovery/web/Discovery.css"]["sectionHeader"];
336
+
}
337
+
338
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/Forums.css" {
339
+
import { MappedModules } from "@moonlight-mod/mappings";
340
+
export const container: MappedModules["discord/modules/forums/web/Forums.css"]["container"];
341
+
export const uploadArea: MappedModules["discord/modules/forums/web/Forums.css"]["uploadArea"];
342
+
export const label: MappedModules["discord/modules/forums/web/Forums.css"]["label"];
343
+
export const content: MappedModules["discord/modules/forums/web/Forums.css"]["content"];
344
+
export const noListContainer: MappedModules["discord/modules/forums/web/Forums.css"]["noListContainer"];
345
+
export const list: MappedModules["discord/modules/forums/web/Forums.css"]["list"];
346
+
export const grid: MappedModules["discord/modules/forums/web/Forums.css"]["grid"];
347
+
export const headerRow: MappedModules["discord/modules/forums/web/Forums.css"]["headerRow"];
348
+
export const card: MappedModules["discord/modules/forums/web/Forums.css"]["card"];
349
+
export const columnsSpan: MappedModules["discord/modules/forums/web/Forums.css"]["columnsSpan"];
350
+
export const emptyStateRow: MappedModules["discord/modules/forums/web/Forums.css"]["emptyStateRow"];
351
+
export const newMemberBanner: MappedModules["discord/modules/forums/web/Forums.css"]["newMemberBanner"];
352
+
export const gridViewBanner: MappedModules["discord/modules/forums/web/Forums.css"]["gridViewBanner"];
353
+
export const placeholder: MappedModules["discord/modules/forums/web/Forums.css"]["placeholder"];
354
+
export const mainCard: MappedModules["discord/modules/forums/web/Forums.css"]["mainCard"];
355
+
export const emptyMainCard: MappedModules["discord/modules/forums/web/Forums.css"]["emptyMainCard"];
356
+
export const outOfDate: MappedModules["discord/modules/forums/web/Forums.css"]["outOfDate"];
357
+
export const header: MappedModules["discord/modules/forums/web/Forums.css"]["header"];
358
+
export const matchingPostsRow: MappedModules["discord/modules/forums/web/Forums.css"]["matchingPostsRow"];
359
+
export const headerWithMatchingPosts: MappedModules["discord/modules/forums/web/Forums.css"]["headerWithMatchingPosts"];
360
+
export const noForm: MappedModules["discord/modules/forums/web/Forums.css"]["noForm"];
361
+
export const sortContainer: MappedModules["discord/modules/forums/web/Forums.css"]["sortContainer"];
362
+
export const sort: MappedModules["discord/modules/forums/web/Forums.css"]["sort"];
363
+
export const sortPopout: MappedModules["discord/modules/forums/web/Forums.css"]["sortPopout"];
364
+
export const archivedDividerRow: MappedModules["discord/modules/forums/web/Forums.css"]["archivedDividerRow"];
365
+
export const archivedDivider: MappedModules["discord/modules/forums/web/Forums.css"]["archivedDivider"];
366
+
export const newPostsButton: MappedModules["discord/modules/forums/web/Forums.css"]["newPostsButton"];
367
+
export const loadingCard: MappedModules["discord/modules/forums/web/Forums.css"]["loadingCard"];
368
+
export const enterIcon: MappedModules["discord/modules/forums/web/Forums.css"]["enterIcon"];
369
+
export const warnIcon: MappedModules["discord/modules/forums/web/Forums.css"]["warnIcon"];
370
+
export const searchIcon: MappedModules["discord/modules/forums/web/Forums.css"]["searchIcon"];
371
+
export const missingReadHistoryPermission: MappedModules["discord/modules/forums/web/Forums.css"]["missingReadHistoryPermission"];
372
+
export const divider: MappedModules["discord/modules/forums/web/Forums.css"]["divider"];
373
+
export const tagsContainer: MappedModules["discord/modules/forums/web/Forums.css"]["tagsContainer"];
374
+
export const filterIcon: MappedModules["discord/modules/forums/web/Forums.css"]["filterIcon"];
375
+
export const tagList: MappedModules["discord/modules/forums/web/Forums.css"]["tagList"];
376
+
export const tagListInner: MappedModules["discord/modules/forums/web/Forums.css"]["tagListInner"];
377
+
export const tag: MappedModules["discord/modules/forums/web/Forums.css"]["tag"];
378
+
export const tagsButton: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButton"];
379
+
export const tagsButtonInner: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonInner"];
380
+
export const tagsButtonPlaceholder: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonPlaceholder"];
381
+
export const tagsButtonWithCount: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonWithCount"];
382
+
export const sortDropdown: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdown"];
383
+
export const sortDropdownInner: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdownInner"];
384
+
export const sortDropdownText: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdownText"];
385
+
export const clear: MappedModules["discord/modules/forums/web/Forums.css"]["clear"];
386
+
export const matchingPosts: MappedModules["discord/modules/forums/web/Forums.css"]["matchingPosts"];
387
+
export const startPostHelp: MappedModules["discord/modules/forums/web/Forums.css"]["startPostHelp"];
388
+
export const tagsSpacer: MappedModules["discord/modules/forums/web/Forums.css"]["tagsSpacer"];
389
+
export const keyboardShortcut: MappedModules["discord/modules/forums/web/Forums.css"]["keyboardShortcut"];
390
+
export const key: MappedModules["discord/modules/forums/web/Forums.css"]["key"];
391
+
export const countContainer: MappedModules["discord/modules/forums/web/Forums.css"]["countContainer"];
392
+
export const countText: MappedModules["discord/modules/forums/web/Forums.css"]["countText"];
393
+
export const optInNotice: MappedModules["discord/modules/forums/web/Forums.css"]["optInNotice"];
394
+
}
395
+
396
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/Header.css" {
397
+
import { MappedModules } from "@moonlight-mod/mappings";
398
+
export const container: MappedModules["discord/modules/forums/web/Header.css"]["container"];
399
+
export const header: MappedModules["discord/modules/forums/web/Header.css"]["header"];
400
+
export const headerLeft: MappedModules["discord/modules/forums/web/Header.css"]["headerLeft"];
401
+
export const headerText: MappedModules["discord/modules/forums/web/Header.css"]["headerText"];
402
+
export const countContainer: MappedModules["discord/modules/forums/web/Header.css"]["countContainer"];
403
+
export const countText: MappedModules["discord/modules/forums/web/Header.css"]["countText"];
404
+
export const tagContainer: MappedModules["discord/modules/forums/web/Header.css"]["tagContainer"];
405
+
export const tag: MappedModules["discord/modules/forums/web/Header.css"]["tag"];
406
+
export const clear: MappedModules["discord/modules/forums/web/Header.css"]["clear"];
407
+
export const row: MappedModules["discord/modules/forums/web/Header.css"]["row"];
408
+
export const separator: MappedModules["discord/modules/forums/web/Header.css"]["separator"];
409
+
}
410
+
411
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/SortMenu.css" {
412
+
import { MappedModules } from "@moonlight-mod/mappings";
413
+
export const container: MappedModules["discord/modules/forums/web/SortMenu.css"]["container"];
414
+
export const clearText: MappedModules["discord/modules/forums/web/SortMenu.css"]["clearText"];
415
+
}
416
+
417
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/Tag" {
418
+
import { MappedModules } from "@moonlight-mod/mappings";
419
+
const _default: MappedModules["discord/modules/forums/web/Tag"]["default"];
420
+
export default _default;
421
+
export const TagBar: MappedModules["discord/modules/forums/web/Tag"]["TagBar"];
422
+
}
423
+
424
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css" {
425
+
import { MappedModules } from "@moonlight-mod/mappings";
426
+
export const addButton: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["addButton"];
427
+
export const container: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["container"];
428
+
export const emptyRowContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["emptyRowContainer"];
429
+
export const emptyRowText: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["emptyRowText"];
430
+
export const headerContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["headerContainer"];
431
+
export const list: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["list"];
432
+
export const memberDetails: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["memberDetails"];
433
+
export const memberRow: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["memberRow"];
434
+
export const removeButton: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButton"];
435
+
export const removeButtonContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButtonContainer"];
436
+
export const removeButtonDisabled: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButtonDisabled"];
437
+
export const removeTip: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeTip"];
438
+
export const searchContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["searchContainer"];
439
+
export const searchWarning: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["searchWarning"];
440
+
}
441
+
442
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCard.css" {
443
+
import { MappedModules } from "@moonlight-mod/mappings";
444
+
export const card: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["card"];
445
+
export const inModal: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["inModal"];
446
+
export const cardHeader: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["cardHeader"];
447
+
export const title: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["title"];
448
+
}
449
+
450
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCardItem.css" {
451
+
import { MappedModules } from "@moonlight-mod/mappings";
452
+
export const icon: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["icon"];
453
+
export const identifier: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["identifier"];
454
+
export const item: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["item"];
455
+
export const statusContainer: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusContainer"];
456
+
export const statusLine: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusLine"];
457
+
export const statusIcon: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusIcon"];
458
+
}
459
+
460
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/SearchSection.css" {
461
+
import { MappedModules } from "@moonlight-mod/mappings";
462
+
export const container: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["container"];
463
+
export const headerContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["headerContainer"];
464
+
export const searchContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["searchContainer"];
465
+
export const searchWarning: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["searchWarning"];
466
+
export const addButton: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["addButton"];
467
+
export const memberRow: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["memberRow"];
468
+
export const emptyRowContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["emptyRowContainer"];
469
+
export const emptyRowText: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["emptyRowText"];
470
+
export const memberDetails: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["memberDetails"];
471
+
export const list: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["list"];
472
+
export const removeButtonContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButtonContainer"];
473
+
export const removeButton: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButton"];
474
+
export const removeButtonDisabled: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButtonDisabled"];
475
+
export const removeTip: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeTip"];
476
+
}
477
+
478
+
declare module "@moonlight-mod/wp/discord/modules/guild_sidebar/web/CategoryChannel.css" {
479
+
import { MappedModules } from "@moonlight-mod/mappings";
480
+
export const containerDefault: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDefault"];
481
+
export const containerDragBefore: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDragBefore"];
482
+
export const containerDragAfter: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDragAfter"];
483
+
export const addButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["addButton"];
484
+
export const forceVisible: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["forceVisible"];
485
+
export const iconVisibility: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["iconVisibility"];
486
+
export const addButtonIcon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["addButtonIcon"];
487
+
export const wrapper: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["wrapper"];
488
+
export const wrapperStatic: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["wrapperStatic"];
489
+
export const clickable: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["clickable"];
490
+
export const children: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["children"];
491
+
export const mainContent: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["mainContent"];
492
+
export const icon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["icon"];
493
+
export const collapsed: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["collapsed"];
494
+
export const muted: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["muted"];
495
+
export const name: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["name"];
496
+
export const dismissWrapper: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismissWrapper"];
497
+
export const dismissButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismissButton"];
498
+
export const dismiss: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismiss"];
499
+
export const voiceChannelsButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["voiceChannelsButton"];
500
+
export const voiceChannelsToggleIcon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["voiceChannelsToggleIcon"];
501
+
export const refreshVoiceChannelsButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["refreshVoiceChannelsButton"];
502
+
export const refreshVoiceChannelsButtonInner: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["refreshVoiceChannelsButtonInner"];
503
}
504
505
declare module "@moonlight-mod/wp/discord/modules/markup/MarkupUtils" {
506
import { MappedModules } from "@moonlight-mod/mappings";
507
+
const _default: MappedModules["discord/modules/markup/MarkupUtils"]["default"];
508
+
export default _default;
509
+
}
510
+
511
+
declare module "@moonlight-mod/wp/discord/modules/menus/web/Menu" {
512
+
import { MappedModules } from "@moonlight-mod/mappings";
513
+
export const MenuSpinner: MappedModules["discord/modules/menus/web/Menu"]["MenuSpinner"];
514
+
export const Menu: MappedModules["discord/modules/menus/web/Menu"]["Menu"];
515
+
}
516
+
517
+
declare module "@moonlight-mod/wp/discord/modules/messages/web/Markup.css" {
518
+
import { MappedModules } from "@moonlight-mod/mappings";
519
+
export const markup: MappedModules["discord/modules/messages/web/Markup.css"]["markup"];
520
+
export const inlineFormat: MappedModules["discord/modules/messages/web/Markup.css"]["inlineFormat"];
521
+
export const codeContainer: MappedModules["discord/modules/messages/web/Markup.css"]["codeContainer"];
522
+
export const codeActions: MappedModules["discord/modules/messages/web/Markup.css"]["codeActions"];
523
+
export const blockquoteContainer: MappedModules["discord/modules/messages/web/Markup.css"]["blockquoteContainer"];
524
+
export const blockquoteDivider: MappedModules["discord/modules/messages/web/Markup.css"]["blockquoteDivider"];
525
+
export const slateBlockquoteContainer: MappedModules["discord/modules/messages/web/Markup.css"]["slateBlockquoteContainer"];
526
+
export const roleMention: MappedModules["discord/modules/messages/web/Markup.css"]["roleMention"];
527
+
export const rolePopout: MappedModules["discord/modules/messages/web/Markup.css"]["rolePopout"];
528
+
export const roleHeader: MappedModules["discord/modules/messages/web/Markup.css"]["roleHeader"];
529
+
export const roleScroller: MappedModules["discord/modules/messages/web/Markup.css"]["roleScroller"];
530
+
export const timestamp: MappedModules["discord/modules/messages/web/Markup.css"]["timestamp"];
531
+
export const timestampTooltip: MappedModules["discord/modules/messages/web/Markup.css"]["timestampTooltip"];
532
+
}
533
+
534
+
declare module "@moonlight-mod/wp/discord/modules/messages/web/Message.css" {
535
+
import { MappedModules } from "@moonlight-mod/mappings";
536
+
export const wrapper: MappedModules["discord/modules/messages/web/Message.css"]["wrapper"];
537
+
export const compact: MappedModules["discord/modules/messages/web/Message.css"]["compact"];
538
+
export const cozy: MappedModules["discord/modules/messages/web/Message.css"]["cozy"];
539
+
export const contentOnly: MappedModules["discord/modules/messages/web/Message.css"]["contentOnly"];
540
+
export const repliedMessage: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessage"];
541
+
export const threadMessageAccessory: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessory"];
542
+
export const executedCommand: MappedModules["discord/modules/messages/web/Message.css"]["executedCommand"];
543
+
export const latin12CompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["latin12CompactTimeStamp"];
544
+
export const latin24CompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["latin24CompactTimeStamp"];
545
+
export const asianCompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["asianCompactTimeStamp"];
546
+
export const contextCommandMessage: MappedModules["discord/modules/messages/web/Message.css"]["contextCommandMessage"];
547
+
export const messageSpine: MappedModules["discord/modules/messages/web/Message.css"]["messageSpine"];
548
+
export const repliedMessageClickableSpine: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageClickableSpine"];
549
+
export const repliedMessageContentHovered: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageContentHovered"];
550
+
export const threadMessageAccessoryAvatar: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryAvatar"];
551
+
export const replyAvatar: MappedModules["discord/modules/messages/web/Message.css"]["replyAvatar"];
552
+
export const replyBadge: MappedModules["discord/modules/messages/web/Message.css"]["replyBadge"];
553
+
export const executedCommandAvatar: MappedModules["discord/modules/messages/web/Message.css"]["executedCommandAvatar"];
554
+
export const replyChatIconContainer: MappedModules["discord/modules/messages/web/Message.css"]["replyChatIconContainer"];
555
+
export const replyIcon: MappedModules["discord/modules/messages/web/Message.css"]["replyIcon"];
556
+
export const clanTagChiplet: MappedModules["discord/modules/messages/web/Message.css"]["clanTagChiplet"];
557
+
export const userJoinSystemMessageIcon: MappedModules["discord/modules/messages/web/Message.css"]["userJoinSystemMessageIcon"];
558
+
export const ticketIcon: MappedModules["discord/modules/messages/web/Message.css"]["ticketIcon"];
559
+
export const commandIcon: MappedModules["discord/modules/messages/web/Message.css"]["commandIcon"];
560
+
export const username: MappedModules["discord/modules/messages/web/Message.css"]["username"];
561
+
export const roleDot: MappedModules["discord/modules/messages/web/Message.css"]["roleDot"];
562
+
export const commandName: MappedModules["discord/modules/messages/web/Message.css"]["commandName"];
563
+
export const appsIcon: MappedModules["discord/modules/messages/web/Message.css"]["appsIcon"];
564
+
export const appLauncherOnboardingCommandName: MappedModules["discord/modules/messages/web/Message.css"]["appLauncherOnboardingCommandName"];
565
+
export const targetUsername: MappedModules["discord/modules/messages/web/Message.css"]["targetUsername"];
566
+
export const executedCommandSeparator: MappedModules["discord/modules/messages/web/Message.css"]["executedCommandSeparator"];
567
+
export const repliedTextPreview: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextPreview"];
568
+
export const threadMessageAccessoryPreview: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryPreview"];
569
+
export const repliedTextContent: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContent"];
570
+
export const clickable: MappedModules["discord/modules/messages/web/Message.css"]["clickable"];
571
+
export const repliedMessageClickableSpineHovered: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageClickableSpineHovered"];
572
+
export const threadMessageAccessoryContent: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContent"];
573
+
export const repliedTextPlaceholder: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextPlaceholder"];
574
+
export const threadMessageAccessoryPlaceholder: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryPlaceholder"];
575
+
export const repliedTextContentTrailingIcon: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContentTrailingIcon"];
576
+
export const threadMessageAccessoryContentTrailingIcon: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContentTrailingIcon"];
577
+
export const repliedTextContentLeadingIcon: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContentLeadingIcon"];
578
+
export const threadMessageAccessoryContentLeadingIcon: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContentLeadingIcon"];
579
+
export const contents: MappedModules["discord/modules/messages/web/Message.css"]["contents"];
580
+
export const zalgo: MappedModules["discord/modules/messages/web/Message.css"]["zalgo"];
581
+
export const messageContent: MappedModules["discord/modules/messages/web/Message.css"]["messageContent"];
582
+
export const header: MappedModules["discord/modules/messages/web/Message.css"]["header"];
583
+
export const buttonContainer: MappedModules["discord/modules/messages/web/Message.css"]["buttonContainer"];
584
+
export const avatar: MappedModules["discord/modules/messages/web/Message.css"]["avatar"];
585
+
export const avatarDecoration: MappedModules["discord/modules/messages/web/Message.css"]["avatarDecoration"];
586
+
export const roleIcon: MappedModules["discord/modules/messages/web/Message.css"]["roleIcon"];
587
+
export const timestamp: MappedModules["discord/modules/messages/web/Message.css"]["timestamp"];
588
+
export const timestampInline: MappedModules["discord/modules/messages/web/Message.css"]["timestampInline"];
589
+
export const alt: MappedModules["discord/modules/messages/web/Message.css"]["alt"];
590
+
export const timestampTooltip: MappedModules["discord/modules/messages/web/Message.css"]["timestampTooltip"];
591
+
export const timestampVisibleOnHover: MappedModules["discord/modules/messages/web/Message.css"]["timestampVisibleOnHover"];
592
+
export const nitroAuthorBadgeTootip: MappedModules["discord/modules/messages/web/Message.css"]["nitroAuthorBadgeTootip"];
593
+
export const headerText: MappedModules["discord/modules/messages/web/Message.css"]["headerText"];
594
+
export const hasRoleIcon: MappedModules["discord/modules/messages/web/Message.css"]["hasRoleIcon"];
595
+
export const hasBadges: MappedModules["discord/modules/messages/web/Message.css"]["hasBadges"];
596
+
export const botTagCompact: MappedModules["discord/modules/messages/web/Message.css"]["botTagCompact"];
597
+
export const botTagCozy: MappedModules["discord/modules/messages/web/Message.css"]["botTagCozy"];
598
+
export const nitroBadgeSvg: MappedModules["discord/modules/messages/web/Message.css"]["nitroBadgeSvg"];
599
+
export const nitroAuthorBadgeContainer: MappedModules["discord/modules/messages/web/Message.css"]["nitroAuthorBadgeContainer"];
600
+
export const separator: MappedModules["discord/modules/messages/web/Message.css"]["separator"];
601
+
export const hasThread: MappedModules["discord/modules/messages/web/Message.css"]["hasThread"];
602
+
export const isSystemMessage: MappedModules["discord/modules/messages/web/Message.css"]["isSystemMessage"];
603
+
export const hasReply: MappedModules["discord/modules/messages/web/Message.css"]["hasReply"];
604
+
export const markupRtl: MappedModules["discord/modules/messages/web/Message.css"]["markupRtl"];
605
+
export const isSending: MappedModules["discord/modules/messages/web/Message.css"]["isSending"];
606
+
export const isFailed: MappedModules["discord/modules/messages/web/Message.css"]["isFailed"];
607
+
export const isUnsupported: MappedModules["discord/modules/messages/web/Message.css"]["isUnsupported"];
608
+
export const edited: MappedModules["discord/modules/messages/web/Message.css"]["edited"];
609
+
export const communicationDisabled: MappedModules["discord/modules/messages/web/Message.css"]["communicationDisabled"];
610
+
export const compactCommunicationDisabled: MappedModules["discord/modules/messages/web/Message.css"]["compactCommunicationDisabled"];
611
+
export const communicationDisabledOpacity: MappedModules["discord/modules/messages/web/Message.css"]["communicationDisabledOpacity"];
612
+
export const badgesContainer: MappedModules["discord/modules/messages/web/Message.css"]["badgesContainer"];
613
+
}
614
+
615
+
declare module "@moonlight-mod/wp/discord/modules/modals/Modals" {
616
+
import { MappedModules } from "@moonlight-mod/mappings";
617
+
export const closeAllModals: MappedModules["discord/modules/modals/Modals"]["closeAllModals"];
618
+
export const closeAllModalsForContext: MappedModules["discord/modules/modals/Modals"]["closeAllModalsForContext"];
619
+
export const closeModal: MappedModules["discord/modules/modals/Modals"]["closeModal"];
620
+
export const getInteractingModalContext: MappedModules["discord/modules/modals/Modals"]["getInteractingModalContext"];
621
+
export const hasAnyModalOpen: MappedModules["discord/modules/modals/Modals"]["hasAnyModalOpen"];
622
+
export const hasAnyModalOpenSelector: MappedModules["discord/modules/modals/Modals"]["hasAnyModalOpenSelector"];
623
+
export const hasModalOpen: MappedModules["discord/modules/modals/Modals"]["hasModalOpen"];
624
+
export const hasModalOpenSelector: MappedModules["discord/modules/modals/Modals"]["hasModalOpenSelector"];
625
+
export const openModal: MappedModules["discord/modules/modals/Modals"]["openModal"];
626
+
export const openModalLazy: MappedModules["discord/modules/modals/Modals"]["openModalLazy"];
627
+
export const updateModal: MappedModules["discord/modules/modals/Modals"]["updateModal"];
628
+
export const useHasAnyModalOpen: MappedModules["discord/modules/modals/Modals"]["useHasAnyModalOpen"];
629
+
export const useIsModalAtTop: MappedModules["discord/modules/modals/Modals"]["useIsModalAtTop"];
630
+
export const useModalsStore: MappedModules["discord/modules/modals/Modals"]["useModalsStore"];
631
+
}
632
+
633
+
declare module "@moonlight-mod/wp/discord/modules/oauth2/index" {
634
+
import { MappedModules } from "@moonlight-mod/mappings";
635
+
export const OAuth2AuthorizeModal: MappedModules["discord/modules/oauth2/index"]["OAuth2AuthorizeModal"];
636
+
export const OAuth2AuthorizePage: MappedModules["discord/modules/oauth2/index"]["OAuth2AuthorizePage"];
637
+
export const getOAuth2AuthorizeProps: MappedModules["discord/modules/oauth2/index"]["getOAuth2AuthorizeProps"];
638
+
export const openOAuth2Modal: MappedModules["discord/modules/oauth2/index"]["openOAuth2Modal"];
639
+
export const openOAuth2ModalWithCreateGuildModal: MappedModules["discord/modules/oauth2/index"]["openOAuth2ModalWithCreateGuildModal"];
640
+
export const useOAuth2AuthorizeForm: MappedModules["discord/modules/oauth2/index"]["useOAuth2AuthorizeForm"];
641
+
}
642
+
643
+
declare module "@moonlight-mod/wp/discord/modules/people/web/PeoplePage.css" {
644
+
import { MappedModules } from "@moonlight-mod/mappings";
645
+
export const addFriend: MappedModules["discord/modules/people/web/PeoplePage.css"]["addFriend"];
646
+
export const badge: MappedModules["discord/modules/people/web/PeoplePage.css"]["badge"];
647
+
export const container: MappedModules["discord/modules/people/web/PeoplePage.css"]["container"];
648
+
export const inviteToolbar: MappedModules["discord/modules/people/web/PeoplePage.css"]["inviteToolbar"];
649
+
export const item: MappedModules["discord/modules/people/web/PeoplePage.css"]["item"];
650
+
export const nowPlayingColumn: MappedModules["discord/modules/people/web/PeoplePage.css"]["nowPlayingColumn"];
651
+
export const peopleColumn: MappedModules["discord/modules/people/web/PeoplePage.css"]["peopleColumn"];
652
+
export const tabBar: MappedModules["discord/modules/people/web/PeoplePage.css"]["tabBar"];
653
+
export const tabBody: MappedModules["discord/modules/people/web/PeoplePage.css"]["tabBody"];
654
+
}
655
+
656
+
declare module "@moonlight-mod/wp/discord/modules/user_profile/web/BiteSizeActivity.css" {
657
+
import { MappedModules } from "@moonlight-mod/mappings";
658
+
export const header: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["header"];
659
+
export const headerTag: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["headerTag"];
660
+
export const body: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["body"];
661
+
export const footer: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["footer"];
662
+
export const backdrop: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["backdrop"];
663
+
export const toast: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["toast"];
664
+
export const activity: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["activity"];
665
+
export const upsell: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["upsell"];
666
}
667
668
declare module "@moonlight-mod/wp/discord/packages/flux" {
669
import { MappedModules } from "@moonlight-mod/mappings";
670
+
export const BatchedStoreListener: MappedModules["discord/packages/flux"]["BatchedStoreListener"];
671
+
export const Dispatcher: MappedModules["discord/packages/flux"]["Dispatcher"];
672
+
export const Store: MappedModules["discord/packages/flux"]["Store"];
673
+
const _default: MappedModules["discord/packages/flux"]["default"];
674
+
export default _default;
675
+
export const statesWillNeverBeEqual: MappedModules["discord/packages/flux"]["statesWillNeverBeEqual"];
676
+
export const useStateFromStores: MappedModules["discord/packages/flux"]["useStateFromStores"];
677
+
export const useStateFromStoresArray: MappedModules["discord/packages/flux"]["useStateFromStoresArray"];
678
+
export const useStateFromStoresObject: MappedModules["discord/packages/flux"]["useStateFromStoresObject"];
679
+
}
680
+
681
+
declare module "@moonlight-mod/wp/discord/packages/flux/BatchedStoreListener" {
682
+
import { MappedModules } from "@moonlight-mod/mappings";
683
+
const _default: MappedModules["discord/packages/flux/BatchedStoreListener"]["default"];
684
+
export default _default;
685
+
}
686
+
687
+
declare module "@moonlight-mod/wp/discord/packages/flux/ChangeListeners" {
688
+
import { MappedModules } from "@moonlight-mod/mappings";
689
+
const _default: MappedModules["discord/packages/flux/ChangeListeners"]["default"];
690
+
export default _default;
691
+
}
692
+
693
+
declare module "@moonlight-mod/wp/discord/packages/flux/Dispatcher" {
694
+
import { MappedModules } from "@moonlight-mod/mappings";
695
+
export const Dispatcher: MappedModules["discord/packages/flux/Dispatcher"]["Dispatcher"];
696
+
}
697
+
698
+
declare module "@moonlight-mod/wp/discord/packages/flux/Emitter" {
699
+
import { MappedModules } from "@moonlight-mod/mappings";
700
+
const _default: MappedModules["discord/packages/flux/Emitter"]["default"];
701
+
export default _default;
702
+
}
703
+
704
+
declare module "@moonlight-mod/wp/discord/packages/flux/LoggingUtils" {
705
+
import { MappedModules } from "@moonlight-mod/mappings";
706
+
const _default: MappedModules["discord/packages/flux/LoggingUtils"]["default"];
707
+
export default _default;
708
+
}
709
+
710
+
declare module "@moonlight-mod/wp/discord/packages/flux/PersistedStore" {
711
+
import { MappedModules } from "@moonlight-mod/mappings";
712
+
export const PersistedStore: MappedModules["discord/packages/flux/PersistedStore"]["PersistedStore"];
713
+
}
714
+
715
+
declare module "@moonlight-mod/wp/discord/packages/flux/Store" {
716
+
import { MappedModules } from "@moonlight-mod/mappings";
717
+
export const Store: MappedModules["discord/packages/flux/Store"]["Store"];
718
+
}
719
+
720
+
declare module "@moonlight-mod/wp/discord/packages/flux/connectStores" {
721
+
import { MappedModules } from "@moonlight-mod/mappings";
722
+
const _default: MappedModules["discord/packages/flux/connectStores"]["default"];
723
+
export default _default;
724
+
}
725
+
726
+
declare module "@moonlight-mod/wp/discord/records/UserRecord" {
727
+
import { MappedModules } from "@moonlight-mod/mappings";
728
+
const _default: MappedModules["discord/records/UserRecord"]["default"];
729
+
export default _default;
730
+
}
731
+
732
+
declare module "@moonlight-mod/wp/discord/styles/shared/Margins.css" {
733
+
import { MappedModules } from "@moonlight-mod/mappings";
734
+
export const marginReset: MappedModules["discord/styles/shared/Margins.css"]["marginReset"];
735
+
export const marginTop4: MappedModules["discord/styles/shared/Margins.css"]["marginTop4"];
736
+
export const marginBottom4: MappedModules["discord/styles/shared/Margins.css"]["marginBottom4"];
737
+
export const marginTop8: MappedModules["discord/styles/shared/Margins.css"]["marginTop8"];
738
+
export const marginBottom8: MappedModules["discord/styles/shared/Margins.css"]["marginBottom8"];
739
+
export const marginTop20: MappedModules["discord/styles/shared/Margins.css"]["marginTop20"];
740
+
export const marginBottom20: MappedModules["discord/styles/shared/Margins.css"]["marginBottom20"];
741
+
export const marginTop40: MappedModules["discord/styles/shared/Margins.css"]["marginTop40"];
742
+
export const marginBottom40: MappedModules["discord/styles/shared/Margins.css"]["marginBottom40"];
743
+
export const marginTop60: MappedModules["discord/styles/shared/Margins.css"]["marginTop60"];
744
+
export const marginBottom60: MappedModules["discord/styles/shared/Margins.css"]["marginBottom60"];
745
+
export const marginCenterHorz: MappedModules["discord/styles/shared/Margins.css"]["marginCenterHorz"];
746
+
export const marginLeft8: MappedModules["discord/styles/shared/Margins.css"]["marginLeft8"];
747
}
748
749
declare module "@moonlight-mod/wp/discord/uikit/Flex" {
750
import { MappedModules } from "@moonlight-mod/mappings";
751
+
const _default: MappedModules["discord/uikit/Flex"]["default"];
752
+
export default _default;
753
+
}
754
+
755
+
declare module "@moonlight-mod/wp/discord/utils/ClipboardUtils" {
756
+
import { MappedModules } from "@moonlight-mod/mappings";
757
+
export const SUPPORTS_COPY: MappedModules["discord/utils/ClipboardUtils"]["SUPPORTS_COPY"];
758
+
export const copy: MappedModules["discord/utils/ClipboardUtils"]["copy"];
759
+
}
760
+
761
+
declare module "@moonlight-mod/wp/discord/utils/ComponentDispatchUtils" {
762
+
import { MappedModules } from "@moonlight-mod/mappings";
763
+
export const ComponentDispatcher: MappedModules["discord/utils/ComponentDispatchUtils"]["ComponentDispatcher"];
764
+
export const ComponentDispatch: MappedModules["discord/utils/ComponentDispatchUtils"]["ComponentDispatch"];
765
+
}
766
+
767
+
declare module "@moonlight-mod/wp/discord/utils/HTTPUtils" {
768
+
import { MappedModules } from "@moonlight-mod/mappings";
769
+
export const HTTP: MappedModules["discord/utils/HTTPUtils"]["HTTP"];
770
+
}
771
+
772
+
declare module "@moonlight-mod/wp/discord/utils/MaskedLinkUtils" {
773
+
import { MappedModules } from "@moonlight-mod/mappings";
774
+
export const isLinkTrusted: MappedModules["discord/utils/MaskedLinkUtils"]["isLinkTrusted"];
775
+
export const handleClick: MappedModules["discord/utils/MaskedLinkUtils"]["handleClick"];
776
+
}
777
+
778
+
declare module "@moonlight-mod/wp/discord/utils/NativeUtils" {
779
+
import { MappedModules } from "@moonlight-mod/mappings";
780
+
const _default: MappedModules["discord/utils/NativeUtils"]["default"];
781
+
export default _default;
782
+
}
783
+
784
+
declare module "@moonlight-mod/wp/highlight.js" {
785
+
import { MappedModules } from "@moonlight-mod/mappings";
786
+
export const highlight: MappedModules["highlight.js"]["highlight"];
787
+
export const highlightAuto: MappedModules["highlight.js"]["highlightAuto"];
788
+
export const fixMarkup: MappedModules["highlight.js"]["fixMarkup"];
789
+
export const highlightBlock: MappedModules["highlight.js"]["highlightBlock"];
790
+
export const configure: MappedModules["highlight.js"]["configure"];
791
+
export const initHighlighting: MappedModules["highlight.js"]["initHighlighting"];
792
+
export const initHighlightingOnLoad: MappedModules["highlight.js"]["initHighlightingOnLoad"];
793
+
export const registerLanguage: MappedModules["highlight.js"]["registerLanguage"];
794
+
export const listLanguages: MappedModules["highlight.js"]["listLanguages"];
795
+
export const getLanguage: MappedModules["highlight.js"]["getLanguage"];
796
+
export const inherit: MappedModules["highlight.js"]["inherit"];
797
+
export const COMMENT: MappedModules["highlight.js"]["COMMENT"];
798
+
export const IDENT_RE: MappedModules["highlight.js"]["IDENT_RE"];
799
+
export const UNDERSCORE_IDENT_RE: MappedModules["highlight.js"]["UNDERSCORE_IDENT_RE"];
800
+
export const NUMBER_RE: MappedModules["highlight.js"]["NUMBER_RE"];
801
+
export const C_NUMBER_RE: MappedModules["highlight.js"]["C_NUMBER_RE"];
802
+
export const BINARY_NUMBER_RE: MappedModules["highlight.js"]["BINARY_NUMBER_RE"];
803
+
export const RE_STARTERS_RE: MappedModules["highlight.js"]["RE_STARTERS_RE"];
804
+
export const BACKSLASH_ESCAPE: MappedModules["highlight.js"]["BACKSLASH_ESCAPE"];
805
+
export const APOS_STRING_MODE: MappedModules["highlight.js"]["APOS_STRING_MODE"];
806
+
export const QUOTE_STRING_MODE: MappedModules["highlight.js"]["QUOTE_STRING_MODE"];
807
+
export const PHRASAL_WORDS_MODE: MappedModules["highlight.js"]["PHRASAL_WORDS_MODE"];
808
+
export const C_LINE_COMMENT_MODE: MappedModules["highlight.js"]["C_LINE_COMMENT_MODE"];
809
+
export const C_BLOCK_COMMENT_MODE: MappedModules["highlight.js"]["C_BLOCK_COMMENT_MODE"];
810
+
export const HASH_COMMENT_MODE: MappedModules["highlight.js"]["HASH_COMMENT_MODE"];
811
+
export const NUMBER_MODE: MappedModules["highlight.js"]["NUMBER_MODE"];
812
+
export const C_NUMBER_MODE: MappedModules["highlight.js"]["C_NUMBER_MODE"];
813
+
export const BINARY_NUMBER_MODE: MappedModules["highlight.js"]["BINARY_NUMBER_MODE"];
814
+
export const CSS_NUMBER_MODE: MappedModules["highlight.js"]["CSS_NUMBER_MODE"];
815
+
export const REGEX_MODE: MappedModules["highlight.js"]["REGEX_MODE"];
816
+
export const TITLE_MODE: MappedModules["highlight.js"]["TITLE_MODE"];
817
+
export const UNDERSCORE_TITLE_MODE: MappedModules["highlight.js"]["UNDERSCORE_TITLE_MODE"];
818
+
const _default: MappedModules["highlight.js"]["default"];
819
+
export default _default;
820
+
export const HighlightJS: MappedModules["highlight.js"]["HighlightJS"];
821
+
}
822
+
823
+
declare module "@moonlight-mod/wp/highlight.js/lib/core" {
824
+
import { MappedModules } from "@moonlight-mod/mappings";
825
+
export const highlight: MappedModules["highlight.js/lib/core"]["highlight"];
826
+
export const highlightAuto: MappedModules["highlight.js/lib/core"]["highlightAuto"];
827
+
export const fixMarkup: MappedModules["highlight.js/lib/core"]["fixMarkup"];
828
+
export const highlightBlock: MappedModules["highlight.js/lib/core"]["highlightBlock"];
829
+
export const configure: MappedModules["highlight.js/lib/core"]["configure"];
830
+
export const initHighlighting: MappedModules["highlight.js/lib/core"]["initHighlighting"];
831
+
export const initHighlightingOnLoad: MappedModules["highlight.js/lib/core"]["initHighlightingOnLoad"];
832
+
export const registerLanguage: MappedModules["highlight.js/lib/core"]["registerLanguage"];
833
+
export const listLanguages: MappedModules["highlight.js/lib/core"]["listLanguages"];
834
+
export const getLanguage: MappedModules["highlight.js/lib/core"]["getLanguage"];
835
+
export const inherit: MappedModules["highlight.js/lib/core"]["inherit"];
836
+
export const COMMENT: MappedModules["highlight.js/lib/core"]["COMMENT"];
837
+
export const IDENT_RE: MappedModules["highlight.js/lib/core"]["IDENT_RE"];
838
+
export const UNDERSCORE_IDENT_RE: MappedModules["highlight.js/lib/core"]["UNDERSCORE_IDENT_RE"];
839
+
export const NUMBER_RE: MappedModules["highlight.js/lib/core"]["NUMBER_RE"];
840
+
export const C_NUMBER_RE: MappedModules["highlight.js/lib/core"]["C_NUMBER_RE"];
841
+
export const BINARY_NUMBER_RE: MappedModules["highlight.js/lib/core"]["BINARY_NUMBER_RE"];
842
+
export const RE_STARTERS_RE: MappedModules["highlight.js/lib/core"]["RE_STARTERS_RE"];
843
+
export const BACKSLASH_ESCAPE: MappedModules["highlight.js/lib/core"]["BACKSLASH_ESCAPE"];
844
+
export const APOS_STRING_MODE: MappedModules["highlight.js/lib/core"]["APOS_STRING_MODE"];
845
+
export const QUOTE_STRING_MODE: MappedModules["highlight.js/lib/core"]["QUOTE_STRING_MODE"];
846
+
export const PHRASAL_WORDS_MODE: MappedModules["highlight.js/lib/core"]["PHRASAL_WORDS_MODE"];
847
+
export const C_LINE_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["C_LINE_COMMENT_MODE"];
848
+
export const C_BLOCK_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["C_BLOCK_COMMENT_MODE"];
849
+
export const HASH_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["HASH_COMMENT_MODE"];
850
+
export const NUMBER_MODE: MappedModules["highlight.js/lib/core"]["NUMBER_MODE"];
851
+
export const C_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["C_NUMBER_MODE"];
852
+
export const BINARY_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["BINARY_NUMBER_MODE"];
853
+
export const CSS_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["CSS_NUMBER_MODE"];
854
+
export const REGEX_MODE: MappedModules["highlight.js/lib/core"]["REGEX_MODE"];
855
+
export const TITLE_MODE: MappedModules["highlight.js/lib/core"]["TITLE_MODE"];
856
+
export const UNDERSCORE_TITLE_MODE: MappedModules["highlight.js/lib/core"]["UNDERSCORE_TITLE_MODE"];
857
+
}
858
+
859
+
declare module "@moonlight-mod/wp/lodash" {}
860
+
861
+
declare module "@moonlight-mod/wp/murmurhash" {
862
+
import { MappedModules } from "@moonlight-mod/mappings";
863
+
export const v2: MappedModules["murmurhash"]["v2"];
864
+
export const v3: MappedModules["murmurhash"]["v3"];
865
+
}
866
+
867
+
declare module "@moonlight-mod/wp/platform.js" {
868
+
import { MappedModules } from "@moonlight-mod/mappings";
869
+
export const description: MappedModules["platform.js"]["description"];
870
+
export const layout: MappedModules["platform.js"]["layout"];
871
+
export const manufacturer: MappedModules["platform.js"]["manufacturer"];
872
+
export const name: MappedModules["platform.js"]["name"];
873
+
export const prerelease: MappedModules["platform.js"]["prerelease"];
874
+
export const product: MappedModules["platform.js"]["product"];
875
+
export const ua: MappedModules["platform.js"]["ua"];
876
+
export const version: MappedModules["platform.js"]["version"];
877
+
export const os: MappedModules["platform.js"]["os"];
878
+
export const parse: MappedModules["platform.js"]["parse"];
879
+
export const toString: MappedModules["platform.js"]["toString"];
880
}
881
882
declare module "@moonlight-mod/wp/react" {
883
import { MappedModules } from "@moonlight-mod/mappings";
884
+
const _: Omit<MappedModules["react"], "__mappings_exportEquals">;
885
export = _;
886
}
887
+
888
+
declare module "@moonlight-mod/wp/uuid/v4" {}
+7
-7
packages/types/tsconfig.json
+7
-7
packages/types/tsconfig.json
···
1
{
2
"compilerOptions": {
3
-
"target": "es2016",
4
-
"module": "es6",
5
-
"esModuleInterop": true,
6
-
"forceConsistentCasingInFileNames": true,
7
-
"strict": true,
8
-
"moduleResolution": "bundler",
9
"jsx": "react",
10
-
"declaration": true
11
},
12
"include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"]
13
}
···
1
{
2
"compilerOptions": {
3
+
"target": "ES2016",
4
"jsx": "react",
5
+
"module": "ES6",
6
+
"moduleResolution": "bundler",
7
+
"strict": true,
8
+
"declaration": true,
9
+
"esModuleInterop": true,
10
+
"forceConsistentCasingInFileNames": true
11
},
12
"include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"]
13
}
+10
-3
packages/web-preload/package.json
+10
-3
packages/web-preload/package.json
···
2
"name": "@moonlight-mod/web-preload",
3
"private": true,
4
"main": "src/index.ts",
5
"dependencies": {
6
"@moonlight-mod/core": "workspace:*",
7
-
"@moonlight-mod/lunast": "^1.0.0",
8
-
"@moonlight-mod/mappings": "^1.0.2",
9
-
"@moonlight-mod/moonmap": "^1.0.2",
10
"@moonlight-mod/types": "workspace:*"
11
}
12
}
···
2
"name": "@moonlight-mod/web-preload",
3
"private": true,
4
"main": "src/index.ts",
5
+
"engineStrict": true,
6
+
"engines": {
7
+
"node": ">=22",
8
+
"pnpm": ">=10",
9
+
"npm": "pnpm",
10
+
"yarn": "pnpm"
11
+
},
12
"dependencies": {
13
"@moonlight-mod/core": "workspace:*",
14
+
"@moonlight-mod/lunast": "catalog:prod",
15
+
"@moonlight-mod/mappings": "catalog:prod",
16
+
"@moonlight-mod/moonmap": "catalog:prod",
17
"@moonlight-mod/types": "workspace:*"
18
}
19
}
+16
-19
packages/web-preload/src/index.ts
+16
-19
packages/web-preload/src/index.ts
···
1
import { loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
2
-
import {
3
-
installWebpackPatcher,
4
-
onModuleLoad,
5
-
registerPatch,
6
-
registerWebpackModule
7
-
} from "@moonlight-mod/core/patch";
8
import { constants, MoonlightBranch } from "@moonlight-mod/types";
9
import { installStyles } from "@moonlight-mod/core/styles";
10
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
···
12
import Moonmap from "@moonlight-mod/moonmap";
13
import loadMappings from "@moonlight-mod/mappings";
14
import { createEventEmitter } from "@moonlight-mod/core/util/event";
15
-
import { EventPayloads, EventType } from "@moonlight-mod/types/core/event";
16
17
async function load() {
18
initLogger(moonlightNode.config);
19
const logger = new Logger("web-preload");
20
21
window.moonlight = {
22
-
apiLevel: constants.apiLevel,
23
unpatched: new Set(),
24
pendingModules: new Set(),
25
enabledExtensions: new Set(),
26
-
events: createEventEmitter<EventType, EventPayloads>(),
27
patchingInternals: {
28
onModuleLoad,
29
registerPatch,
30
registerWebpackModule
31
},
32
33
version: MOONLIGHT_VERSION,
34
branch: MOONLIGHT_BRANCH as MoonlightBranch,
35
36
getConfig: moonlightNode.getConfig.bind(moonlightNode),
37
getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode),
38
getNatives: moonlightNode.getNatives.bind(moonlightNode),
39
getLogger(id) {
40
return new Logger(id);
41
},
42
lunast: new LunAST(),
43
moonmap: new Moonmap()
44
};
···
51
logger.error("Error setting up web-preload", e);
52
}
53
54
-
if (MOONLIGHT_ENV === "web-preload") {
55
-
window.addEventListener("DOMContentLoaded", () => {
56
-
installStyles();
57
-
});
58
} else {
59
-
installStyles();
60
}
61
}
62
63
-
if (MOONLIGHT_ENV === "web-preload") {
64
-
load();
65
-
} else {
66
-
window._moonlightBrowserLoad = load;
67
-
}
···
1
import { loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
2
+
import { installWebpackPatcher, onModuleLoad, registerPatch, registerWebpackModule } from "@moonlight-mod/core/patch";
3
import { constants, MoonlightBranch } from "@moonlight-mod/types";
4
import { installStyles } from "@moonlight-mod/core/styles";
5
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
···
7
import Moonmap from "@moonlight-mod/moonmap";
8
import loadMappings from "@moonlight-mod/mappings";
9
import { createEventEmitter } from "@moonlight-mod/core/util/event";
10
+
import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event";
11
12
async function load() {
13
+
delete window._moonlightWebLoad;
14
initLogger(moonlightNode.config);
15
const logger = new Logger("web-preload");
16
17
window.moonlight = {
18
+
patched: new Map(),
19
unpatched: new Set(),
20
pendingModules: new Set(),
21
enabledExtensions: new Set(),
22
+
23
+
events: createEventEmitter<WebEventType, WebEventPayloads>(),
24
patchingInternals: {
25
onModuleLoad,
26
registerPatch,
27
registerWebpackModule
28
},
29
+
localStorage: window.localStorage,
30
31
version: MOONLIGHT_VERSION,
32
branch: MOONLIGHT_BRANCH as MoonlightBranch,
33
+
apiLevel: constants.apiLevel,
34
35
getConfig: moonlightNode.getConfig.bind(moonlightNode),
36
getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode),
37
+
setConfigOption: moonlightNode.setConfigOption.bind(moonlightNode),
38
+
writeConfig: moonlightNode.writeConfig.bind(moonlightNode),
39
+
40
getNatives: moonlightNode.getNatives.bind(moonlightNode),
41
getLogger(id) {
42
return new Logger(id);
43
},
44
+
45
lunast: new LunAST(),
46
moonmap: new Moonmap()
47
};
···
54
logger.error("Error setting up web-preload", e);
55
}
56
57
+
if (document.readyState === "complete") {
58
+
installStyles();
59
} else {
60
+
window.addEventListener("load", installStyles);
61
}
62
}
63
64
+
window._moonlightWebLoad = load;
+4
-1
packages/web-preload/tsconfig.json
+4
-1
packages/web-preload/tsconfig.json
+1498
-1196
pnpm-lock.yaml
+1498
-1196
pnpm-lock.yaml
···
4
autoInstallPeers: true
5
excludeLinksFromLockfile: false
6
7
importers:
8
9
.:
10
devDependencies:
11
-
'@typescript-eslint/eslint-plugin':
12
-
specifier: ^6.13.2
13
-
version: 6.13.2(@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2))(eslint@8.55.0)(typescript@5.3.2)
14
-
'@typescript-eslint/parser':
15
-
specifier: ^6.13.2
16
-
version: 6.13.2(eslint@8.55.0)(typescript@5.3.2)
17
esbuild:
18
-
specifier: ^0.19.3
19
version: 0.19.3
20
esbuild-copy-static-files:
21
-
specifier: ^0.1.0
22
version: 0.1.0
23
eslint:
24
-
specifier: ^8.55.0
25
-
version: 8.55.0
26
-
eslint-config-prettier:
27
-
specifier: ^9.1.0
28
-
version: 9.1.0(eslint@8.55.0)
29
-
eslint-plugin-prettier:
30
-
specifier: ^5.0.1
31
-
version: 5.0.1(eslint-config-prettier@9.1.0(eslint@8.55.0))(eslint@8.55.0)(prettier@3.1.0)
32
-
eslint-plugin-react:
33
-
specifier: ^7.33.2
34
-
version: 7.33.2(eslint@8.55.0)
35
husky:
36
-
specifier: ^8.0.3
37
version: 8.0.3
38
prettier:
39
-
specifier: ^3.1.0
40
version: 3.1.0
41
typescript:
42
-
specifier: ^5.3.2
43
-
version: 5.3.2
44
45
packages/browser:
46
dependencies:
···
54
specifier: workspace:*
55
version: link:../web-preload
56
'@zenfs/core':
57
-
specifier: ^1.0.2
58
-
version: 1.0.2
59
'@zenfs/dom':
60
-
specifier: ^0.2.16
61
-
version: 0.2.16(@zenfs/core@1.0.2)
62
63
packages/core:
64
dependencies:
···
74
'@moonlight-mod/types':
75
specifier: workspace:*
76
version: link:../types
77
nanotar:
78
-
specifier: ^0.1.1
79
version: 0.1.1
80
81
packages/injector:
···
99
packages/types:
100
dependencies:
101
'@moonlight-mod/lunast':
102
-
specifier: ^1.0.0
103
-
version: 1.0.0
104
'@moonlight-mod/mappings':
105
-
specifier: ^1.0.2
106
-
version: 1.0.2(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2)
107
'@moonlight-mod/moonmap':
108
-
specifier: ^1.0.2
109
-
version: 1.0.2
110
'@types/react':
111
specifier: ^18.3.10
112
-
version: 18.3.10
113
csstype:
114
-
specifier: ^3.1.2
115
-
version: 3.1.2
116
standalone-electron-types:
117
specifier: ^1.0.0
118
version: 1.0.0
···
123
specifier: workspace:*
124
version: link:../core
125
'@moonlight-mod/lunast':
126
-
specifier: ^1.0.0
127
-
version: 1.0.0
128
'@moonlight-mod/mappings':
129
-
specifier: ^1.0.2
130
-
version: 1.0.2(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2)
131
'@moonlight-mod/moonmap':
132
-
specifier: ^1.0.2
133
-
version: 1.0.2
134
'@moonlight-mod/types':
135
specifier: workspace:*
136
version: link:../types
···
140
'@aashutoshrathi/word-wrap@1.2.6':
141
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
142
engines: {node: '>=0.10.0'}
143
144
'@esbuild/android-arm64@0.19.3':
145
resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==}
···
273
cpu: [x64]
274
os: [win32]
275
276
-
'@eslint-community/eslint-utils@4.4.0':
277
-
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
278
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
279
peerDependencies:
280
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
281
282
-
'@eslint-community/regexpp@4.10.0':
283
-
resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==}
284
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
285
286
-
'@eslint/eslintrc@2.1.4':
287
-
resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
288
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
289
290
-
'@eslint/js@8.55.0':
291
-
resolution: {integrity: sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==}
292
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
293
294
-
'@humanwhocodes/config-array@0.11.13':
295
-
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
296
-
engines: {node: '>=10.10.0'}
297
-
deprecated: Use @eslint/config-array instead
298
299
'@humanwhocodes/module-importer@1.0.1':
300
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
301
engines: {node: '>=12.22'}
302
303
-
'@humanwhocodes/object-schema@2.0.1':
304
-
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
305
-
deprecated: Use @eslint/object-schema instead
306
307
-
'@moonlight-mod/lunast@1.0.0':
308
-
resolution: {integrity: sha512-kJgf41K12i6/2LbXK97CNO+pNO7ADGh9N4bCQcOPwosocKMcwKHDEZUgPqeihNshY3c3AEW1LiyXjlsl24PdDw==}
309
310
-
'@moonlight-mod/mappings@1.0.2':
311
-
resolution: {integrity: sha512-PjIv4LFyt3j4LyGiokUmJ6a0L5JljoLXjUkixCynLLpNLd660qTcLe8f9tbhOovvD8joqejq+f5oqSo2V4/Vfg==}
312
peerDependencies:
313
-
'@moonlight-mod/lunast': ^1.0.0
314
-
'@moonlight-mod/moonmap': ^1.0.0
315
316
-
'@moonlight-mod/moonmap@1.0.2':
317
-
resolution: {integrity: sha512-dqMFwk8o0duRfvBNYo6EwalEUWWR3bNF5V2N04ogHp4gYON6/5+XOUrTlQ9BFBDj9anZwGgVwGqnxV42Qs9pPw==}
318
319
'@nodelib/fs.scandir@2.1.5':
320
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
···
328
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
329
engines: {node: '>= 8'}
330
331
-
'@pkgr/utils@2.4.2':
332
-
resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
333
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
334
335
'@types/estree-jsx@1.0.5':
336
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
337
338
'@types/estree@1.0.6':
339
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
340
341
'@types/fbemitter@2.0.35':
342
resolution: {integrity: sha512-Xem6d7qUfmouCHntCrRYgDBwbf+WWRd6G+7WEFlEZFZ67LZXiYRvT2LV8wcZa6mIaAil95+ABQdKgB6hPIsnng==}
343
344
'@types/flux@3.1.14':
345
resolution: {integrity: sha512-WRXN0kQPCnqxN0/PgNgc7WBF6c8rbSHsEep3/qBLpsQ824RONdOmTs0TV7XhIW2GDNRAHO2CqCgAFLR5PChosw==}
346
347
'@types/json-schema@7.0.15':
348
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
349
350
'@types/node@18.17.17':
351
resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==}
352
353
-
'@types/node@20.16.10':
354
-
resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==}
355
356
'@types/prop-types@15.7.13':
357
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
358
359
-
'@types/react@18.3.10':
360
-
resolution: {integrity: sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==}
361
362
-
'@types/readable-stream@4.0.15':
363
-
resolution: {integrity: sha512-oAZ3kw+kJFkEqyh7xORZOku1YAKvsFTogRY8kVl4vHpEKiDkfnSA/My8haRE7fvmix5Zyy+1pwzOi7yycGLBJw==}
364
-
365
-
'@types/semver@7.5.6':
366
-
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
367
-
368
-
'@typescript-eslint/eslint-plugin@6.13.2':
369
-
resolution: {integrity: sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==}
370
-
engines: {node: ^16.0.0 || >=18.0.0}
371
peerDependencies:
372
-
'@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
373
-
eslint: ^7.0.0 || ^8.0.0
374
-
typescript: '*'
375
-
peerDependenciesMeta:
376
-
typescript:
377
-
optional: true
378
379
-
'@typescript-eslint/parser@6.13.2':
380
-
resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==}
381
-
engines: {node: ^16.0.0 || >=18.0.0}
382
peerDependencies:
383
-
eslint: ^7.0.0 || ^8.0.0
384
-
typescript: '*'
385
-
peerDependenciesMeta:
386
-
typescript:
387
-
optional: true
388
389
-
'@typescript-eslint/scope-manager@6.13.2':
390
-
resolution: {integrity: sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==}
391
-
engines: {node: ^16.0.0 || >=18.0.0}
392
393
-
'@typescript-eslint/type-utils@6.13.2':
394
-
resolution: {integrity: sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==}
395
-
engines: {node: ^16.0.0 || >=18.0.0}
396
peerDependencies:
397
-
eslint: ^7.0.0 || ^8.0.0
398
-
typescript: '*'
399
-
peerDependenciesMeta:
400
-
typescript:
401
-
optional: true
402
403
-
'@typescript-eslint/types@6.13.2':
404
-
resolution: {integrity: sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==}
405
-
engines: {node: ^16.0.0 || >=18.0.0}
406
407
-
'@typescript-eslint/typescript-estree@6.13.2':
408
-
resolution: {integrity: sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==}
409
-
engines: {node: ^16.0.0 || >=18.0.0}
410
peerDependencies:
411
-
typescript: '*'
412
-
peerDependenciesMeta:
413
-
typescript:
414
-
optional: true
415
416
-
'@typescript-eslint/utils@6.13.2':
417
-
resolution: {integrity: sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==}
418
-
engines: {node: ^16.0.0 || >=18.0.0}
419
peerDependencies:
420
-
eslint: ^7.0.0 || ^8.0.0
421
422
-
'@typescript-eslint/visitor-keys@6.13.2':
423
-
resolution: {integrity: sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==}
424
-
engines: {node: ^16.0.0 || >=18.0.0}
425
426
-
'@ungap/structured-clone@1.2.0':
427
-
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
428
429
-
'@zenfs/core@1.0.2':
430
-
resolution: {integrity: sha512-LMTD4ntn6Ag1y+IeOSVykDDvYC12dsGFtsX8M/54OQrLs7v+YnX4bpo0o2osbm8XFmU2MTNMX/G3PLsvzgWzrg==}
431
-
engines: {node: '>= 16'}
432
hasBin: true
433
434
-
'@zenfs/dom@0.2.16':
435
-
resolution: {integrity: sha512-6Ev+ol9hZIgQECNZR+xxjQ/a99EhhrWeiQttm/+U7YJK3HdTjiKfU39DsfGeH64vSqhpa5Vj+LWRx75SHkjw0Q==}
436
engines: {node: '>= 18'}
437
peerDependencies:
438
-
'@zenfs/core': ^1.0.0
439
440
abort-controller@3.0.0:
441
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
···
446
peerDependencies:
447
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
448
449
-
acorn@8.12.1:
450
-
resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
451
engines: {node: '>=0.4.0'}
452
hasBin: true
453
454
ajv@6.12.6:
455
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
456
457
-
ansi-regex@5.0.1:
458
-
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
459
-
engines: {node: '>=8'}
460
-
461
ansi-styles@4.3.0:
462
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
463
engines: {node: '>=8'}
464
465
argparse@2.0.1:
466
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
467
468
-
array-buffer-byte-length@1.0.0:
469
-
resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
470
471
-
array-includes@3.1.7:
472
-
resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==}
473
engines: {node: '>= 0.4'}
474
475
-
array-union@2.1.0:
476
-
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
477
-
engines: {node: '>=8'}
478
479
-
array.prototype.flat@1.3.2:
480
-
resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
481
engines: {node: '>= 0.4'}
482
483
-
array.prototype.flatmap@1.3.2:
484
-
resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
485
engines: {node: '>= 0.4'}
486
487
-
array.prototype.tosorted@1.1.2:
488
-
resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==}
489
490
-
arraybuffer.prototype.slice@1.0.2:
491
-
resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==}
492
engines: {node: '>= 0.4'}
493
494
astring@1.9.0:
495
resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
496
hasBin: true
497
498
-
asynciterator.prototype@1.0.0:
499
-
resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==}
500
501
-
available-typed-arrays@1.0.5:
502
-
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
503
engines: {node: '>= 0.4'}
504
505
balanced-match@1.0.2:
···
508
base64-js@1.5.1:
509
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
510
511
-
big-integer@1.6.52:
512
-
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
513
-
engines: {node: '>=0.6'}
514
-
515
-
bplist-parser@0.2.0:
516
-
resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==}
517
-
engines: {node: '>= 5.10.0'}
518
-
519
brace-expansion@1.1.11:
520
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
521
522
brace-expansion@2.0.1:
523
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
524
525
-
braces@3.0.2:
526
-
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
527
engines: {node: '>=8'}
528
529
buffer@6.0.3:
530
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
531
532
-
bundle-name@3.0.0:
533
-
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
534
-
engines: {node: '>=12'}
535
536
-
call-bind@1.0.5:
537
-
resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
538
539
callsites@3.1.0:
540
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
···
554
concat-map@0.0.1:
555
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
556
557
-
cross-spawn@7.0.3:
558
-
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
559
engines: {node: '>= 8'}
560
561
-
csstype@3.1.2:
562
-
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
563
-
564
csstype@3.1.3:
565
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
566
567
-
debug@4.3.4:
568
-
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
569
engines: {node: '>=6.0'}
570
peerDependencies:
571
supports-color: '*'
···
576
deep-is@0.1.4:
577
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
578
579
-
default-browser-id@3.0.0:
580
-
resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==}
581
-
engines: {node: '>=12'}
582
-
583
-
default-browser@4.0.0:
584
-
resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==}
585
-
engines: {node: '>=14.16'}
586
-
587
-
define-data-property@1.1.1:
588
-
resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==}
589
engines: {node: '>= 0.4'}
590
591
-
define-lazy-prop@3.0.0:
592
-
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
593
-
engines: {node: '>=12'}
594
-
595
define-properties@1.2.1:
596
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
597
engines: {node: '>= 0.4'}
598
599
-
dir-glob@3.0.1:
600
-
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
601
-
engines: {node: '>=8'}
602
603
doctrine@2.1.0:
604
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
605
engines: {node: '>=0.10.0'}
606
607
-
doctrine@3.0.0:
608
-
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
609
-
engines: {node: '>=6.0.0'}
610
611
-
es-abstract@1.22.3:
612
-
resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==}
613
engines: {node: '>= 0.4'}
614
615
-
es-iterator-helpers@1.0.15:
616
-
resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==}
617
618
-
es-set-tostringtag@2.0.2:
619
-
resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==}
620
engines: {node: '>= 0.4'}
621
622
-
es-shim-unscopables@1.0.2:
623
-
resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
624
625
-
es-to-primitive@1.2.1:
626
-
resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
627
engines: {node: '>= 0.4'}
628
629
esbuild-copy-static-files@0.1.0:
···
644
peerDependencies:
645
eslint: '>=7.0.0'
646
647
-
eslint-plugin-prettier@5.0.1:
648
-
resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==}
649
engines: {node: ^14.18.0 || >=16.0.0}
650
peerDependencies:
651
'@types/eslint': '>=8.0.0'
652
eslint: '>=8.0.0'
653
-
eslint-config-prettier: '*'
654
prettier: '>=3.0.0'
655
peerDependenciesMeta:
656
'@types/eslint':
···
658
eslint-config-prettier:
659
optional: true
660
661
-
eslint-plugin-react@7.33.2:
662
-
resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==}
663
engines: {node: '>=4'}
664
peerDependencies:
665
-
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
666
667
-
eslint-scope@7.2.2:
668
-
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
669
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
670
671
eslint-visitor-keys@3.4.3:
672
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
673
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
674
675
-
eslint@8.55.0:
676
-
resolution: {integrity: sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==}
677
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
678
-
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
679
hasBin: true
680
681
-
espree@9.6.1:
682
-
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
683
-
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
684
685
-
esquery@1.5.0:
686
-
resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
687
engines: {node: '>=0.10'}
688
689
esrecurse@4.3.0:
···
712
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
713
engines: {node: '>=0.8.x'}
714
715
-
execa@5.1.1:
716
-
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
717
-
engines: {node: '>=10'}
718
-
719
-
execa@7.2.0:
720
-
resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
721
-
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
722
-
723
fast-deep-equal@3.1.3:
724
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
725
···
736
fast-levenshtein@2.0.6:
737
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
738
739
-
fastq@1.15.0:
740
-
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
741
742
-
file-entry-cache@6.0.1:
743
-
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
744
-
engines: {node: ^10.12.0 || >=12.0.0}
745
746
-
fill-range@7.0.1:
747
-
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
748
engines: {node: '>=8'}
749
750
find-up@5.0.0:
751
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
752
engines: {node: '>=10'}
753
754
-
flat-cache@3.2.0:
755
-
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
756
-
engines: {node: ^10.12.0 || >=12.0.0}
757
758
flatted@3.2.9:
759
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
760
761
-
for-each@0.3.3:
762
-
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
763
-
764
-
fs.realpath@1.0.0:
765
-
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
766
767
function-bind@1.1.2:
768
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
769
770
-
function.prototype.name@1.1.6:
771
-
resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
772
engines: {node: '>= 0.4'}
773
774
functions-have-names@1.2.3:
775
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
776
777
-
get-intrinsic@1.2.2:
778
-
resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==}
779
780
-
get-stream@6.0.1:
781
-
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
782
-
engines: {node: '>=10'}
783
784
-
get-symbol-description@1.0.0:
785
-
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
786
engines: {node: '>= 0.4'}
787
788
glob-parent@5.1.2:
···
793
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
794
engines: {node: '>=10.13.0'}
795
796
-
glob@7.2.3:
797
-
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
798
-
deprecated: Glob versions prior to v9 are no longer supported
799
800
-
globals@13.23.0:
801
-
resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==}
802
-
engines: {node: '>=8'}
803
-
804
-
globalthis@1.0.3:
805
-
resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
806
engines: {node: '>= 0.4'}
807
808
-
globby@11.1.0:
809
-
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
810
-
engines: {node: '>=10'}
811
-
812
-
gopd@1.0.1:
813
-
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
814
815
graphemer@1.4.0:
816
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
817
818
-
has-bigints@1.0.2:
819
-
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
820
821
has-flag@4.0.0:
822
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
823
engines: {node: '>=8'}
824
825
-
has-property-descriptors@1.0.1:
826
-
resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==}
827
828
-
has-proto@1.0.1:
829
-
resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
830
engines: {node: '>= 0.4'}
831
832
-
has-symbols@1.0.3:
833
-
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
834
engines: {node: '>= 0.4'}
835
836
-
has-tostringtag@1.0.0:
837
-
resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
838
engines: {node: '>= 0.4'}
839
840
-
hasown@2.0.0:
841
-
resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
842
engines: {node: '>= 0.4'}
843
844
-
human-signals@2.1.0:
845
-
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
846
-
engines: {node: '>=10.17.0'}
847
-
848
-
human-signals@4.3.1:
849
-
resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
850
-
engines: {node: '>=14.18.0'}
851
-
852
husky@8.0.3:
853
resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
854
engines: {node: '>=14'}
···
857
ieee754@1.2.1:
858
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
859
860
-
ignore@5.3.0:
861
-
resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==}
862
engines: {node: '>= 4'}
863
864
import-fresh@3.3.0:
···
869
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
870
engines: {node: '>=0.8.19'}
871
872
-
inflight@1.0.6:
873
-
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
874
-
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
875
-
876
-
inherits@2.0.4:
877
-
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
878
-
879
-
internal-slot@1.0.6:
880
-
resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==}
881
engines: {node: '>= 0.4'}
882
883
-
is-array-buffer@3.0.2:
884
-
resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
885
886
-
is-async-function@2.0.0:
887
-
resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==}
888
engines: {node: '>= 0.4'}
889
890
-
is-bigint@1.0.4:
891
-
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
892
893
-
is-boolean-object@1.1.2:
894
-
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
895
engines: {node: '>= 0.4'}
896
897
is-callable@1.2.7:
898
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
899
engines: {node: '>= 0.4'}
900
901
-
is-core-module@2.13.1:
902
-
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
903
904
-
is-date-object@1.0.5:
905
-
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
906
engines: {node: '>= 0.4'}
907
908
-
is-docker@2.2.1:
909
-
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
910
-
engines: {node: '>=8'}
911
-
hasBin: true
912
-
913
-
is-docker@3.0.0:
914
-
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
915
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
916
-
hasBin: true
917
918
is-extglob@2.1.1:
919
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
920
engines: {node: '>=0.10.0'}
921
922
-
is-finalizationregistry@1.0.2:
923
-
resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==}
924
925
-
is-generator-function@1.0.10:
926
-
resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
927
engines: {node: '>= 0.4'}
928
929
is-glob@4.0.3:
930
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
931
engines: {node: '>=0.10.0'}
932
933
-
is-inside-container@1.0.0:
934
-
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
935
-
engines: {node: '>=14.16'}
936
-
hasBin: true
937
-
938
-
is-map@2.0.2:
939
-
resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
940
-
941
-
is-negative-zero@2.0.2:
942
-
resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
943
engines: {node: '>= 0.4'}
944
945
-
is-number-object@1.0.7:
946
-
resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
947
engines: {node: '>= 0.4'}
948
949
is-number@7.0.0:
950
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
951
engines: {node: '>=0.12.0'}
952
953
-
is-path-inside@3.0.3:
954
-
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
955
-
engines: {node: '>=8'}
956
957
-
is-regex@1.1.4:
958
-
resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
959
engines: {node: '>= 0.4'}
960
961
-
is-set@2.0.2:
962
-
resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==}
963
964
-
is-shared-array-buffer@1.0.2:
965
-
resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
966
967
-
is-stream@2.0.1:
968
-
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
969
-
engines: {node: '>=8'}
970
971
-
is-stream@3.0.0:
972
-
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
973
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
974
975
-
is-string@1.0.7:
976
-
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
977
engines: {node: '>= 0.4'}
978
979
-
is-symbol@1.0.4:
980
-
resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
981
engines: {node: '>= 0.4'}
982
983
-
is-typed-array@1.1.12:
984
-
resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==}
985
engines: {node: '>= 0.4'}
986
987
-
is-weakmap@2.0.1:
988
-
resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==}
989
-
990
-
is-weakref@1.0.2:
991
-
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
992
-
993
-
is-weakset@2.0.2:
994
-
resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==}
995
-
996
-
is-wsl@2.2.0:
997
-
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
998
-
engines: {node: '>=8'}
999
-
1000
isarray@2.0.5:
1001
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
1002
1003
isexe@2.0.0:
1004
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
1005
1006
-
iterator.prototype@1.1.2:
1007
-
resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==}
1008
1009
js-tokens@4.0.0:
1010
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
···
1044
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
1045
hasBin: true
1046
1047
-
lru-cache@6.0.0:
1048
-
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
1049
-
engines: {node: '>=10'}
1050
-
1051
-
merge-stream@2.0.0:
1052
-
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
1053
1054
merge2@1.4.1:
1055
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
···
1059
resolution: {integrity: sha512-OyvYIOgpzXREySYJ1cqEb2pOKdeQMTfF9M8dRU6nC4hi/GXMmNpe9ssZCrSoTHazu05BSAoRBN/uYeco+ymfOg==}
1060
engines: {node: '>=18.0.0'}
1061
1062
-
micromatch@4.0.5:
1063
-
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
1064
-
engines: {node: '>=8.6'}
1065
1066
-
mimic-fn@2.1.0:
1067
-
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
1068
-
engines: {node: '>=6'}
1069
1070
-
mimic-fn@4.0.0:
1071
-
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
1072
-
engines: {node: '>=12'}
1073
1074
minimatch@3.1.2:
1075
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
···
1078
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
1079
engines: {node: '>=16 || 14 >=14.17'}
1080
1081
-
ms@2.1.2:
1082
-
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
1083
1084
nanotar@0.1.1:
1085
resolution: {integrity: sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ==}
···
1087
natural-compare@1.4.0:
1088
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
1089
1090
-
npm-run-path@4.0.1:
1091
-
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
1092
-
engines: {node: '>=8'}
1093
-
1094
-
npm-run-path@5.1.0:
1095
-
resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==}
1096
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
1097
1098
object-assign@4.1.1:
1099
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
1100
engines: {node: '>=0.10.0'}
1101
1102
-
object-inspect@1.13.1:
1103
-
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
1104
1105
object-keys@1.1.1:
1106
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
1107
engines: {node: '>= 0.4'}
1108
1109
-
object.assign@4.1.5:
1110
-
resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
1111
engines: {node: '>= 0.4'}
1112
1113
-
object.entries@1.1.7:
1114
-
resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==}
1115
engines: {node: '>= 0.4'}
1116
1117
-
object.fromentries@2.0.7:
1118
-
resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==}
1119
engines: {node: '>= 0.4'}
1120
1121
-
object.hasown@1.1.3:
1122
-
resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==}
1123
-
1124
-
object.values@1.1.7:
1125
-
resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==}
1126
engines: {node: '>= 0.4'}
1127
1128
-
once@1.4.0:
1129
-
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
1130
1131
-
onetime@5.1.2:
1132
-
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
1133
-
engines: {node: '>=6'}
1134
-
1135
-
onetime@6.0.0:
1136
-
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
1137
-
engines: {node: '>=12'}
1138
-
1139
-
open@9.1.0:
1140
-
resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==}
1141
-
engines: {node: '>=14.16'}
1142
1143
optionator@0.9.3:
1144
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
1145
engines: {node: '>= 0.8.0'}
1146
1147
p-limit@3.1.0:
1148
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
1149
engines: {node: '>=10'}
···
1152
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
1153
engines: {node: '>=10'}
1154
1155
parent-module@1.0.1:
1156
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
1157
engines: {node: '>=6'}
···
1159
path-exists@4.0.0:
1160
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
1161
engines: {node: '>=8'}
1162
-
1163
-
path-is-absolute@1.0.1:
1164
-
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
1165
-
engines: {node: '>=0.10.0'}
1166
1167
path-key@3.1.1:
1168
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
1169
engines: {node: '>=8'}
1170
1171
-
path-key@4.0.0:
1172
-
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
1173
-
engines: {node: '>=12'}
1174
-
1175
path-parse@1.0.7:
1176
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
1177
1178
-
path-type@4.0.0:
1179
-
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
1180
-
engines: {node: '>=8'}
1181
-
1182
-
picocolors@1.0.0:
1183
-
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
1184
1185
picomatch@2.3.1:
1186
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
1187
engines: {node: '>=8.6'}
1188
1189
prelude-ls@1.2.1:
1190
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
···
1209
punycode@2.3.1:
1210
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
1211
engines: {node: '>=6'}
1212
1213
queue-microtask@1.2.3:
1214
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
···
1220
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
1221
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1222
1223
-
reflect.getprototypeof@1.0.4:
1224
-
resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==}
1225
engines: {node: '>= 0.4'}
1226
1227
-
regexp.prototype.flags@1.5.1:
1228
-
resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==}
1229
engines: {node: '>= 0.4'}
1230
1231
resolve-from@4.0.0:
···
1236
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
1237
hasBin: true
1238
1239
reusify@1.0.4:
1240
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
1241
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
1242
-
1243
-
rimraf@3.0.2:
1244
-
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
1245
-
deprecated: Rimraf versions prior to v4 are no longer supported
1246
-
hasBin: true
1247
-
1248
-
run-applescript@5.0.0:
1249
-
resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==}
1250
-
engines: {node: '>=12'}
1251
1252
run-parallel@1.2.0:
1253
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
1254
1255
-
safe-array-concat@1.0.1:
1256
-
resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==}
1257
engines: {node: '>=0.4'}
1258
1259
-
safe-buffer@5.1.2:
1260
-
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
1261
-
1262
safe-buffer@5.2.1:
1263
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
1264
1265
-
safe-regex-test@1.0.0:
1266
-
resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
1267
1268
semver@6.3.1:
1269
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
1270
hasBin: true
1271
1272
-
semver@7.5.4:
1273
-
resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
1274
engines: {node: '>=10'}
1275
hasBin: true
1276
1277
-
set-function-length@1.1.1:
1278
-
resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==}
1279
engines: {node: '>= 0.4'}
1280
1281
-
set-function-name@2.0.1:
1282
-
resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==}
1283
engines: {node: '>= 0.4'}
1284
1285
shebang-command@2.0.0:
···
1290
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
1291
engines: {node: '>=8'}
1292
1293
-
side-channel@1.0.4:
1294
-
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
1295
1296
-
signal-exit@3.0.7:
1297
-
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
1298
1299
-
slash@3.0.0:
1300
-
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
1301
-
engines: {node: '>=8'}
1302
1303
standalone-electron-types@1.0.0:
1304
resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==}
1305
1306
-
string.prototype.matchall@4.0.10:
1307
-
resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==}
1308
1309
-
string.prototype.trim@1.2.8:
1310
-
resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
1311
engines: {node: '>= 0.4'}
1312
1313
-
string.prototype.trimend@1.0.7:
1314
-
resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==}
1315
1316
-
string.prototype.trimstart@1.0.7:
1317
-
resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==}
1318
1319
string_decoder@1.3.0:
1320
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
1321
1322
-
strip-ansi@6.0.1:
1323
-
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
1324
-
engines: {node: '>=8'}
1325
-
1326
-
strip-final-newline@2.0.0:
1327
-
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
1328
-
engines: {node: '>=6'}
1329
-
1330
-
strip-final-newline@3.0.0:
1331
-
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
1332
-
engines: {node: '>=12'}
1333
-
1334
strip-json-comments@3.1.1:
1335
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
1336
engines: {node: '>=8'}
···
1343
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
1344
engines: {node: '>= 0.4'}
1345
1346
-
synckit@0.8.6:
1347
-
resolution: {integrity: sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==}
1348
engines: {node: ^14.18.0 || >=16.0.0}
1349
1350
-
text-table@0.2.0:
1351
-
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
1352
1353
-
titleize@3.0.0:
1354
-
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
1355
-
engines: {node: '>=12'}
1356
1357
to-regex-range@5.0.1:
1358
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
1359
engines: {node: '>=8.0'}
1360
1361
-
ts-api-utils@1.0.3:
1362
-
resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==}
1363
-
engines: {node: '>=16.13.0'}
1364
peerDependencies:
1365
-
typescript: '>=4.2.0'
1366
1367
-
tslib@2.6.2:
1368
-
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
1369
1370
type-check@0.4.0:
1371
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
1372
engines: {node: '>= 0.8.0'}
1373
1374
-
type-fest@0.20.2:
1375
-
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
1376
-
engines: {node: '>=10'}
1377
1378
-
typed-array-buffer@1.0.0:
1379
-
resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==}
1380
engines: {node: '>= 0.4'}
1381
1382
-
typed-array-byte-length@1.0.0:
1383
-
resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==}
1384
engines: {node: '>= 0.4'}
1385
1386
-
typed-array-byte-offset@1.0.0:
1387
-
resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==}
1388
engines: {node: '>= 0.4'}
1389
1390
-
typed-array-length@1.0.4:
1391
-
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
1392
1393
-
typescript@5.3.2:
1394
-
resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==}
1395
engines: {node: '>=14.17'}
1396
hasBin: true
1397
1398
-
unbox-primitive@1.0.2:
1399
-
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
1400
1401
-
undici-types@6.19.8:
1402
-
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
1403
1404
-
untildify@4.0.0:
1405
-
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
1406
-
engines: {node: '>=8'}
1407
1408
uri-js@4.4.1:
1409
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
1410
1411
-
utilium@0.7.1:
1412
-
resolution: {integrity: sha512-2ocvTkI7U8LERmwxL0LhFUvEfN66UqcjF6tMiURvUwSyU7U1QC9gST+3iSUSiGccFfnP3f2EXwHNXOnOzx+lAg==}
1413
1414
-
which-boxed-primitive@1.0.2:
1415
-
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
1416
1417
-
which-builtin-type@1.1.3:
1418
-
resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==}
1419
engines: {node: '>= 0.4'}
1420
1421
-
which-collection@1.0.1:
1422
-
resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==}
1423
1424
-
which-typed-array@1.1.13:
1425
-
resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==}
1426
engines: {node: '>= 0.4'}
1427
1428
which@2.0.2:
···
1430
engines: {node: '>= 8'}
1431
hasBin: true
1432
1433
-
wrappy@1.0.2:
1434
-
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
1435
-
1436
-
yallist@4.0.0:
1437
-
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
1438
1439
yocto-queue@0.1.0:
1440
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
1441
engines: {node: '>=10'}
1442
1443
snapshots:
1444
1445
'@aashutoshrathi/word-wrap@1.2.6': {}
1446
1447
'@esbuild/android-arm64@0.19.3':
1448
optional: true
···
1510
'@esbuild/win32-x64@0.19.3':
1511
optional: true
1512
1513
-
'@eslint-community/eslint-utils@4.4.0(eslint@8.55.0)':
1514
dependencies:
1515
-
eslint: 8.55.0
1516
eslint-visitor-keys: 3.4.3
1517
1518
-
'@eslint-community/regexpp@4.10.0': {}
1519
1520
-
'@eslint/eslintrc@2.1.4':
1521
dependencies:
1522
ajv: 6.12.6
1523
-
debug: 4.3.4
1524
-
espree: 9.6.1
1525
-
globals: 13.23.0
1526
-
ignore: 5.3.0
1527
import-fresh: 3.3.0
1528
js-yaml: 4.1.0
1529
minimatch: 3.1.2
···
1531
transitivePeerDependencies:
1532
- supports-color
1533
1534
-
'@eslint/js@8.55.0': {}
1535
1536
-
'@humanwhocodes/config-array@0.11.13':
1537
dependencies:
1538
-
'@humanwhocodes/object-schema': 2.0.1
1539
-
debug: 4.3.4
1540
-
minimatch: 3.1.2
1541
-
transitivePeerDependencies:
1542
-
- supports-color
1543
1544
'@humanwhocodes/module-importer@1.0.1': {}
1545
1546
-
'@humanwhocodes/object-schema@2.0.1': {}
1547
1548
-
'@moonlight-mod/lunast@1.0.0':
1549
dependencies:
1550
astring: 1.9.0
1551
estree-toolkit: 1.7.8
1552
meriyah: 6.0.1
1553
1554
-
'@moonlight-mod/mappings@1.0.2(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2)':
1555
dependencies:
1556
-
'@moonlight-mod/lunast': 1.0.0
1557
-
'@moonlight-mod/moonmap': 1.0.2
1558
'@types/flux': 3.1.14
1559
-
'@types/react': 18.3.10
1560
csstype: 3.1.3
1561
1562
-
'@moonlight-mod/moonmap@1.0.2': {}
1563
1564
'@nodelib/fs.scandir@2.1.5':
1565
dependencies:
···
1571
'@nodelib/fs.walk@1.2.8':
1572
dependencies:
1573
'@nodelib/fs.scandir': 2.1.5
1574
-
fastq: 1.15.0
1575
1576
-
'@pkgr/utils@2.4.2':
1577
dependencies:
1578
-
cross-spawn: 7.0.3
1579
-
fast-glob: 3.3.2
1580
-
is-glob: 4.0.3
1581
-
open: 9.1.0
1582
-
picocolors: 1.0.0
1583
-
tslib: 2.6.2
1584
1585
'@types/estree-jsx@1.0.5':
1586
dependencies:
···
1588
1589
'@types/estree@1.0.6': {}
1590
1591
'@types/fbemitter@2.0.35': {}
1592
1593
'@types/flux@3.1.14':
1594
dependencies:
1595
'@types/fbemitter': 2.0.35
1596
-
'@types/react': 18.3.10
1597
1598
'@types/json-schema@7.0.15': {}
1599
1600
'@types/node@18.17.17': {}
1601
1602
-
'@types/node@20.16.10':
1603
dependencies:
1604
-
undici-types: 6.19.8
1605
1606
'@types/prop-types@15.7.13': {}
1607
1608
-
'@types/react@18.3.10':
1609
dependencies:
1610
'@types/prop-types': 15.7.13
1611
csstype: 3.1.3
1612
1613
-
'@types/readable-stream@4.0.15':
1614
dependencies:
1615
-
'@types/node': 20.16.10
1616
-
safe-buffer: 5.1.2
1617
-
1618
-
'@types/semver@7.5.6': {}
1619
-
1620
-
'@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2))(eslint@8.55.0)(typescript@5.3.2)':
1621
-
dependencies:
1622
-
'@eslint-community/regexpp': 4.10.0
1623
-
'@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
1624
-
'@typescript-eslint/scope-manager': 6.13.2
1625
-
'@typescript-eslint/type-utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
1626
-
'@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
1627
-
'@typescript-eslint/visitor-keys': 6.13.2
1628
-
debug: 4.3.4
1629
-
eslint: 8.55.0
1630
graphemer: 1.4.0
1631
-
ignore: 5.3.0
1632
natural-compare: 1.4.0
1633
-
semver: 7.5.4
1634
-
ts-api-utils: 1.0.3(typescript@5.3.2)
1635
-
optionalDependencies:
1636
-
typescript: 5.3.2
1637
transitivePeerDependencies:
1638
- supports-color
1639
1640
-
'@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2)':
1641
dependencies:
1642
-
'@typescript-eslint/scope-manager': 6.13.2
1643
-
'@typescript-eslint/types': 6.13.2
1644
-
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
1645
-
'@typescript-eslint/visitor-keys': 6.13.2
1646
-
debug: 4.3.4
1647
-
eslint: 8.55.0
1648
-
optionalDependencies:
1649
-
typescript: 5.3.2
1650
transitivePeerDependencies:
1651
- supports-color
1652
1653
-
'@typescript-eslint/scope-manager@6.13.2':
1654
dependencies:
1655
-
'@typescript-eslint/types': 6.13.2
1656
-
'@typescript-eslint/visitor-keys': 6.13.2
1657
1658
-
'@typescript-eslint/type-utils@6.13.2(eslint@8.55.0)(typescript@5.3.2)':
1659
dependencies:
1660
-
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
1661
-
'@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2)
1662
-
debug: 4.3.4
1663
-
eslint: 8.55.0
1664
-
ts-api-utils: 1.0.3(typescript@5.3.2)
1665
-
optionalDependencies:
1666
-
typescript: 5.3.2
1667
transitivePeerDependencies:
1668
- supports-color
1669
1670
-
'@typescript-eslint/types@6.13.2': {}
1671
1672
-
'@typescript-eslint/typescript-estree@6.13.2(typescript@5.3.2)':
1673
dependencies:
1674
-
'@typescript-eslint/types': 6.13.2
1675
-
'@typescript-eslint/visitor-keys': 6.13.2
1676
-
debug: 4.3.4
1677
-
globby: 11.1.0
1678
is-glob: 4.0.3
1679
-
semver: 7.5.4
1680
-
ts-api-utils: 1.0.3(typescript@5.3.2)
1681
-
optionalDependencies:
1682
-
typescript: 5.3.2
1683
transitivePeerDependencies:
1684
- supports-color
1685
1686
-
'@typescript-eslint/utils@6.13.2(eslint@8.55.0)(typescript@5.3.2)':
1687
dependencies:
1688
-
'@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0)
1689
-
'@types/json-schema': 7.0.15
1690
-
'@types/semver': 7.5.6
1691
-
'@typescript-eslint/scope-manager': 6.13.2
1692
-
'@typescript-eslint/types': 6.13.2
1693
-
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
1694
-
eslint: 8.55.0
1695
-
semver: 7.5.4
1696
transitivePeerDependencies:
1697
- supports-color
1698
-
- typescript
1699
1700
-
'@typescript-eslint/visitor-keys@6.13.2':
1701
dependencies:
1702
-
'@typescript-eslint/types': 6.13.2
1703
-
eslint-visitor-keys: 3.4.3
1704
1705
-
'@ungap/structured-clone@1.2.0': {}
1706
1707
-
'@zenfs/core@1.0.2':
1708
dependencies:
1709
-
'@types/node': 20.16.10
1710
-
'@types/readable-stream': 4.0.15
1711
buffer: 6.0.3
1712
eventemitter3: 5.0.1
1713
-
minimatch: 9.0.5
1714
readable-stream: 4.5.2
1715
-
utilium: 0.7.1
1716
1717
-
'@zenfs/dom@0.2.16(@zenfs/core@1.0.2)':
1718
dependencies:
1719
-
'@zenfs/core': 1.0.2
1720
1721
abort-controller@3.0.0:
1722
dependencies:
1723
event-target-shim: 5.0.1
1724
1725
-
acorn-jsx@5.3.2(acorn@8.12.1):
1726
dependencies:
1727
-
acorn: 8.12.1
1728
1729
-
acorn@8.12.1: {}
1730
1731
ajv@6.12.6:
1732
dependencies:
···
1734
fast-json-stable-stringify: 2.1.0
1735
json-schema-traverse: 0.4.1
1736
uri-js: 4.4.1
1737
-
1738
-
ansi-regex@5.0.1: {}
1739
1740
ansi-styles@4.3.0:
1741
dependencies:
1742
color-convert: 2.0.1
1743
1744
argparse@2.0.1: {}
1745
1746
-
array-buffer-byte-length@1.0.0:
1747
dependencies:
1748
-
call-bind: 1.0.5
1749
-
is-array-buffer: 3.0.2
1750
1751
-
array-includes@3.1.7:
1752
dependencies:
1753
-
call-bind: 1.0.5
1754
define-properties: 1.2.1
1755
-
es-abstract: 1.22.3
1756
-
get-intrinsic: 1.2.2
1757
-
is-string: 1.0.7
1758
1759
-
array-union@2.1.0: {}
1760
1761
-
array.prototype.flat@1.3.2:
1762
dependencies:
1763
-
call-bind: 1.0.5
1764
define-properties: 1.2.1
1765
-
es-abstract: 1.22.3
1766
-
es-shim-unscopables: 1.0.2
1767
1768
-
array.prototype.flatmap@1.3.2:
1769
dependencies:
1770
-
call-bind: 1.0.5
1771
define-properties: 1.2.1
1772
-
es-abstract: 1.22.3
1773
-
es-shim-unscopables: 1.0.2
1774
1775
-
array.prototype.tosorted@1.1.2:
1776
dependencies:
1777
-
call-bind: 1.0.5
1778
define-properties: 1.2.1
1779
-
es-abstract: 1.22.3
1780
-
es-shim-unscopables: 1.0.2
1781
-
get-intrinsic: 1.2.2
1782
1783
-
arraybuffer.prototype.slice@1.0.2:
1784
dependencies:
1785
-
array-buffer-byte-length: 1.0.0
1786
-
call-bind: 1.0.5
1787
define-properties: 1.2.1
1788
-
es-abstract: 1.22.3
1789
-
get-intrinsic: 1.2.2
1790
-
is-array-buffer: 3.0.2
1791
-
is-shared-array-buffer: 1.0.2
1792
1793
astring@1.9.0: {}
1794
1795
-
asynciterator.prototype@1.0.0:
1796
-
dependencies:
1797
-
has-symbols: 1.0.3
1798
1799
-
available-typed-arrays@1.0.5: {}
1800
1801
balanced-match@1.0.2: {}
1802
1803
base64-js@1.5.1: {}
1804
1805
-
big-integer@1.6.52: {}
1806
-
1807
-
bplist-parser@0.2.0:
1808
-
dependencies:
1809
-
big-integer: 1.6.52
1810
-
1811
brace-expansion@1.1.11:
1812
dependencies:
1813
balanced-match: 1.0.2
···
1817
dependencies:
1818
balanced-match: 1.0.2
1819
1820
-
braces@3.0.2:
1821
dependencies:
1822
-
fill-range: 7.0.1
1823
1824
buffer@6.0.3:
1825
dependencies:
1826
base64-js: 1.5.1
1827
ieee754: 1.2.1
1828
1829
-
bundle-name@3.0.0:
1830
-
dependencies:
1831
-
run-applescript: 5.0.0
1832
1833
-
call-bind@1.0.5:
1834
dependencies:
1835
function-bind: 1.1.2
1836
-
get-intrinsic: 1.2.2
1837
-
set-function-length: 1.1.1
1838
1839
callsites@3.1.0: {}
1840
···
1851
1852
concat-map@0.0.1: {}
1853
1854
-
cross-spawn@7.0.3:
1855
dependencies:
1856
path-key: 3.1.1
1857
shebang-command: 2.0.0
1858
which: 2.0.2
1859
1860
-
csstype@3.1.2: {}
1861
-
1862
csstype@3.1.3: {}
1863
1864
-
debug@4.3.4:
1865
dependencies:
1866
-
ms: 2.1.2
1867
-
1868
-
deep-is@0.1.4: {}
1869
1870
-
default-browser-id@3.0.0:
1871
dependencies:
1872
-
bplist-parser: 0.2.0
1873
-
untildify: 4.0.0
1874
1875
-
default-browser@4.0.0:
1876
dependencies:
1877
-
bundle-name: 3.0.0
1878
-
default-browser-id: 3.0.0
1879
-
execa: 7.2.0
1880
-
titleize: 3.0.0
1881
1882
-
define-data-property@1.1.1:
1883
dependencies:
1884
-
get-intrinsic: 1.2.2
1885
-
gopd: 1.0.1
1886
-
has-property-descriptors: 1.0.1
1887
1888
-
define-lazy-prop@3.0.0: {}
1889
1890
define-properties@1.2.1:
1891
dependencies:
1892
-
define-data-property: 1.1.1
1893
-
has-property-descriptors: 1.0.1
1894
object-keys: 1.1.1
1895
1896
-
dir-glob@3.0.1:
1897
-
dependencies:
1898
-
path-type: 4.0.0
1899
1900
doctrine@2.1.0:
1901
dependencies:
1902
esutils: 2.0.3
1903
1904
-
doctrine@3.0.0:
1905
dependencies:
1906
-
esutils: 2.0.3
1907
1908
-
es-abstract@1.22.3:
1909
dependencies:
1910
-
array-buffer-byte-length: 1.0.0
1911
-
arraybuffer.prototype.slice: 1.0.2
1912
-
available-typed-arrays: 1.0.5
1913
-
call-bind: 1.0.5
1914
-
es-set-tostringtag: 2.0.2
1915
-
es-to-primitive: 1.2.1
1916
-
function.prototype.name: 1.1.6
1917
-
get-intrinsic: 1.2.2
1918
-
get-symbol-description: 1.0.0
1919
-
globalthis: 1.0.3
1920
-
gopd: 1.0.1
1921
-
has-property-descriptors: 1.0.1
1922
-
has-proto: 1.0.1
1923
-
has-symbols: 1.0.3
1924
-
hasown: 2.0.0
1925
-
internal-slot: 1.0.6
1926
-
is-array-buffer: 3.0.2
1927
is-callable: 1.2.7
1928
-
is-negative-zero: 2.0.2
1929
-
is-regex: 1.1.4
1930
-
is-shared-array-buffer: 1.0.2
1931
-
is-string: 1.0.7
1932
-
is-typed-array: 1.1.12
1933
-
is-weakref: 1.0.2
1934
-
object-inspect: 1.13.1
1935
object-keys: 1.1.1
1936
-
object.assign: 4.1.5
1937
-
regexp.prototype.flags: 1.5.1
1938
-
safe-array-concat: 1.0.1
1939
-
safe-regex-test: 1.0.0
1940
-
string.prototype.trim: 1.2.8
1941
-
string.prototype.trimend: 1.0.7
1942
-
string.prototype.trimstart: 1.0.7
1943
-
typed-array-buffer: 1.0.0
1944
-
typed-array-byte-length: 1.0.0
1945
-
typed-array-byte-offset: 1.0.0
1946
-
typed-array-length: 1.0.4
1947
-
unbox-primitive: 1.0.2
1948
-
which-typed-array: 1.1.13
1949
1950
-
es-iterator-helpers@1.0.15:
1951
dependencies:
1952
-
asynciterator.prototype: 1.0.0
1953
-
call-bind: 1.0.5
1954
define-properties: 1.2.1
1955
-
es-abstract: 1.22.3
1956
-
es-set-tostringtag: 2.0.2
1957
function-bind: 1.1.2
1958
-
get-intrinsic: 1.2.2
1959
-
globalthis: 1.0.3
1960
-
has-property-descriptors: 1.0.1
1961
-
has-proto: 1.0.1
1962
-
has-symbols: 1.0.3
1963
-
internal-slot: 1.0.6
1964
-
iterator.prototype: 1.1.2
1965
-
safe-array-concat: 1.0.1
1966
1967
-
es-set-tostringtag@2.0.2:
1968
dependencies:
1969
-
get-intrinsic: 1.2.2
1970
-
has-tostringtag: 1.0.0
1971
-
hasown: 2.0.0
1972
1973
-
es-shim-unscopables@1.0.2:
1974
dependencies:
1975
-
hasown: 2.0.0
1976
1977
-
es-to-primitive@1.2.1:
1978
dependencies:
1979
is-callable: 1.2.7
1980
-
is-date-object: 1.0.5
1981
-
is-symbol: 1.0.4
1982
1983
esbuild-copy-static-files@0.1.0: {}
1984
···
2009
2010
escape-string-regexp@4.0.0: {}
2011
2012
-
eslint-config-prettier@9.1.0(eslint@8.55.0):
2013
dependencies:
2014
-
eslint: 8.55.0
2015
2016
-
eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.1.0(eslint@8.55.0))(eslint@8.55.0)(prettier@3.1.0):
2017
dependencies:
2018
-
eslint: 8.55.0
2019
prettier: 3.1.0
2020
prettier-linter-helpers: 1.0.0
2021
-
synckit: 0.8.6
2022
optionalDependencies:
2023
-
eslint-config-prettier: 9.1.0(eslint@8.55.0)
2024
2025
-
eslint-plugin-react@7.33.2(eslint@8.55.0):
2026
dependencies:
2027
-
array-includes: 3.1.7
2028
-
array.prototype.flatmap: 1.3.2
2029
-
array.prototype.tosorted: 1.1.2
2030
doctrine: 2.1.0
2031
-
es-iterator-helpers: 1.0.15
2032
-
eslint: 8.55.0
2033
estraverse: 5.3.0
2034
jsx-ast-utils: 3.3.5
2035
minimatch: 3.1.2
2036
-
object.entries: 1.1.7
2037
-
object.fromentries: 2.0.7
2038
-
object.hasown: 1.1.3
2039
-
object.values: 1.1.7
2040
prop-types: 15.8.1
2041
resolve: 2.0.0-next.5
2042
semver: 6.3.1
2043
-
string.prototype.matchall: 4.0.10
2044
2045
-
eslint-scope@7.2.2:
2046
dependencies:
2047
esrecurse: 4.3.0
2048
estraverse: 5.3.0
2049
2050
eslint-visitor-keys@3.4.3: {}
2051
2052
-
eslint@8.55.0:
2053
dependencies:
2054
-
'@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0)
2055
-
'@eslint-community/regexpp': 4.10.0
2056
-
'@eslint/eslintrc': 2.1.4
2057
-
'@eslint/js': 8.55.0
2058
-
'@humanwhocodes/config-array': 0.11.13
2059
'@humanwhocodes/module-importer': 1.0.1
2060
-
'@nodelib/fs.walk': 1.2.8
2061
-
'@ungap/structured-clone': 1.2.0
2062
ajv: 6.12.6
2063
chalk: 4.1.2
2064
-
cross-spawn: 7.0.3
2065
-
debug: 4.3.4
2066
-
doctrine: 3.0.0
2067
escape-string-regexp: 4.0.0
2068
-
eslint-scope: 7.2.2
2069
-
eslint-visitor-keys: 3.4.3
2070
-
espree: 9.6.1
2071
-
esquery: 1.5.0
2072
esutils: 2.0.3
2073
fast-deep-equal: 3.1.3
2074
-
file-entry-cache: 6.0.1
2075
find-up: 5.0.0
2076
glob-parent: 6.0.2
2077
-
globals: 13.23.0
2078
-
graphemer: 1.4.0
2079
-
ignore: 5.3.0
2080
imurmurhash: 0.1.4
2081
is-glob: 4.0.3
2082
-
is-path-inside: 3.0.3
2083
-
js-yaml: 4.1.0
2084
json-stable-stringify-without-jsonify: 1.0.1
2085
-
levn: 0.4.1
2086
lodash.merge: 4.6.2
2087
minimatch: 3.1.2
2088
natural-compare: 1.4.0
2089
optionator: 0.9.3
2090
-
strip-ansi: 6.0.1
2091
-
text-table: 0.2.0
2092
transitivePeerDependencies:
2093
- supports-color
2094
2095
-
espree@9.6.1:
2096
dependencies:
2097
-
acorn: 8.12.1
2098
-
acorn-jsx: 5.3.2(acorn@8.12.1)
2099
-
eslint-visitor-keys: 3.4.3
2100
2101
-
esquery@1.5.0:
2102
dependencies:
2103
estraverse: 5.3.0
2104
···
2121
2122
events@3.3.0: {}
2123
2124
-
execa@5.1.1:
2125
-
dependencies:
2126
-
cross-spawn: 7.0.3
2127
-
get-stream: 6.0.1
2128
-
human-signals: 2.1.0
2129
-
is-stream: 2.0.1
2130
-
merge-stream: 2.0.0
2131
-
npm-run-path: 4.0.1
2132
-
onetime: 5.1.2
2133
-
signal-exit: 3.0.7
2134
-
strip-final-newline: 2.0.0
2135
-
2136
-
execa@7.2.0:
2137
-
dependencies:
2138
-
cross-spawn: 7.0.3
2139
-
get-stream: 6.0.1
2140
-
human-signals: 4.3.1
2141
-
is-stream: 3.0.0
2142
-
merge-stream: 2.0.0
2143
-
npm-run-path: 5.1.0
2144
-
onetime: 6.0.0
2145
-
signal-exit: 3.0.7
2146
-
strip-final-newline: 3.0.0
2147
-
2148
fast-deep-equal@3.1.3: {}
2149
2150
fast-diff@1.3.0: {}
···
2155
'@nodelib/fs.walk': 1.2.8
2156
glob-parent: 5.1.2
2157
merge2: 1.4.1
2158
-
micromatch: 4.0.5
2159
2160
fast-json-stable-stringify@2.1.0: {}
2161
2162
fast-levenshtein@2.0.6: {}
2163
2164
-
fastq@1.15.0:
2165
dependencies:
2166
reusify: 1.0.4
2167
2168
-
file-entry-cache@6.0.1:
2169
dependencies:
2170
-
flat-cache: 3.2.0
2171
2172
-
fill-range@7.0.1:
2173
dependencies:
2174
to-regex-range: 5.0.1
2175
2176
find-up@5.0.0:
2177
dependencies:
2178
locate-path: 6.0.0
2179
path-exists: 4.0.0
2180
2181
-
flat-cache@3.2.0:
2182
dependencies:
2183
flatted: 3.2.9
2184
keyv: 4.5.4
2185
-
rimraf: 3.0.2
2186
2187
flatted@3.2.9: {}
2188
2189
-
for-each@0.3.3:
2190
dependencies:
2191
is-callable: 1.2.7
2192
2193
-
fs.realpath@1.0.0: {}
2194
-
2195
function-bind@1.1.2: {}
2196
2197
-
function.prototype.name@1.1.6:
2198
dependencies:
2199
-
call-bind: 1.0.5
2200
define-properties: 1.2.1
2201
-
es-abstract: 1.22.3
2202
functions-have-names: 1.2.3
2203
2204
functions-have-names@1.2.3: {}
2205
2206
-
get-intrinsic@1.2.2:
2207
dependencies:
2208
function-bind: 1.1.2
2209
-
has-proto: 1.0.1
2210
-
has-symbols: 1.0.3
2211
-
hasown: 2.0.0
2212
2213
-
get-stream@6.0.1: {}
2214
2215
-
get-symbol-description@1.0.0:
2216
dependencies:
2217
-
call-bind: 1.0.5
2218
-
get-intrinsic: 1.2.2
2219
2220
glob-parent@5.1.2:
2221
dependencies:
···
2225
dependencies:
2226
is-glob: 4.0.3
2227
2228
-
glob@7.2.3:
2229
-
dependencies:
2230
-
fs.realpath: 1.0.0
2231
-
inflight: 1.0.6
2232
-
inherits: 2.0.4
2233
-
minimatch: 3.1.2
2234
-
once: 1.4.0
2235
-
path-is-absolute: 1.0.1
2236
2237
-
globals@13.23.0:
2238
-
dependencies:
2239
-
type-fest: 0.20.2
2240
-
2241
-
globalthis@1.0.3:
2242
dependencies:
2243
define-properties: 1.2.1
2244
-
2245
-
globby@11.1.0:
2246
-
dependencies:
2247
-
array-union: 2.1.0
2248
-
dir-glob: 3.0.1
2249
-
fast-glob: 3.3.2
2250
-
ignore: 5.3.0
2251
-
merge2: 1.4.1
2252
-
slash: 3.0.0
2253
2254
-
gopd@1.0.1:
2255
-
dependencies:
2256
-
get-intrinsic: 1.2.2
2257
2258
graphemer@1.4.0: {}
2259
2260
-
has-bigints@1.0.2: {}
2261
2262
has-flag@4.0.0: {}
2263
2264
-
has-property-descriptors@1.0.1:
2265
dependencies:
2266
-
get-intrinsic: 1.2.2
2267
2268
-
has-proto@1.0.1: {}
2269
2270
-
has-symbols@1.0.3: {}
2271
2272
-
has-tostringtag@1.0.0:
2273
dependencies:
2274
-
has-symbols: 1.0.3
2275
2276
-
hasown@2.0.0:
2277
dependencies:
2278
function-bind: 1.1.2
2279
2280
-
human-signals@2.1.0: {}
2281
-
2282
-
human-signals@4.3.1: {}
2283
-
2284
husky@8.0.3: {}
2285
2286
ieee754@1.2.1: {}
2287
2288
-
ignore@5.3.0: {}
2289
2290
import-fresh@3.3.0:
2291
dependencies:
···
2294
2295
imurmurhash@0.1.4: {}
2296
2297
-
inflight@1.0.6:
2298
dependencies:
2299
-
once: 1.4.0
2300
-
wrappy: 1.0.2
2301
2302
-
inherits@2.0.4: {}
2303
-
2304
-
internal-slot@1.0.6:
2305
dependencies:
2306
-
get-intrinsic: 1.2.2
2307
-
hasown: 2.0.0
2308
-
side-channel: 1.0.4
2309
-
2310
-
is-array-buffer@3.0.2:
2311
-
dependencies:
2312
-
call-bind: 1.0.5
2313
-
get-intrinsic: 1.2.2
2314
-
is-typed-array: 1.1.12
2315
2316
-
is-async-function@2.0.0:
2317
dependencies:
2318
-
has-tostringtag: 1.0.0
2319
2320
-
is-bigint@1.0.4:
2321
dependencies:
2322
-
has-bigints: 1.0.2
2323
2324
-
is-boolean-object@1.1.2:
2325
dependencies:
2326
-
call-bind: 1.0.5
2327
-
has-tostringtag: 1.0.0
2328
2329
is-callable@1.2.7: {}
2330
2331
-
is-core-module@2.13.1:
2332
dependencies:
2333
-
hasown: 2.0.0
2334
2335
-
is-date-object@1.0.5:
2336
dependencies:
2337
-
has-tostringtag: 1.0.0
2338
2339
-
is-docker@2.2.1: {}
2340
-
2341
-
is-docker@3.0.0: {}
2342
2343
is-extglob@2.1.1: {}
2344
2345
-
is-finalizationregistry@1.0.2:
2346
dependencies:
2347
-
call-bind: 1.0.5
2348
2349
-
is-generator-function@1.0.10:
2350
dependencies:
2351
-
has-tostringtag: 1.0.0
2352
2353
is-glob@4.0.3:
2354
dependencies:
2355
is-extglob: 2.1.1
2356
2357
-
is-inside-container@1.0.0:
2358
-
dependencies:
2359
-
is-docker: 3.0.0
2360
2361
-
is-map@2.0.2: {}
2362
-
2363
-
is-negative-zero@2.0.2: {}
2364
-
2365
-
is-number-object@1.0.7:
2366
dependencies:
2367
-
has-tostringtag: 1.0.0
2368
2369
is-number@7.0.0: {}
2370
2371
-
is-path-inside@3.0.3: {}
2372
-
2373
-
is-regex@1.1.4:
2374
dependencies:
2375
-
call-bind: 1.0.5
2376
-
has-tostringtag: 1.0.0
2377
2378
-
is-set@2.0.2: {}
2379
2380
-
is-shared-array-buffer@1.0.2:
2381
dependencies:
2382
-
call-bind: 1.0.5
2383
-
2384
-
is-stream@2.0.1: {}
2385
-
2386
-
is-stream@3.0.0: {}
2387
2388
-
is-string@1.0.7:
2389
dependencies:
2390
-
has-tostringtag: 1.0.0
2391
2392
-
is-symbol@1.0.4:
2393
dependencies:
2394
-
has-symbols: 1.0.3
2395
2396
-
is-typed-array@1.1.12:
2397
dependencies:
2398
-
which-typed-array: 1.1.13
2399
2400
-
is-weakmap@2.0.1: {}
2401
2402
-
is-weakref@1.0.2:
2403
dependencies:
2404
-
call-bind: 1.0.5
2405
2406
-
is-weakset@2.0.2:
2407
dependencies:
2408
-
call-bind: 1.0.5
2409
-
get-intrinsic: 1.2.2
2410
-
2411
-
is-wsl@2.2.0:
2412
-
dependencies:
2413
-
is-docker: 2.2.1
2414
2415
isarray@2.0.5: {}
2416
2417
isexe@2.0.0: {}
2418
2419
-
iterator.prototype@1.1.2:
2420
dependencies:
2421
-
define-properties: 1.2.1
2422
-
get-intrinsic: 1.2.2
2423
-
has-symbols: 1.0.3
2424
-
reflect.getprototypeof: 1.0.4
2425
-
set-function-name: 2.0.1
2426
2427
js-tokens@4.0.0: {}
2428
···
2438
2439
jsx-ast-utils@3.3.5:
2440
dependencies:
2441
-
array-includes: 3.1.7
2442
-
array.prototype.flat: 1.3.2
2443
-
object.assign: 4.1.5
2444
-
object.values: 1.1.7
2445
2446
keyv@4.5.4:
2447
dependencies:
···
2462
dependencies:
2463
js-tokens: 4.0.0
2464
2465
-
lru-cache@6.0.0:
2466
-
dependencies:
2467
-
yallist: 4.0.0
2468
-
2469
-
merge-stream@2.0.0: {}
2470
2471
merge2@1.4.1: {}
2472
2473
meriyah@6.0.1: {}
2474
2475
-
micromatch@4.0.5:
2476
dependencies:
2477
-
braces: 3.0.2
2478
picomatch: 2.3.1
2479
2480
-
mimic-fn@2.1.0: {}
2481
-
2482
-
mimic-fn@4.0.0: {}
2483
2484
minimatch@3.1.2:
2485
dependencies:
···
2489
dependencies:
2490
brace-expansion: 2.0.1
2491
2492
-
ms@2.1.2: {}
2493
2494
nanotar@0.1.1: {}
2495
2496
natural-compare@1.4.0: {}
2497
2498
-
npm-run-path@4.0.1:
2499
-
dependencies:
2500
-
path-key: 3.1.1
2501
-
2502
-
npm-run-path@5.1.0:
2503
-
dependencies:
2504
-
path-key: 4.0.0
2505
2506
object-assign@4.1.1: {}
2507
2508
-
object-inspect@1.13.1: {}
2509
2510
object-keys@1.1.1: {}
2511
2512
-
object.assign@4.1.5:
2513
dependencies:
2514
-
call-bind: 1.0.5
2515
define-properties: 1.2.1
2516
-
has-symbols: 1.0.3
2517
object-keys: 1.1.1
2518
2519
-
object.entries@1.1.7:
2520
dependencies:
2521
-
call-bind: 1.0.5
2522
define-properties: 1.2.1
2523
-
es-abstract: 1.22.3
2524
2525
-
object.fromentries@2.0.7:
2526
dependencies:
2527
-
call-bind: 1.0.5
2528
-
define-properties: 1.2.1
2529
-
es-abstract: 1.22.3
2530
-
2531
-
object.hasown@1.1.3:
2532
-
dependencies:
2533
define-properties: 1.2.1
2534
-
es-abstract: 1.22.3
2535
2536
-
object.values@1.1.7:
2537
dependencies:
2538
-
call-bind: 1.0.5
2539
define-properties: 1.2.1
2540
-
es-abstract: 1.22.3
2541
2542
-
once@1.4.0:
2543
dependencies:
2544
-
wrappy: 1.0.2
2545
-
2546
-
onetime@5.1.2:
2547
-
dependencies:
2548
-
mimic-fn: 2.1.0
2549
-
2550
-
onetime@6.0.0:
2551
-
dependencies:
2552
-
mimic-fn: 4.0.0
2553
2554
-
open@9.1.0:
2555
dependencies:
2556
-
default-browser: 4.0.0
2557
-
define-lazy-prop: 3.0.0
2558
-
is-inside-container: 1.0.0
2559
-
is-wsl: 2.2.0
2560
2561
optionator@0.9.3:
2562
dependencies:
···
2567
prelude-ls: 1.2.1
2568
type-check: 0.4.0
2569
2570
p-limit@3.1.0:
2571
dependencies:
2572
yocto-queue: 0.1.0
···
2574
p-locate@5.0.0:
2575
dependencies:
2576
p-limit: 3.1.0
2577
2578
parent-module@1.0.1:
2579
dependencies:
···
2581
2582
path-exists@4.0.0: {}
2583
2584
-
path-is-absolute@1.0.1: {}
2585
-
2586
path-key@3.1.1: {}
2587
2588
-
path-key@4.0.0: {}
2589
-
2590
path-parse@1.0.7: {}
2591
2592
-
path-type@4.0.0: {}
2593
2594
-
picocolors@1.0.0: {}
2595
2596
-
picomatch@2.3.1: {}
2597
2598
prelude-ls@1.2.1: {}
2599
···
2613
2614
punycode@2.3.1: {}
2615
2616
queue-microtask@1.2.3: {}
2617
2618
react-is@16.13.1: {}
···
2625
process: 0.11.10
2626
string_decoder: 1.3.0
2627
2628
-
reflect.getprototypeof@1.0.4:
2629
dependencies:
2630
-
call-bind: 1.0.5
2631
define-properties: 1.2.1
2632
-
es-abstract: 1.22.3
2633
-
get-intrinsic: 1.2.2
2634
-
globalthis: 1.0.3
2635
-
which-builtin-type: 1.1.3
2636
2637
-
regexp.prototype.flags@1.5.1:
2638
dependencies:
2639
-
call-bind: 1.0.5
2640
define-properties: 1.2.1
2641
-
set-function-name: 2.0.1
2642
2643
resolve-from@4.0.0: {}
2644
2645
resolve@2.0.0-next.5:
2646
dependencies:
2647
-
is-core-module: 2.13.1
2648
path-parse: 1.0.7
2649
supports-preserve-symlinks-flag: 1.0.0
2650
2651
-
reusify@1.0.4: {}
2652
-
2653
-
rimraf@3.0.2:
2654
dependencies:
2655
-
glob: 7.2.3
2656
2657
-
run-applescript@5.0.0:
2658
-
dependencies:
2659
-
execa: 5.1.1
2660
2661
run-parallel@1.2.0:
2662
dependencies:
2663
queue-microtask: 1.2.3
2664
2665
-
safe-array-concat@1.0.1:
2666
dependencies:
2667
-
call-bind: 1.0.5
2668
-
get-intrinsic: 1.2.2
2669
-
has-symbols: 1.0.3
2670
isarray: 2.0.5
2671
2672
-
safe-buffer@5.1.2: {}
2673
-
2674
safe-buffer@5.2.1: {}
2675
2676
-
safe-regex-test@1.0.0:
2677
dependencies:
2678
-
call-bind: 1.0.5
2679
-
get-intrinsic: 1.2.2
2680
-
is-regex: 1.1.4
2681
2682
semver@6.3.1: {}
2683
2684
-
semver@7.5.4:
2685
-
dependencies:
2686
-
lru-cache: 6.0.0
2687
2688
-
set-function-length@1.1.1:
2689
dependencies:
2690
-
define-data-property: 1.1.1
2691
-
get-intrinsic: 1.2.2
2692
-
gopd: 1.0.1
2693
-
has-property-descriptors: 1.0.1
2694
2695
-
set-function-name@2.0.1:
2696
dependencies:
2697
-
define-data-property: 1.1.1
2698
functions-have-names: 1.2.3
2699
-
has-property-descriptors: 1.0.1
2700
2701
shebang-command@2.0.0:
2702
dependencies:
···
2704
2705
shebang-regex@3.0.0: {}
2706
2707
-
side-channel@1.0.4:
2708
dependencies:
2709
-
call-bind: 1.0.5
2710
-
get-intrinsic: 1.2.2
2711
-
object-inspect: 1.13.1
2712
2713
-
signal-exit@3.0.7: {}
2714
2715
-
slash@3.0.0: {}
2716
2717
standalone-electron-types@1.0.0:
2718
dependencies:
2719
'@types/node': 18.17.17
2720
2721
-
string.prototype.matchall@4.0.10:
2722
dependencies:
2723
-
call-bind: 1.0.5
2724
define-properties: 1.2.1
2725
-
es-abstract: 1.22.3
2726
-
get-intrinsic: 1.2.2
2727
-
has-symbols: 1.0.3
2728
-
internal-slot: 1.0.6
2729
-
regexp.prototype.flags: 1.5.1
2730
-
set-function-name: 2.0.1
2731
-
side-channel: 1.0.4
2732
2733
-
string.prototype.trim@1.2.8:
2734
dependencies:
2735
-
call-bind: 1.0.5
2736
define-properties: 1.2.1
2737
-
es-abstract: 1.22.3
2738
2739
-
string.prototype.trimend@1.0.7:
2740
dependencies:
2741
-
call-bind: 1.0.5
2742
define-properties: 1.2.1
2743
-
es-abstract: 1.22.3
2744
2745
-
string.prototype.trimstart@1.0.7:
2746
dependencies:
2747
-
call-bind: 1.0.5
2748
define-properties: 1.2.1
2749
-
es-abstract: 1.22.3
2750
2751
-
string_decoder@1.3.0:
2752
dependencies:
2753
-
safe-buffer: 5.2.1
2754
2755
-
strip-ansi@6.0.1:
2756
dependencies:
2757
-
ansi-regex: 5.0.1
2758
-
2759
-
strip-final-newline@2.0.0: {}
2760
-
2761
-
strip-final-newline@3.0.0: {}
2762
2763
strip-json-comments@3.1.1: {}
2764
···
2768
2769
supports-preserve-symlinks-flag@1.0.0: {}
2770
2771
-
synckit@0.8.6:
2772
dependencies:
2773
-
'@pkgr/utils': 2.4.2
2774
-
tslib: 2.6.2
2775
2776
-
text-table@0.2.0: {}
2777
2778
-
titleize@3.0.0: {}
2779
2780
to-regex-range@5.0.1:
2781
dependencies:
2782
is-number: 7.0.0
2783
2784
-
ts-api-utils@1.0.3(typescript@5.3.2):
2785
dependencies:
2786
-
typescript: 5.3.2
2787
2788
-
tslib@2.6.2: {}
2789
2790
type-check@0.4.0:
2791
dependencies:
2792
prelude-ls: 1.2.1
2793
2794
-
type-fest@0.20.2: {}
2795
2796
-
typed-array-buffer@1.0.0:
2797
dependencies:
2798
-
call-bind: 1.0.5
2799
-
get-intrinsic: 1.2.2
2800
-
is-typed-array: 1.1.12
2801
2802
-
typed-array-byte-length@1.0.0:
2803
dependencies:
2804
-
call-bind: 1.0.5
2805
-
for-each: 0.3.3
2806
-
has-proto: 1.0.1
2807
-
is-typed-array: 1.1.12
2808
2809
-
typed-array-byte-offset@1.0.0:
2810
dependencies:
2811
-
available-typed-arrays: 1.0.5
2812
-
call-bind: 1.0.5
2813
-
for-each: 0.3.3
2814
-
has-proto: 1.0.1
2815
-
is-typed-array: 1.1.12
2816
2817
-
typed-array-length@1.0.4:
2818
dependencies:
2819
-
call-bind: 1.0.5
2820
-
for-each: 0.3.3
2821
-
is-typed-array: 1.1.12
2822
2823
-
typescript@5.3.2: {}
2824
2825
-
unbox-primitive@1.0.2:
2826
dependencies:
2827
-
call-bind: 1.0.5
2828
-
has-bigints: 1.0.2
2829
-
has-symbols: 1.0.3
2830
-
which-boxed-primitive: 1.0.2
2831
2832
-
undici-types@6.19.8: {}
2833
2834
-
untildify@4.0.0: {}
2835
2836
uri-js@4.4.1:
2837
dependencies:
2838
punycode: 2.3.1
2839
2840
-
utilium@0.7.1:
2841
dependencies:
2842
eventemitter3: 5.0.1
2843
2844
-
which-boxed-primitive@1.0.2:
2845
dependencies:
2846
-
is-bigint: 1.0.4
2847
-
is-boolean-object: 1.1.2
2848
-
is-number-object: 1.0.7
2849
-
is-string: 1.0.7
2850
-
is-symbol: 1.0.4
2851
2852
-
which-builtin-type@1.1.3:
2853
dependencies:
2854
-
function.prototype.name: 1.1.6
2855
-
has-tostringtag: 1.0.0
2856
-
is-async-function: 2.0.0
2857
-
is-date-object: 1.0.5
2858
-
is-finalizationregistry: 1.0.2
2859
-
is-generator-function: 1.0.10
2860
-
is-regex: 1.1.4
2861
-
is-weakref: 1.0.2
2862
isarray: 2.0.5
2863
-
which-boxed-primitive: 1.0.2
2864
-
which-collection: 1.0.1
2865
-
which-typed-array: 1.1.13
2866
2867
-
which-collection@1.0.1:
2868
dependencies:
2869
-
is-map: 2.0.2
2870
-
is-set: 2.0.2
2871
-
is-weakmap: 2.0.1
2872
-
is-weakset: 2.0.2
2873
2874
-
which-typed-array@1.1.13:
2875
dependencies:
2876
-
available-typed-arrays: 1.0.5
2877
-
call-bind: 1.0.5
2878
-
for-each: 0.3.3
2879
-
gopd: 1.0.1
2880
-
has-tostringtag: 1.0.0
2881
2882
which@2.0.2:
2883
dependencies:
2884
isexe: 2.0.0
2885
2886
-
wrappy@1.0.2: {}
2887
-
2888
-
yallist@4.0.0: {}
2889
2890
yocto-queue@0.1.0: {}
···
4
autoInstallPeers: true
5
excludeLinksFromLockfile: false
6
7
+
catalogs:
8
+
dev:
9
+
'@moonlight-mod/eslint-config':
10
+
specifier: github:moonlight-mod/eslint-config
11
+
version: 1.0.1
12
+
'@types/chrome':
13
+
specifier: ^0.0.313
14
+
version: 0.0.313
15
+
'@types/node':
16
+
specifier: ^22.14.0
17
+
version: 22.14.0
18
+
esbuild:
19
+
specifier: ^0.19.3
20
+
version: 0.19.3
21
+
esbuild-copy-static-files:
22
+
specifier: ^0.1.0
23
+
version: 0.1.0
24
+
eslint:
25
+
specifier: ^9.12.0
26
+
version: 9.23.0
27
+
husky:
28
+
specifier: ^8.0.3
29
+
version: 8.0.3
30
+
prettier:
31
+
specifier: ^3.1.0
32
+
version: 3.1.0
33
+
taze:
34
+
specifier: ^19.0.4
35
+
version: 19.0.4
36
+
typescript:
37
+
specifier: ^5.3.3
38
+
version: 5.8.2
39
+
prod:
40
+
'@moonlight-mod/lunast':
41
+
specifier: ^1.0.1
42
+
version: 1.0.1
43
+
'@moonlight-mod/mappings':
44
+
specifier: ^1.1.25
45
+
version: 1.1.25
46
+
'@moonlight-mod/moonmap':
47
+
specifier: ^1.0.5
48
+
version: 1.0.5
49
+
'@zenfs/core':
50
+
specifier: ^2.0.0
51
+
version: 2.0.0
52
+
'@zenfs/dom':
53
+
specifier: ^1.1.3
54
+
version: 1.1.6
55
+
microdiff:
56
+
specifier: ^1.5.0
57
+
version: 1.5.0
58
+
nanotar:
59
+
specifier: ^0.1.1
60
+
version: 0.1.1
61
+
62
importers:
63
64
.:
65
devDependencies:
66
+
'@moonlight-mod/eslint-config':
67
+
specifier: catalog:dev
68
+
version: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9(@types/eslint@9.6.1)(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0)(typescript@5.8.2)
69
+
'@types/node':
70
+
specifier: catalog:dev
71
+
version: 22.14.0
72
esbuild:
73
+
specifier: catalog:dev
74
version: 0.19.3
75
esbuild-copy-static-files:
76
+
specifier: catalog:dev
77
version: 0.1.0
78
eslint:
79
+
specifier: catalog:dev
80
+
version: 9.23.0(jiti@2.4.2)
81
husky:
82
+
specifier: catalog:dev
83
version: 8.0.3
84
prettier:
85
+
specifier: catalog:dev
86
version: 3.1.0
87
+
taze:
88
+
specifier: catalog:dev
89
+
version: 19.0.4
90
typescript:
91
+
specifier: catalog:dev
92
+
version: 5.8.2
93
94
packages/browser:
95
dependencies:
···
103
specifier: workspace:*
104
version: link:../web-preload
105
'@zenfs/core':
106
+
specifier: catalog:prod
107
+
version: 2.0.0
108
'@zenfs/dom':
109
+
specifier: catalog:prod
110
+
version: 1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1)
111
+
devDependencies:
112
+
'@types/chrome':
113
+
specifier: catalog:dev
114
+
version: 0.0.313
115
116
packages/core:
117
dependencies:
···
127
'@moonlight-mod/types':
128
specifier: workspace:*
129
version: link:../types
130
+
microdiff:
131
+
specifier: catalog:prod
132
+
version: 1.5.0
133
nanotar:
134
+
specifier: catalog:prod
135
version: 0.1.1
136
137
packages/injector:
···
155
packages/types:
156
dependencies:
157
'@moonlight-mod/lunast':
158
+
specifier: ^1.0.1
159
+
version: 1.0.1
160
'@moonlight-mod/mappings':
161
+
specifier: ^1.1.25
162
+
version: 1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)
163
'@moonlight-mod/moonmap':
164
+
specifier: ^1.0.5
165
+
version: 1.0.5
166
'@types/react':
167
specifier: ^18.3.10
168
+
version: 18.3.20
169
csstype:
170
+
specifier: ^3.1.3
171
+
version: 3.1.3
172
standalone-electron-types:
173
specifier: ^1.0.0
174
version: 1.0.0
···
179
specifier: workspace:*
180
version: link:../core
181
'@moonlight-mod/lunast':
182
+
specifier: catalog:prod
183
+
version: 1.0.1
184
'@moonlight-mod/mappings':
185
+
specifier: catalog:prod
186
+
version: 1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)
187
'@moonlight-mod/moonmap':
188
+
specifier: catalog:prod
189
+
version: 1.0.5
190
'@moonlight-mod/types':
191
specifier: workspace:*
192
version: link:../types
···
196
'@aashutoshrathi/word-wrap@1.2.6':
197
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
198
engines: {node: '>=0.10.0'}
199
+
200
+
'@antfu/ni@24.3.0':
201
+
resolution: {integrity: sha512-wBSav4mBxvHEW9RbdSo1SWLQ6MAlT0Dc423weC58yOWqW4OcMvtnNDdDrxOZeJ88fEIyPK93gDUWIelBxzSf8g==}
202
+
hasBin: true
203
204
'@esbuild/android-arm64@0.19.3':
205
resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==}
···
333
cpu: [x64]
334
os: [win32]
335
336
+
'@eslint-community/eslint-utils@4.5.1':
337
+
resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==}
338
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
339
peerDependencies:
340
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
341
342
+
'@eslint-community/regexpp@4.12.1':
343
+
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
344
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
345
346
+
'@eslint/config-array@0.19.2':
347
+
resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==}
348
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
349
+
350
+
'@eslint/config-helpers@0.2.1':
351
+
resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==}
352
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
353
+
354
+
'@eslint/core@0.12.0':
355
+
resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==}
356
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
357
+
358
+
'@eslint/core@0.13.0':
359
+
resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==}
360
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
361
+
362
+
'@eslint/eslintrc@3.3.1':
363
+
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
364
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
365
366
+
'@eslint/js@9.23.0':
367
+
resolution: {integrity: sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==}
368
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
369
370
+
'@eslint/object-schema@2.1.6':
371
+
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
372
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
373
+
374
+
'@eslint/plugin-kit@0.2.8':
375
+
resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==}
376
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
377
+
378
+
'@humanfs/core@0.19.1':
379
+
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
380
+
engines: {node: '>=18.18.0'}
381
+
382
+
'@humanfs/node@0.16.6':
383
+
resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
384
+
engines: {node: '>=18.18.0'}
385
386
'@humanwhocodes/module-importer@1.0.1':
387
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
388
engines: {node: '>=12.22'}
389
390
+
'@humanwhocodes/retry@0.3.1':
391
+
resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
392
+
engines: {node: '>=18.18'}
393
394
+
'@humanwhocodes/retry@0.4.2':
395
+
resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
396
+
engines: {node: '>=18.18'}
397
398
+
'@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9':
399
+
resolution: {tarball: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9}
400
+
version: 1.0.1
401
peerDependencies:
402
+
eslint: '>= 9'
403
+
typescript: '>= 5.3'
404
+
405
+
'@moonlight-mod/lunast@1.0.1':
406
+
resolution: {integrity: sha512-K3vxzDlfFuYKjciIW2FMlcZ1qrrkAGDGpSBlNqYGtJ0sMt9bRCd2lpSpg6AX/giSljDtmAUXa/5mOfUoDQxjBA==}
407
408
+
'@moonlight-mod/mappings@1.1.25':
409
+
resolution: {integrity: sha512-bgnSN9H/IBdMGxGev6RQKXuzhQxwo1090NhIDHnflguZnjiu2pg/usPfh76bqyhxRuX4SS7tiZSNTwBoSflCLg==}
410
+
engines: {node: '>=22', npm: pnpm, pnpm: '>=10', yarn: pnpm}
411
+
peerDependencies:
412
+
'@moonlight-mod/lunast': ^1.0.1
413
+
'@moonlight-mod/moonmap': ^1.0.5
414
+
415
+
'@moonlight-mod/moonmap@1.0.5':
416
+
resolution: {integrity: sha512-Fdpxj8ghdulKB6TlTnchlCPey2YUKgEf1chuO1ofOIcvlqnVPBcQwSf2S80naOUQpXCDo4dQ+LWSE2fmhdDiiw==}
417
418
'@nodelib/fs.scandir@2.1.5':
419
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
···
427
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
428
engines: {node: '>= 8'}
429
430
+
'@pkgr/core@0.2.0':
431
+
resolution: {integrity: sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==}
432
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
433
434
+
'@quansync/fs@0.1.2':
435
+
resolution: {integrity: sha512-ezIadUb1aFhwJLd++WVqVpi9rnlX8vnd4ju7saPhwLHJN1mJgOv0puePTGV+FbtSnWtwoHDT8lAm4kagDZmpCg==}
436
+
engines: {node: '>=20.0.0'}
437
+
438
+
'@types/chroma-js@3.1.0':
439
+
resolution: {integrity: sha512-Uwl3SOtUkbQ6Ye6ZYu4q4xdLGBzmY839sEHYtOT7i691neeyd+7fXWT5VIkcUSfNwIFrIjQutNYQn9h4q5HFvg==}
440
+
441
+
'@types/chrome@0.0.313':
442
+
resolution: {integrity: sha512-9R5T7gTaYZhkxlu+Ho4wk9FL+y/werWQY2yjGWSqCuiTsqS7nL/BE5UMTP6rU7J+oIG2FRKqrEycHhJATeltVA==}
443
+
444
+
'@types/eslint@9.6.1':
445
+
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
446
+
447
'@types/estree-jsx@1.0.5':
448
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
449
450
'@types/estree@1.0.6':
451
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
452
453
+
'@types/estree@1.0.7':
454
+
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
455
+
456
'@types/fbemitter@2.0.35':
457
resolution: {integrity: sha512-Xem6d7qUfmouCHntCrRYgDBwbf+WWRd6G+7WEFlEZFZ67LZXiYRvT2LV8wcZa6mIaAil95+ABQdKgB6hPIsnng==}
458
459
+
'@types/filesystem@0.0.36':
460
+
resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==}
461
+
462
+
'@types/filewriter@0.0.33':
463
+
resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==}
464
+
465
'@types/flux@3.1.14':
466
resolution: {integrity: sha512-WRXN0kQPCnqxN0/PgNgc7WBF6c8rbSHsEep3/qBLpsQ824RONdOmTs0TV7XhIW2GDNRAHO2CqCgAFLR5PChosw==}
467
468
+
'@types/har-format@1.2.16':
469
+
resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==}
470
+
471
+
'@types/highlightjs@9.12.6':
472
+
resolution: {integrity: sha512-Qfd1DUrwE851Hc3tExADJY4qY8yeZMt06Xw9AJm/UtpneepJS3MZY29c33BY0wP899veaaHD4gZzYiSuQm84Fg==}
473
+
474
'@types/json-schema@7.0.15':
475
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
476
477
+
'@types/lodash@4.17.14':
478
+
resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==}
479
+
480
'@types/node@18.17.17':
481
resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==}
482
483
+
'@types/node@22.13.6':
484
+
resolution: {integrity: sha512-GYmF65GI7417CpZXsEXMjT8goQQDnpRnJnDw6jIYa+le3V/lMazPZ4vZmK1B/9R17fh2VLr2zuy9d/h5xgrLAg==}
485
+
486
+
'@types/node@22.14.0':
487
+
resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==}
488
+
489
+
'@types/platform@1.3.6':
490
+
resolution: {integrity: sha512-ZmSaqHuvzv+jC232cFoz2QqPUkaj6EvMmCrWcx3WRr7xTPVFCMUOTcOq8m2d+Zw1iKRc1kDiaA+jtNrV0hkVew==}
491
492
'@types/prop-types@15.7.13':
493
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
494
495
+
'@types/react@18.3.20':
496
+
resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==}
497
498
+
'@typescript-eslint/eslint-plugin@8.29.0':
499
+
resolution: {integrity: sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==}
500
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
501
peerDependencies:
502
+
'@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
503
+
eslint: ^8.57.0 || ^9.0.0
504
+
typescript: '>=4.8.4 <5.9.0'
505
506
+
'@typescript-eslint/parser@8.29.0':
507
+
resolution: {integrity: sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==}
508
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
509
peerDependencies:
510
+
eslint: ^8.57.0 || ^9.0.0
511
+
typescript: '>=4.8.4 <5.9.0'
512
513
+
'@typescript-eslint/scope-manager@8.29.0':
514
+
resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==}
515
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
516
517
+
'@typescript-eslint/type-utils@8.29.0':
518
+
resolution: {integrity: sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==}
519
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
520
peerDependencies:
521
+
eslint: ^8.57.0 || ^9.0.0
522
+
typescript: '>=4.8.4 <5.9.0'
523
524
+
'@typescript-eslint/types@8.29.0':
525
+
resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==}
526
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
527
528
+
'@typescript-eslint/typescript-estree@8.29.0':
529
+
resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==}
530
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
531
peerDependencies:
532
+
typescript: '>=4.8.4 <5.9.0'
533
534
+
'@typescript-eslint/utils@8.29.0':
535
+
resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==}
536
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
537
peerDependencies:
538
+
eslint: ^8.57.0 || ^9.0.0
539
+
typescript: '>=4.8.4 <5.9.0'
540
541
+
'@typescript-eslint/visitor-keys@8.29.0':
542
+
resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==}
543
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
544
545
+
'@xterm/xterm@5.5.0':
546
+
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
547
548
+
'@zenfs/core@2.0.0':
549
+
resolution: {integrity: sha512-wOKNFTY1DJ1vdLqKdU7M8cRh0nVYZcDVu7WHuk/3u49hrSwTZVm4PzGxJUjFd8O9Wi3U5nYTbZoN7RX5mS2ldA==}
550
+
engines: {node: '>= 18'}
551
hasBin: true
552
553
+
'@zenfs/dom@1.1.6':
554
+
resolution: {integrity: sha512-7SBTWgA0esuEv/TE+N/xk6W/XJf8uBF+LhlPNHQdXds0H7aOy/UYsWv/8glvARe+meDMMidoeWFLzUWoMXfjlA==}
555
engines: {node: '>= 18'}
556
peerDependencies:
557
+
'@zenfs/core': ^2.0.0
558
+
utilium: ^1.9.0
559
560
abort-controller@3.0.0:
561
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
···
566
peerDependencies:
567
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
568
569
+
acorn@8.14.1:
570
+
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
571
engines: {node: '>=0.4.0'}
572
hasBin: true
573
574
ajv@6.12.6:
575
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
576
577
ansi-styles@4.3.0:
578
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
579
engines: {node: '>=8'}
580
581
+
ansis@3.17.0:
582
+
resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==}
583
+
engines: {node: '>=14'}
584
+
585
argparse@2.0.1:
586
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
587
588
+
array-buffer-byte-length@1.0.2:
589
+
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
590
+
engines: {node: '>= 0.4'}
591
592
+
array-includes@3.1.8:
593
+
resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
594
engines: {node: '>= 0.4'}
595
596
+
array.prototype.findlast@1.2.5:
597
+
resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
598
+
engines: {node: '>= 0.4'}
599
600
+
array.prototype.flat@1.3.3:
601
+
resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
602
engines: {node: '>= 0.4'}
603
604
+
array.prototype.flatmap@1.3.3:
605
+
resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
606
engines: {node: '>= 0.4'}
607
608
+
array.prototype.tosorted@1.1.4:
609
+
resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
610
+
engines: {node: '>= 0.4'}
611
612
+
arraybuffer.prototype.slice@1.0.4:
613
+
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
614
engines: {node: '>= 0.4'}
615
616
astring@1.9.0:
617
resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
618
hasBin: true
619
620
+
async-function@1.0.0:
621
+
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
622
+
engines: {node: '>= 0.4'}
623
624
+
available-typed-arrays@1.0.7:
625
+
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
626
engines: {node: '>= 0.4'}
627
628
balanced-match@1.0.2:
···
631
base64-js@1.5.1:
632
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
633
634
brace-expansion@1.1.11:
635
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
636
637
brace-expansion@2.0.1:
638
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
639
640
+
braces@3.0.3:
641
+
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
642
engines: {node: '>=8'}
643
644
buffer@6.0.3:
645
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
646
647
+
cac@6.7.14:
648
+
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
649
+
engines: {node: '>=8'}
650
651
+
call-bind-apply-helpers@1.0.2:
652
+
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
653
+
engines: {node: '>= 0.4'}
654
+
655
+
call-bind@1.0.8:
656
+
resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
657
+
engines: {node: '>= 0.4'}
658
+
659
+
call-bound@1.0.4:
660
+
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
661
+
engines: {node: '>= 0.4'}
662
663
callsites@3.1.0:
664
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
···
678
concat-map@0.0.1:
679
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
680
681
+
cross-spawn@7.0.6:
682
+
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
683
engines: {node: '>= 8'}
684
685
csstype@3.1.3:
686
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
687
688
+
data-view-buffer@1.0.2:
689
+
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
690
+
engines: {node: '>= 0.4'}
691
+
692
+
data-view-byte-length@1.0.2:
693
+
resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
694
+
engines: {node: '>= 0.4'}
695
+
696
+
data-view-byte-offset@1.0.1:
697
+
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
698
+
engines: {node: '>= 0.4'}
699
+
700
+
debug@4.4.0:
701
+
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
702
engines: {node: '>=6.0'}
703
peerDependencies:
704
supports-color: '*'
···
709
deep-is@0.1.4:
710
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
711
712
+
define-data-property@1.1.4:
713
+
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
714
engines: {node: '>= 0.4'}
715
716
define-properties@1.2.1:
717
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
718
engines: {node: '>= 0.4'}
719
720
+
defu@6.1.4:
721
+
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
722
+
723
+
destr@2.0.4:
724
+
resolution: {integrity: sha512-FCAorltMy7QwX0QU38jOkhrv20LBpsHA8ogzvMhhPHCCKVCaN6GxrB0GGaWEWBUYI4eEjjfJ95RdP6dk9IdMQA==}
725
726
doctrine@2.1.0:
727
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
728
engines: {node: '>=0.10.0'}
729
730
+
dunder-proto@1.0.1:
731
+
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
732
+
engines: {node: '>= 0.4'}
733
734
+
es-abstract@1.23.9:
735
+
resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==}
736
engines: {node: '>= 0.4'}
737
738
+
es-define-property@1.0.1:
739
+
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
740
+
engines: {node: '>= 0.4'}
741
742
+
es-errors@1.3.0:
743
+
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
744
engines: {node: '>= 0.4'}
745
746
+
es-iterator-helpers@1.2.1:
747
+
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
748
+
engines: {node: '>= 0.4'}
749
750
+
es-object-atoms@1.1.1:
751
+
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
752
+
engines: {node: '>= 0.4'}
753
+
754
+
es-set-tostringtag@2.1.0:
755
+
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
756
+
engines: {node: '>= 0.4'}
757
+
758
+
es-shim-unscopables@1.1.0:
759
+
resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
760
+
engines: {node: '>= 0.4'}
761
+
762
+
es-to-primitive@1.3.0:
763
+
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
764
engines: {node: '>= 0.4'}
765
766
esbuild-copy-static-files@0.1.0:
···
781
peerDependencies:
782
eslint: '>=7.0.0'
783
784
+
eslint-plugin-prettier@5.2.6:
785
+
resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==}
786
engines: {node: ^14.18.0 || >=16.0.0}
787
peerDependencies:
788
'@types/eslint': '>=8.0.0'
789
eslint: '>=8.0.0'
790
+
eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0'
791
prettier: '>=3.0.0'
792
peerDependenciesMeta:
793
'@types/eslint':
···
795
eslint-config-prettier:
796
optional: true
797
798
+
eslint-plugin-react@7.37.5:
799
+
resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
800
engines: {node: '>=4'}
801
peerDependencies:
802
+
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
803
804
+
eslint-scope@8.3.0:
805
+
resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==}
806
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
807
808
eslint-visitor-keys@3.4.3:
809
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
810
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
811
812
+
eslint-visitor-keys@4.2.0:
813
+
resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
814
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
815
+
816
+
eslint@9.23.0:
817
+
resolution: {integrity: sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==}
818
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
819
hasBin: true
820
+
peerDependencies:
821
+
jiti: '*'
822
+
peerDependenciesMeta:
823
+
jiti:
824
+
optional: true
825
826
+
espree@10.3.0:
827
+
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
828
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
829
830
+
esquery@1.6.0:
831
+
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
832
engines: {node: '>=0.10'}
833
834
esrecurse@4.3.0:
···
857
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
858
engines: {node: '>=0.8.x'}
859
860
fast-deep-equal@3.1.3:
861
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
862
···
873
fast-levenshtein@2.0.6:
874
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
875
876
+
fastq@1.17.1:
877
+
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
878
879
+
fdir@6.4.3:
880
+
resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
881
+
peerDependencies:
882
+
picomatch: ^3 || ^4
883
+
peerDependenciesMeta:
884
+
picomatch:
885
+
optional: true
886
+
887
+
file-entry-cache@8.0.0:
888
+
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
889
+
engines: {node: '>=16.0.0'}
890
891
+
fill-range@7.1.1:
892
+
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
893
engines: {node: '>=8'}
894
895
+
find-up-simple@1.0.1:
896
+
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
897
+
engines: {node: '>=18'}
898
+
899
find-up@5.0.0:
900
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
901
engines: {node: '>=10'}
902
903
+
flat-cache@4.0.1:
904
+
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
905
+
engines: {node: '>=16'}
906
907
flatted@3.2.9:
908
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
909
910
+
for-each@0.3.5:
911
+
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
912
+
engines: {node: '>= 0.4'}
913
914
function-bind@1.1.2:
915
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
916
917
+
function.prototype.name@1.1.8:
918
+
resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
919
engines: {node: '>= 0.4'}
920
921
functions-have-names@1.2.3:
922
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
923
924
+
fzf@0.5.2:
925
+
resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==}
926
+
927
+
get-intrinsic@1.3.0:
928
+
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
929
+
engines: {node: '>= 0.4'}
930
931
+
get-proto@1.0.1:
932
+
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
933
+
engines: {node: '>= 0.4'}
934
935
+
get-symbol-description@1.1.0:
936
+
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
937
engines: {node: '>= 0.4'}
938
939
glob-parent@5.1.2:
···
944
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
945
engines: {node: '>=10.13.0'}
946
947
+
globals@14.0.0:
948
+
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
949
+
engines: {node: '>=18'}
950
951
+
globalthis@1.0.4:
952
+
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
953
engines: {node: '>= 0.4'}
954
955
+
gopd@1.2.0:
956
+
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
957
+
engines: {node: '>= 0.4'}
958
959
graphemer@1.4.0:
960
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
961
962
+
has-bigints@1.1.0:
963
+
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
964
+
engines: {node: '>= 0.4'}
965
966
has-flag@4.0.0:
967
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
968
engines: {node: '>=8'}
969
970
+
has-property-descriptors@1.0.2:
971
+
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
972
973
+
has-proto@1.2.0:
974
+
resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
975
engines: {node: '>= 0.4'}
976
977
+
has-symbols@1.1.0:
978
+
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
979
engines: {node: '>= 0.4'}
980
981
+
has-tostringtag@1.0.2:
982
+
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
983
engines: {node: '>= 0.4'}
984
985
+
hasown@2.0.2:
986
+
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
987
engines: {node: '>= 0.4'}
988
989
husky@8.0.3:
990
resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
991
engines: {node: '>=14'}
···
994
ieee754@1.2.1:
995
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
996
997
+
ignore@5.3.2:
998
+
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
999
engines: {node: '>= 4'}
1000
1001
import-fresh@3.3.0:
···
1006
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
1007
engines: {node: '>=0.8.19'}
1008
1009
+
internal-slot@1.1.0:
1010
+
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
1011
engines: {node: '>= 0.4'}
1012
1013
+
is-array-buffer@3.0.5:
1014
+
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
1015
+
engines: {node: '>= 0.4'}
1016
1017
+
is-async-function@2.1.1:
1018
+
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
1019
engines: {node: '>= 0.4'}
1020
1021
+
is-bigint@1.1.0:
1022
+
resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
1023
+
engines: {node: '>= 0.4'}
1024
1025
+
is-boolean-object@1.2.2:
1026
+
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
1027
engines: {node: '>= 0.4'}
1028
1029
is-callable@1.2.7:
1030
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
1031
engines: {node: '>= 0.4'}
1032
1033
+
is-core-module@2.16.1:
1034
+
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
1035
+
engines: {node: '>= 0.4'}
1036
1037
+
is-data-view@1.0.2:
1038
+
resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
1039
engines: {node: '>= 0.4'}
1040
1041
+
is-date-object@1.1.0:
1042
+
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
1043
+
engines: {node: '>= 0.4'}
1044
1045
is-extglob@2.1.1:
1046
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
1047
engines: {node: '>=0.10.0'}
1048
1049
+
is-finalizationregistry@1.1.1:
1050
+
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
1051
+
engines: {node: '>= 0.4'}
1052
1053
+
is-generator-function@1.1.0:
1054
+
resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
1055
engines: {node: '>= 0.4'}
1056
1057
is-glob@4.0.3:
1058
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
1059
engines: {node: '>=0.10.0'}
1060
1061
+
is-map@2.0.3:
1062
+
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
1063
engines: {node: '>= 0.4'}
1064
1065
+
is-number-object@1.1.1:
1066
+
resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
1067
engines: {node: '>= 0.4'}
1068
1069
is-number@7.0.0:
1070
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
1071
engines: {node: '>=0.12.0'}
1072
1073
+
is-regex@1.2.1:
1074
+
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
1075
+
engines: {node: '>= 0.4'}
1076
1077
+
is-set@2.0.3:
1078
+
resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
1079
engines: {node: '>= 0.4'}
1080
1081
+
is-shared-array-buffer@1.0.4:
1082
+
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
1083
+
engines: {node: '>= 0.4'}
1084
1085
+
is-string@1.1.1:
1086
+
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
1087
+
engines: {node: '>= 0.4'}
1088
1089
+
is-symbol@1.1.1:
1090
+
resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
1091
+
engines: {node: '>= 0.4'}
1092
1093
+
is-typed-array@1.1.15:
1094
+
resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
1095
+
engines: {node: '>= 0.4'}
1096
1097
+
is-weakmap@2.0.2:
1098
+
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
1099
engines: {node: '>= 0.4'}
1100
1101
+
is-weakref@1.1.1:
1102
+
resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
1103
engines: {node: '>= 0.4'}
1104
1105
+
is-weakset@2.0.4:
1106
+
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
1107
engines: {node: '>= 0.4'}
1108
1109
isarray@2.0.5:
1110
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
1111
1112
isexe@2.0.0:
1113
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
1114
1115
+
iterator.prototype@1.1.5:
1116
+
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
1117
+
engines: {node: '>= 0.4'}
1118
+
1119
+
jiti@2.4.2:
1120
+
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
1121
+
hasBin: true
1122
1123
js-tokens@4.0.0:
1124
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
···
1158
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
1159
hasBin: true
1160
1161
+
math-intrinsics@1.1.0:
1162
+
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
1163
+
engines: {node: '>= 0.4'}
1164
1165
merge2@1.4.1:
1166
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
···
1170
resolution: {integrity: sha512-OyvYIOgpzXREySYJ1cqEb2pOKdeQMTfF9M8dRU6nC4hi/GXMmNpe9ssZCrSoTHazu05BSAoRBN/uYeco+ymfOg==}
1171
engines: {node: '>=18.0.0'}
1172
1173
+
microdiff@1.5.0:
1174
+
resolution: {integrity: sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==}
1175
1176
+
micromatch@4.0.8:
1177
+
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
1178
+
engines: {node: '>=8.6'}
1179
1180
+
mimic-function@5.0.1:
1181
+
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
1182
+
engines: {node: '>=18'}
1183
1184
minimatch@3.1.2:
1185
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
···
1188
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
1189
engines: {node: '>=16 || 14 >=14.17'}
1190
1191
+
ms@2.1.3:
1192
+
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
1193
1194
nanotar@0.1.1:
1195
resolution: {integrity: sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ==}
···
1197
natural-compare@1.4.0:
1198
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
1199
1200
+
node-fetch-native@1.6.6:
1201
+
resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==}
1202
1203
object-assign@4.1.1:
1204
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
1205
engines: {node: '>=0.10.0'}
1206
1207
+
object-inspect@1.13.4:
1208
+
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
1209
+
engines: {node: '>= 0.4'}
1210
1211
object-keys@1.1.1:
1212
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
1213
engines: {node: '>= 0.4'}
1214
1215
+
object.assign@4.1.7:
1216
+
resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
1217
engines: {node: '>= 0.4'}
1218
1219
+
object.entries@1.1.9:
1220
+
resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
1221
engines: {node: '>= 0.4'}
1222
1223
+
object.fromentries@2.0.8:
1224
+
resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
1225
engines: {node: '>= 0.4'}
1226
1227
+
object.values@1.2.1:
1228
+
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
1229
engines: {node: '>= 0.4'}
1230
1231
+
ofetch@1.4.1:
1232
+
resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==}
1233
1234
+
onetime@7.0.0:
1235
+
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
1236
+
engines: {node: '>=18'}
1237
1238
optionator@0.9.3:
1239
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
1240
engines: {node: '>= 0.8.0'}
1241
1242
+
own-keys@1.0.1:
1243
+
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
1244
+
engines: {node: '>= 0.4'}
1245
+
1246
p-limit@3.1.0:
1247
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
1248
engines: {node: '>=10'}
···
1251
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
1252
engines: {node: '>=10'}
1253
1254
+
package-manager-detector@1.1.0:
1255
+
resolution: {integrity: sha512-Y8f9qUlBzW8qauJjd/eu6jlpJZsuPJm2ZAV0cDVd420o4EdpH5RPdoCv+60/TdJflGatr4sDfpAL6ArWZbM5tA==}
1256
+
1257
parent-module@1.0.1:
1258
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
1259
engines: {node: '>=6'}
···
1261
path-exists@4.0.0:
1262
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
1263
engines: {node: '>=8'}
1264
1265
path-key@3.1.1:
1266
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
1267
engines: {node: '>=8'}
1268
1269
path-parse@1.0.7:
1270
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
1271
1272
+
pathe@2.0.3:
1273
+
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
1274
1275
picomatch@2.3.1:
1276
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
1277
engines: {node: '>=8.6'}
1278
+
1279
+
picomatch@4.0.2:
1280
+
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
1281
+
engines: {node: '>=12'}
1282
+
1283
+
pnpm-workspace-yaml@0.3.1:
1284
+
resolution: {integrity: sha512-3nW5RLmREmZ8Pm8MbPsO2RM+99RRjYd25ynj3NV0cFsN7CcEl4sDFzgoFmSyduFwxFQ2Qbu3y2UdCh6HlyUOeA==}
1285
+
1286
+
possible-typed-array-names@1.1.0:
1287
+
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
1288
+
engines: {node: '>= 0.4'}
1289
1290
prelude-ls@1.2.1:
1291
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
···
1310
punycode@2.3.1:
1311
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
1312
engines: {node: '>=6'}
1313
+
1314
+
quansync@0.2.10:
1315
+
resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==}
1316
1317
queue-microtask@1.2.3:
1318
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
···
1324
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
1325
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1326
1327
+
reflect.getprototypeof@1.0.10:
1328
+
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
1329
engines: {node: '>= 0.4'}
1330
1331
+
regexp.prototype.flags@1.5.4:
1332
+
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
1333
engines: {node: '>= 0.4'}
1334
1335
resolve-from@4.0.0:
···
1340
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
1341
hasBin: true
1342
1343
+
restore-cursor@5.1.0:
1344
+
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
1345
+
engines: {node: '>=18'}
1346
+
1347
reusify@1.0.4:
1348
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
1349
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
1350
1351
run-parallel@1.2.0:
1352
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
1353
1354
+
safe-array-concat@1.1.3:
1355
+
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
1356
engines: {node: '>=0.4'}
1357
1358
safe-buffer@5.2.1:
1359
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
1360
1361
+
safe-push-apply@1.0.0:
1362
+
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
1363
+
engines: {node: '>= 0.4'}
1364
+
1365
+
safe-regex-test@1.1.0:
1366
+
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
1367
+
engines: {node: '>= 0.4'}
1368
1369
semver@6.3.1:
1370
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
1371
hasBin: true
1372
1373
+
semver@7.7.1:
1374
+
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
1375
engines: {node: '>=10'}
1376
hasBin: true
1377
1378
+
set-function-length@1.2.2:
1379
+
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
1380
+
engines: {node: '>= 0.4'}
1381
+
1382
+
set-function-name@2.0.2:
1383
+
resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
1384
engines: {node: '>= 0.4'}
1385
1386
+
set-proto@1.0.0:
1387
+
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
1388
engines: {node: '>= 0.4'}
1389
1390
shebang-command@2.0.0:
···
1395
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
1396
engines: {node: '>=8'}
1397
1398
+
side-channel-list@1.0.0:
1399
+
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
1400
+
engines: {node: '>= 0.4'}
1401
1402
+
side-channel-map@1.0.1:
1403
+
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
1404
+
engines: {node: '>= 0.4'}
1405
1406
+
side-channel-weakmap@1.0.2:
1407
+
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
1408
+
engines: {node: '>= 0.4'}
1409
+
1410
+
side-channel@1.1.0:
1411
+
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
1412
+
engines: {node: '>= 0.4'}
1413
+
1414
+
signal-exit@4.1.0:
1415
+
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
1416
+
engines: {node: '>=14'}
1417
1418
standalone-electron-types@1.0.0:
1419
resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==}
1420
1421
+
string.prototype.matchall@4.0.12:
1422
+
resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
1423
+
engines: {node: '>= 0.4'}
1424
1425
+
string.prototype.repeat@1.0.0:
1426
+
resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
1427
+
1428
+
string.prototype.trim@1.2.10:
1429
+
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
1430
engines: {node: '>= 0.4'}
1431
1432
+
string.prototype.trimend@1.0.9:
1433
+
resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
1434
+
engines: {node: '>= 0.4'}
1435
1436
+
string.prototype.trimstart@1.0.8:
1437
+
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
1438
+
engines: {node: '>= 0.4'}
1439
1440
string_decoder@1.3.0:
1441
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
1442
1443
strip-json-comments@3.1.1:
1444
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
1445
engines: {node: '>=8'}
···
1452
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
1453
engines: {node: '>= 0.4'}
1454
1455
+
synckit@0.11.1:
1456
+
resolution: {integrity: sha512-fWZqNBZNNFp/7mTUy1fSsydhKsAKJ+u90Nk7kOK5Gcq9vObaqLBLjWFDBkyVU9Vvc6Y71VbOevMuGhqv02bT+Q==}
1457
engines: {node: ^14.18.0 || >=16.0.0}
1458
1459
+
taze@19.0.4:
1460
+
resolution: {integrity: sha512-bviyNotzqcIWpVBCC4QYVb2yupzKyUDGQi2m/8GERdiPaudVMtgAqaE98+x0cDDaByYRMJCyhQWM04ikUL6+kQ==}
1461
+
hasBin: true
1462
1463
+
tinyexec@1.0.1:
1464
+
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
1465
+
1466
+
tinyglobby@0.2.12:
1467
+
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
1468
+
engines: {node: '>=12.0.0'}
1469
1470
to-regex-range@5.0.1:
1471
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
1472
engines: {node: '>=8.0'}
1473
1474
+
ts-api-utils@2.1.0:
1475
+
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
1476
+
engines: {node: '>=18.12'}
1477
peerDependencies:
1478
+
typescript: '>=4.8.4'
1479
1480
+
tslib@2.8.1:
1481
+
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
1482
1483
type-check@0.4.0:
1484
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
1485
engines: {node: '>= 0.8.0'}
1486
1487
+
typed-array-buffer@1.0.3:
1488
+
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
1489
+
engines: {node: '>= 0.4'}
1490
1491
+
typed-array-byte-length@1.0.3:
1492
+
resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
1493
engines: {node: '>= 0.4'}
1494
1495
+
typed-array-byte-offset@1.0.4:
1496
+
resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
1497
engines: {node: '>= 0.4'}
1498
1499
+
typed-array-length@1.0.7:
1500
+
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
1501
engines: {node: '>= 0.4'}
1502
1503
+
typescript-eslint@8.29.0:
1504
+
resolution: {integrity: sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==}
1505
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1506
+
peerDependencies:
1507
+
eslint: ^8.57.0 || ^9.0.0
1508
+
typescript: '>=4.8.4 <5.9.0'
1509
1510
+
typescript@5.8.2:
1511
+
resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
1512
engines: {node: '>=14.17'}
1513
hasBin: true
1514
1515
+
ufo@1.5.4:
1516
+
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
1517
+
1518
+
unbox-primitive@1.1.0:
1519
+
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
1520
+
engines: {node: '>= 0.4'}
1521
1522
+
unconfig@7.3.1:
1523
+
resolution: {integrity: sha512-LH5WL+un92tGAzWS87k7LkAfwpMdm7V0IXG2FxEjZz/QxiIW5J5LkcrKQThj0aRz6+h/lFmKI9EUXmK/T0bcrw==}
1524
1525
+
undici-types@6.20.0:
1526
+
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
1527
+
1528
+
undici-types@6.21.0:
1529
+
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
1530
1531
uri-js@4.4.1:
1532
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
1533
1534
+
utilium@1.10.1:
1535
+
resolution: {integrity: sha512-GQINDTb/ocyz4acQj3GXAe0wipYxws6L+9ouqaq10KlInTk9DGvW9TJd0pYa/Xu3cppNnZuB4T/sBuSXpcN2ng==}
1536
1537
+
which-boxed-primitive@1.1.1:
1538
+
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
1539
+
engines: {node: '>= 0.4'}
1540
1541
+
which-builtin-type@1.2.1:
1542
+
resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
1543
engines: {node: '>= 0.4'}
1544
1545
+
which-collection@1.0.2:
1546
+
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
1547
+
engines: {node: '>= 0.4'}
1548
1549
+
which-typed-array@1.1.19:
1550
+
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
1551
engines: {node: '>= 0.4'}
1552
1553
which@2.0.2:
···
1555
engines: {node: '>= 8'}
1556
hasBin: true
1557
1558
+
yaml@2.7.1:
1559
+
resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==}
1560
+
engines: {node: '>= 14'}
1561
+
hasBin: true
1562
1563
yocto-queue@0.1.0:
1564
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
1565
engines: {node: '>=10'}
1566
1567
+
zustand@5.0.3:
1568
+
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
1569
+
engines: {node: '>=12.20.0'}
1570
+
peerDependencies:
1571
+
'@types/react': '>=18.0.0'
1572
+
immer: '>=9.0.6'
1573
+
react: '>=18.0.0'
1574
+
use-sync-external-store: '>=1.2.0'
1575
+
peerDependenciesMeta:
1576
+
'@types/react':
1577
+
optional: true
1578
+
immer:
1579
+
optional: true
1580
+
react:
1581
+
optional: true
1582
+
use-sync-external-store:
1583
+
optional: true
1584
+
1585
snapshots:
1586
1587
'@aashutoshrathi/word-wrap@1.2.6': {}
1588
+
1589
+
'@antfu/ni@24.3.0':
1590
+
dependencies:
1591
+
ansis: 3.17.0
1592
+
fzf: 0.5.2
1593
+
package-manager-detector: 1.1.0
1594
+
tinyexec: 1.0.1
1595
1596
'@esbuild/android-arm64@0.19.3':
1597
optional: true
···
1659
'@esbuild/win32-x64@0.19.3':
1660
optional: true
1661
1662
+
'@eslint-community/eslint-utils@4.5.1(eslint@9.23.0(jiti@2.4.2))':
1663
dependencies:
1664
+
eslint: 9.23.0(jiti@2.4.2)
1665
eslint-visitor-keys: 3.4.3
1666
1667
+
'@eslint-community/regexpp@4.12.1': {}
1668
1669
+
'@eslint/config-array@0.19.2':
1670
+
dependencies:
1671
+
'@eslint/object-schema': 2.1.6
1672
+
debug: 4.4.0
1673
+
minimatch: 3.1.2
1674
+
transitivePeerDependencies:
1675
+
- supports-color
1676
+
1677
+
'@eslint/config-helpers@0.2.1': {}
1678
+
1679
+
'@eslint/core@0.12.0':
1680
+
dependencies:
1681
+
'@types/json-schema': 7.0.15
1682
+
1683
+
'@eslint/core@0.13.0':
1684
+
dependencies:
1685
+
'@types/json-schema': 7.0.15
1686
+
1687
+
'@eslint/eslintrc@3.3.1':
1688
dependencies:
1689
ajv: 6.12.6
1690
+
debug: 4.4.0
1691
+
espree: 10.3.0
1692
+
globals: 14.0.0
1693
+
ignore: 5.3.2
1694
import-fresh: 3.3.0
1695
js-yaml: 4.1.0
1696
minimatch: 3.1.2
···
1698
transitivePeerDependencies:
1699
- supports-color
1700
1701
+
'@eslint/js@9.23.0': {}
1702
+
1703
+
'@eslint/object-schema@2.1.6': {}
1704
1705
+
'@eslint/plugin-kit@0.2.8':
1706
dependencies:
1707
+
'@eslint/core': 0.13.0
1708
+
levn: 0.4.1
1709
+
1710
+
'@humanfs/core@0.19.1': {}
1711
+
1712
+
'@humanfs/node@0.16.6':
1713
+
dependencies:
1714
+
'@humanfs/core': 0.19.1
1715
+
'@humanwhocodes/retry': 0.3.1
1716
1717
'@humanwhocodes/module-importer@1.0.1': {}
1718
1719
+
'@humanwhocodes/retry@0.3.1': {}
1720
+
1721
+
'@humanwhocodes/retry@0.4.2': {}
1722
1723
+
'@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9(@types/eslint@9.6.1)(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0)(typescript@5.8.2)':
1724
+
dependencies:
1725
+
'@eslint/js': 9.23.0
1726
+
eslint: 9.23.0(jiti@2.4.2)
1727
+
eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@2.4.2))
1728
+
eslint-plugin-prettier: 5.2.6(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)))(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0)
1729
+
eslint-plugin-react: 7.37.5(eslint@9.23.0(jiti@2.4.2))
1730
+
typescript: 5.8.2
1731
+
typescript-eslint: 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1732
+
transitivePeerDependencies:
1733
+
- '@types/eslint'
1734
+
- prettier
1735
+
- supports-color
1736
+
1737
+
'@moonlight-mod/lunast@1.0.1':
1738
dependencies:
1739
astring: 1.9.0
1740
estree-toolkit: 1.7.8
1741
meriyah: 6.0.1
1742
1743
+
'@moonlight-mod/mappings@1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)':
1744
dependencies:
1745
+
'@moonlight-mod/lunast': 1.0.1
1746
+
'@moonlight-mod/moonmap': 1.0.5
1747
+
'@types/chroma-js': 3.1.0
1748
'@types/flux': 3.1.14
1749
+
'@types/highlightjs': 9.12.6
1750
+
'@types/lodash': 4.17.14
1751
+
'@types/platform': 1.3.6
1752
+
'@types/react': 18.3.20
1753
csstype: 3.1.3
1754
+
zustand: 5.0.3(@types/react@18.3.20)
1755
+
transitivePeerDependencies:
1756
+
- immer
1757
+
- react
1758
+
- use-sync-external-store
1759
1760
+
'@moonlight-mod/moonmap@1.0.5': {}
1761
1762
'@nodelib/fs.scandir@2.1.5':
1763
dependencies:
···
1769
'@nodelib/fs.walk@1.2.8':
1770
dependencies:
1771
'@nodelib/fs.scandir': 2.1.5
1772
+
fastq: 1.17.1
1773
+
1774
+
'@pkgr/core@0.2.0': {}
1775
1776
+
'@quansync/fs@0.1.2':
1777
dependencies:
1778
+
quansync: 0.2.10
1779
+
1780
+
'@types/chroma-js@3.1.0': {}
1781
+
1782
+
'@types/chrome@0.0.313':
1783
+
dependencies:
1784
+
'@types/filesystem': 0.0.36
1785
+
'@types/har-format': 1.2.16
1786
+
1787
+
'@types/eslint@9.6.1':
1788
+
dependencies:
1789
+
'@types/estree': 1.0.7
1790
+
'@types/json-schema': 7.0.15
1791
+
optional: true
1792
1793
'@types/estree-jsx@1.0.5':
1794
dependencies:
···
1796
1797
'@types/estree@1.0.6': {}
1798
1799
+
'@types/estree@1.0.7':
1800
+
optional: true
1801
+
1802
'@types/fbemitter@2.0.35': {}
1803
1804
+
'@types/filesystem@0.0.36':
1805
+
dependencies:
1806
+
'@types/filewriter': 0.0.33
1807
+
1808
+
'@types/filewriter@0.0.33': {}
1809
+
1810
'@types/flux@3.1.14':
1811
dependencies:
1812
'@types/fbemitter': 2.0.35
1813
+
'@types/react': 18.3.20
1814
+
1815
+
'@types/har-format@1.2.16': {}
1816
+
1817
+
'@types/highlightjs@9.12.6': {}
1818
1819
'@types/json-schema@7.0.15': {}
1820
1821
+
'@types/lodash@4.17.14': {}
1822
+
1823
'@types/node@18.17.17': {}
1824
1825
+
'@types/node@22.13.6':
1826
+
dependencies:
1827
+
undici-types: 6.20.0
1828
+
1829
+
'@types/node@22.14.0':
1830
dependencies:
1831
+
undici-types: 6.21.0
1832
+
1833
+
'@types/platform@1.3.6': {}
1834
1835
'@types/prop-types@15.7.13': {}
1836
1837
+
'@types/react@18.3.20':
1838
dependencies:
1839
'@types/prop-types': 15.7.13
1840
csstype: 3.1.3
1841
1842
+
'@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
1843
dependencies:
1844
+
'@eslint-community/regexpp': 4.12.1
1845
+
'@typescript-eslint/parser': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1846
+
'@typescript-eslint/scope-manager': 8.29.0
1847
+
'@typescript-eslint/type-utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1848
+
'@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1849
+
'@typescript-eslint/visitor-keys': 8.29.0
1850
+
eslint: 9.23.0(jiti@2.4.2)
1851
graphemer: 1.4.0
1852
+
ignore: 5.3.2
1853
natural-compare: 1.4.0
1854
+
ts-api-utils: 2.1.0(typescript@5.8.2)
1855
+
typescript: 5.8.2
1856
transitivePeerDependencies:
1857
- supports-color
1858
1859
+
'@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
1860
dependencies:
1861
+
'@typescript-eslint/scope-manager': 8.29.0
1862
+
'@typescript-eslint/types': 8.29.0
1863
+
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2)
1864
+
'@typescript-eslint/visitor-keys': 8.29.0
1865
+
debug: 4.4.0
1866
+
eslint: 9.23.0(jiti@2.4.2)
1867
+
typescript: 5.8.2
1868
transitivePeerDependencies:
1869
- supports-color
1870
1871
+
'@typescript-eslint/scope-manager@8.29.0':
1872
dependencies:
1873
+
'@typescript-eslint/types': 8.29.0
1874
+
'@typescript-eslint/visitor-keys': 8.29.0
1875
1876
+
'@typescript-eslint/type-utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
1877
dependencies:
1878
+
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2)
1879
+
'@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1880
+
debug: 4.4.0
1881
+
eslint: 9.23.0(jiti@2.4.2)
1882
+
ts-api-utils: 2.1.0(typescript@5.8.2)
1883
+
typescript: 5.8.2
1884
transitivePeerDependencies:
1885
- supports-color
1886
1887
+
'@typescript-eslint/types@8.29.0': {}
1888
1889
+
'@typescript-eslint/typescript-estree@8.29.0(typescript@5.8.2)':
1890
dependencies:
1891
+
'@typescript-eslint/types': 8.29.0
1892
+
'@typescript-eslint/visitor-keys': 8.29.0
1893
+
debug: 4.4.0
1894
+
fast-glob: 3.3.2
1895
is-glob: 4.0.3
1896
+
minimatch: 9.0.5
1897
+
semver: 7.7.1
1898
+
ts-api-utils: 2.1.0(typescript@5.8.2)
1899
+
typescript: 5.8.2
1900
transitivePeerDependencies:
1901
- supports-color
1902
1903
+
'@typescript-eslint/utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
1904
dependencies:
1905
+
'@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2))
1906
+
'@typescript-eslint/scope-manager': 8.29.0
1907
+
'@typescript-eslint/types': 8.29.0
1908
+
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2)
1909
+
eslint: 9.23.0(jiti@2.4.2)
1910
+
typescript: 5.8.2
1911
transitivePeerDependencies:
1912
- supports-color
1913
1914
+
'@typescript-eslint/visitor-keys@8.29.0':
1915
dependencies:
1916
+
'@typescript-eslint/types': 8.29.0
1917
+
eslint-visitor-keys: 4.2.0
1918
1919
+
'@xterm/xterm@5.5.0':
1920
+
optional: true
1921
1922
+
'@zenfs/core@2.0.0':
1923
dependencies:
1924
+
'@types/node': 22.13.6
1925
buffer: 6.0.3
1926
eventemitter3: 5.0.1
1927
readable-stream: 4.5.2
1928
+
utilium: 1.10.1
1929
1930
+
'@zenfs/dom@1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1)':
1931
dependencies:
1932
+
'@zenfs/core': 2.0.0
1933
+
utilium: 1.10.1
1934
1935
abort-controller@3.0.0:
1936
dependencies:
1937
event-target-shim: 5.0.1
1938
1939
+
acorn-jsx@5.3.2(acorn@8.14.1):
1940
dependencies:
1941
+
acorn: 8.14.1
1942
1943
+
acorn@8.14.1: {}
1944
1945
ajv@6.12.6:
1946
dependencies:
···
1948
fast-json-stable-stringify: 2.1.0
1949
json-schema-traverse: 0.4.1
1950
uri-js: 4.4.1
1951
1952
ansi-styles@4.3.0:
1953
dependencies:
1954
color-convert: 2.0.1
1955
1956
+
ansis@3.17.0: {}
1957
+
1958
argparse@2.0.1: {}
1959
1960
+
array-buffer-byte-length@1.0.2:
1961
dependencies:
1962
+
call-bound: 1.0.4
1963
+
is-array-buffer: 3.0.5
1964
1965
+
array-includes@3.1.8:
1966
dependencies:
1967
+
call-bind: 1.0.8
1968
define-properties: 1.2.1
1969
+
es-abstract: 1.23.9
1970
+
es-object-atoms: 1.1.1
1971
+
get-intrinsic: 1.3.0
1972
+
is-string: 1.1.1
1973
1974
+
array.prototype.findlast@1.2.5:
1975
+
dependencies:
1976
+
call-bind: 1.0.8
1977
+
define-properties: 1.2.1
1978
+
es-abstract: 1.23.9
1979
+
es-errors: 1.3.0
1980
+
es-object-atoms: 1.1.1
1981
+
es-shim-unscopables: 1.1.0
1982
1983
+
array.prototype.flat@1.3.3:
1984
dependencies:
1985
+
call-bind: 1.0.8
1986
define-properties: 1.2.1
1987
+
es-abstract: 1.23.9
1988
+
es-shim-unscopables: 1.1.0
1989
1990
+
array.prototype.flatmap@1.3.3:
1991
dependencies:
1992
+
call-bind: 1.0.8
1993
define-properties: 1.2.1
1994
+
es-abstract: 1.23.9
1995
+
es-shim-unscopables: 1.1.0
1996
1997
+
array.prototype.tosorted@1.1.4:
1998
dependencies:
1999
+
call-bind: 1.0.8
2000
define-properties: 1.2.1
2001
+
es-abstract: 1.23.9
2002
+
es-errors: 1.3.0
2003
+
es-shim-unscopables: 1.1.0
2004
2005
+
arraybuffer.prototype.slice@1.0.4:
2006
dependencies:
2007
+
array-buffer-byte-length: 1.0.2
2008
+
call-bind: 1.0.8
2009
define-properties: 1.2.1
2010
+
es-abstract: 1.23.9
2011
+
es-errors: 1.3.0
2012
+
get-intrinsic: 1.3.0
2013
+
is-array-buffer: 3.0.5
2014
2015
astring@1.9.0: {}
2016
2017
+
async-function@1.0.0: {}
2018
2019
+
available-typed-arrays@1.0.7:
2020
+
dependencies:
2021
+
possible-typed-array-names: 1.1.0
2022
2023
balanced-match@1.0.2: {}
2024
2025
base64-js@1.5.1: {}
2026
2027
brace-expansion@1.1.11:
2028
dependencies:
2029
balanced-match: 1.0.2
···
2033
dependencies:
2034
balanced-match: 1.0.2
2035
2036
+
braces@3.0.3:
2037
dependencies:
2038
+
fill-range: 7.1.1
2039
2040
buffer@6.0.3:
2041
dependencies:
2042
base64-js: 1.5.1
2043
ieee754: 1.2.1
2044
2045
+
cac@6.7.14: {}
2046
2047
+
call-bind-apply-helpers@1.0.2:
2048
dependencies:
2049
+
es-errors: 1.3.0
2050
function-bind: 1.1.2
2051
+
2052
+
call-bind@1.0.8:
2053
+
dependencies:
2054
+
call-bind-apply-helpers: 1.0.2
2055
+
es-define-property: 1.0.1
2056
+
get-intrinsic: 1.3.0
2057
+
set-function-length: 1.2.2
2058
+
2059
+
call-bound@1.0.4:
2060
+
dependencies:
2061
+
call-bind-apply-helpers: 1.0.2
2062
+
get-intrinsic: 1.3.0
2063
2064
callsites@3.1.0: {}
2065
···
2076
2077
concat-map@0.0.1: {}
2078
2079
+
cross-spawn@7.0.6:
2080
dependencies:
2081
path-key: 3.1.1
2082
shebang-command: 2.0.0
2083
which: 2.0.2
2084
2085
csstype@3.1.3: {}
2086
2087
+
data-view-buffer@1.0.2:
2088
dependencies:
2089
+
call-bound: 1.0.4
2090
+
es-errors: 1.3.0
2091
+
is-data-view: 1.0.2
2092
2093
+
data-view-byte-length@1.0.2:
2094
dependencies:
2095
+
call-bound: 1.0.4
2096
+
es-errors: 1.3.0
2097
+
is-data-view: 1.0.2
2098
2099
+
data-view-byte-offset@1.0.1:
2100
dependencies:
2101
+
call-bound: 1.0.4
2102
+
es-errors: 1.3.0
2103
+
is-data-view: 1.0.2
2104
2105
+
debug@4.4.0:
2106
dependencies:
2107
+
ms: 2.1.3
2108
2109
+
deep-is@0.1.4: {}
2110
+
2111
+
define-data-property@1.1.4:
2112
+
dependencies:
2113
+
es-define-property: 1.0.1
2114
+
es-errors: 1.3.0
2115
+
gopd: 1.2.0
2116
2117
define-properties@1.2.1:
2118
dependencies:
2119
+
define-data-property: 1.1.4
2120
+
has-property-descriptors: 1.0.2
2121
object-keys: 1.1.1
2122
2123
+
defu@6.1.4: {}
2124
+
2125
+
destr@2.0.4: {}
2126
2127
doctrine@2.1.0:
2128
dependencies:
2129
esutils: 2.0.3
2130
2131
+
dunder-proto@1.0.1:
2132
dependencies:
2133
+
call-bind-apply-helpers: 1.0.2
2134
+
es-errors: 1.3.0
2135
+
gopd: 1.2.0
2136
2137
+
es-abstract@1.23.9:
2138
dependencies:
2139
+
array-buffer-byte-length: 1.0.2
2140
+
arraybuffer.prototype.slice: 1.0.4
2141
+
available-typed-arrays: 1.0.7
2142
+
call-bind: 1.0.8
2143
+
call-bound: 1.0.4
2144
+
data-view-buffer: 1.0.2
2145
+
data-view-byte-length: 1.0.2
2146
+
data-view-byte-offset: 1.0.1
2147
+
es-define-property: 1.0.1
2148
+
es-errors: 1.3.0
2149
+
es-object-atoms: 1.1.1
2150
+
es-set-tostringtag: 2.1.0
2151
+
es-to-primitive: 1.3.0
2152
+
function.prototype.name: 1.1.8
2153
+
get-intrinsic: 1.3.0
2154
+
get-proto: 1.0.1
2155
+
get-symbol-description: 1.1.0
2156
+
globalthis: 1.0.4
2157
+
gopd: 1.2.0
2158
+
has-property-descriptors: 1.0.2
2159
+
has-proto: 1.2.0
2160
+
has-symbols: 1.1.0
2161
+
hasown: 2.0.2
2162
+
internal-slot: 1.1.0
2163
+
is-array-buffer: 3.0.5
2164
is-callable: 1.2.7
2165
+
is-data-view: 1.0.2
2166
+
is-regex: 1.2.1
2167
+
is-shared-array-buffer: 1.0.4
2168
+
is-string: 1.1.1
2169
+
is-typed-array: 1.1.15
2170
+
is-weakref: 1.1.1
2171
+
math-intrinsics: 1.1.0
2172
+
object-inspect: 1.13.4
2173
object-keys: 1.1.1
2174
+
object.assign: 4.1.7
2175
+
own-keys: 1.0.1
2176
+
regexp.prototype.flags: 1.5.4
2177
+
safe-array-concat: 1.1.3
2178
+
safe-push-apply: 1.0.0
2179
+
safe-regex-test: 1.1.0
2180
+
set-proto: 1.0.0
2181
+
string.prototype.trim: 1.2.10
2182
+
string.prototype.trimend: 1.0.9
2183
+
string.prototype.trimstart: 1.0.8
2184
+
typed-array-buffer: 1.0.3
2185
+
typed-array-byte-length: 1.0.3
2186
+
typed-array-byte-offset: 1.0.4
2187
+
typed-array-length: 1.0.7
2188
+
unbox-primitive: 1.1.0
2189
+
which-typed-array: 1.1.19
2190
+
2191
+
es-define-property@1.0.1: {}
2192
+
2193
+
es-errors@1.3.0: {}
2194
2195
+
es-iterator-helpers@1.2.1:
2196
dependencies:
2197
+
call-bind: 1.0.8
2198
+
call-bound: 1.0.4
2199
define-properties: 1.2.1
2200
+
es-abstract: 1.23.9
2201
+
es-errors: 1.3.0
2202
+
es-set-tostringtag: 2.1.0
2203
function-bind: 1.1.2
2204
+
get-intrinsic: 1.3.0
2205
+
globalthis: 1.0.4
2206
+
gopd: 1.2.0
2207
+
has-property-descriptors: 1.0.2
2208
+
has-proto: 1.2.0
2209
+
has-symbols: 1.1.0
2210
+
internal-slot: 1.1.0
2211
+
iterator.prototype: 1.1.5
2212
+
safe-array-concat: 1.1.3
2213
2214
+
es-object-atoms@1.1.1:
2215
dependencies:
2216
+
es-errors: 1.3.0
2217
2218
+
es-set-tostringtag@2.1.0:
2219
+
dependencies:
2220
+
es-errors: 1.3.0
2221
+
get-intrinsic: 1.3.0
2222
+
has-tostringtag: 1.0.2
2223
+
hasown: 2.0.2
2224
+
2225
+
es-shim-unscopables@1.1.0:
2226
dependencies:
2227
+
hasown: 2.0.2
2228
2229
+
es-to-primitive@1.3.0:
2230
dependencies:
2231
is-callable: 1.2.7
2232
+
is-date-object: 1.1.0
2233
+
is-symbol: 1.1.1
2234
2235
esbuild-copy-static-files@0.1.0: {}
2236
···
2261
2262
escape-string-regexp@4.0.0: {}
2263
2264
+
eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)):
2265
dependencies:
2266
+
eslint: 9.23.0(jiti@2.4.2)
2267
2268
+
eslint-plugin-prettier@5.2.6(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)))(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0):
2269
dependencies:
2270
+
eslint: 9.23.0(jiti@2.4.2)
2271
prettier: 3.1.0
2272
prettier-linter-helpers: 1.0.0
2273
+
synckit: 0.11.1
2274
optionalDependencies:
2275
+
'@types/eslint': 9.6.1
2276
+
eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@2.4.2))
2277
2278
+
eslint-plugin-react@7.37.5(eslint@9.23.0(jiti@2.4.2)):
2279
dependencies:
2280
+
array-includes: 3.1.8
2281
+
array.prototype.findlast: 1.2.5
2282
+
array.prototype.flatmap: 1.3.3
2283
+
array.prototype.tosorted: 1.1.4
2284
doctrine: 2.1.0
2285
+
es-iterator-helpers: 1.2.1
2286
+
eslint: 9.23.0(jiti@2.4.2)
2287
estraverse: 5.3.0
2288
+
hasown: 2.0.2
2289
jsx-ast-utils: 3.3.5
2290
minimatch: 3.1.2
2291
+
object.entries: 1.1.9
2292
+
object.fromentries: 2.0.8
2293
+
object.values: 1.2.1
2294
prop-types: 15.8.1
2295
resolve: 2.0.0-next.5
2296
semver: 6.3.1
2297
+
string.prototype.matchall: 4.0.12
2298
+
string.prototype.repeat: 1.0.0
2299
2300
+
eslint-scope@8.3.0:
2301
dependencies:
2302
esrecurse: 4.3.0
2303
estraverse: 5.3.0
2304
2305
eslint-visitor-keys@3.4.3: {}
2306
2307
+
eslint-visitor-keys@4.2.0: {}
2308
+
2309
+
eslint@9.23.0(jiti@2.4.2):
2310
dependencies:
2311
+
'@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2))
2312
+
'@eslint-community/regexpp': 4.12.1
2313
+
'@eslint/config-array': 0.19.2
2314
+
'@eslint/config-helpers': 0.2.1
2315
+
'@eslint/core': 0.12.0
2316
+
'@eslint/eslintrc': 3.3.1
2317
+
'@eslint/js': 9.23.0
2318
+
'@eslint/plugin-kit': 0.2.8
2319
+
'@humanfs/node': 0.16.6
2320
'@humanwhocodes/module-importer': 1.0.1
2321
+
'@humanwhocodes/retry': 0.4.2
2322
+
'@types/estree': 1.0.6
2323
+
'@types/json-schema': 7.0.15
2324
ajv: 6.12.6
2325
chalk: 4.1.2
2326
+
cross-spawn: 7.0.6
2327
+
debug: 4.4.0
2328
escape-string-regexp: 4.0.0
2329
+
eslint-scope: 8.3.0
2330
+
eslint-visitor-keys: 4.2.0
2331
+
espree: 10.3.0
2332
+
esquery: 1.6.0
2333
esutils: 2.0.3
2334
fast-deep-equal: 3.1.3
2335
+
file-entry-cache: 8.0.0
2336
find-up: 5.0.0
2337
glob-parent: 6.0.2
2338
+
ignore: 5.3.2
2339
imurmurhash: 0.1.4
2340
is-glob: 4.0.3
2341
json-stable-stringify-without-jsonify: 1.0.1
2342
lodash.merge: 4.6.2
2343
minimatch: 3.1.2
2344
natural-compare: 1.4.0
2345
optionator: 0.9.3
2346
+
optionalDependencies:
2347
+
jiti: 2.4.2
2348
transitivePeerDependencies:
2349
- supports-color
2350
2351
+
espree@10.3.0:
2352
dependencies:
2353
+
acorn: 8.14.1
2354
+
acorn-jsx: 5.3.2(acorn@8.14.1)
2355
+
eslint-visitor-keys: 4.2.0
2356
2357
+
esquery@1.6.0:
2358
dependencies:
2359
estraverse: 5.3.0
2360
···
2377
2378
events@3.3.0: {}
2379
2380
fast-deep-equal@3.1.3: {}
2381
2382
fast-diff@1.3.0: {}
···
2387
'@nodelib/fs.walk': 1.2.8
2388
glob-parent: 5.1.2
2389
merge2: 1.4.1
2390
+
micromatch: 4.0.8
2391
2392
fast-json-stable-stringify@2.1.0: {}
2393
2394
fast-levenshtein@2.0.6: {}
2395
2396
+
fastq@1.17.1:
2397
dependencies:
2398
reusify: 1.0.4
2399
2400
+
fdir@6.4.3(picomatch@4.0.2):
2401
+
optionalDependencies:
2402
+
picomatch: 4.0.2
2403
+
2404
+
file-entry-cache@8.0.0:
2405
dependencies:
2406
+
flat-cache: 4.0.1
2407
2408
+
fill-range@7.1.1:
2409
dependencies:
2410
to-regex-range: 5.0.1
2411
+
2412
+
find-up-simple@1.0.1: {}
2413
2414
find-up@5.0.0:
2415
dependencies:
2416
locate-path: 6.0.0
2417
path-exists: 4.0.0
2418
2419
+
flat-cache@4.0.1:
2420
dependencies:
2421
flatted: 3.2.9
2422
keyv: 4.5.4
2423
2424
flatted@3.2.9: {}
2425
2426
+
for-each@0.3.5:
2427
dependencies:
2428
is-callable: 1.2.7
2429
2430
function-bind@1.1.2: {}
2431
2432
+
function.prototype.name@1.1.8:
2433
dependencies:
2434
+
call-bind: 1.0.8
2435
+
call-bound: 1.0.4
2436
define-properties: 1.2.1
2437
functions-have-names: 1.2.3
2438
+
hasown: 2.0.2
2439
+
is-callable: 1.2.7
2440
2441
functions-have-names@1.2.3: {}
2442
2443
+
fzf@0.5.2: {}
2444
+
2445
+
get-intrinsic@1.3.0:
2446
dependencies:
2447
+
call-bind-apply-helpers: 1.0.2
2448
+
es-define-property: 1.0.1
2449
+
es-errors: 1.3.0
2450
+
es-object-atoms: 1.1.1
2451
function-bind: 1.1.2
2452
+
get-proto: 1.0.1
2453
+
gopd: 1.2.0
2454
+
has-symbols: 1.1.0
2455
+
hasown: 2.0.2
2456
+
math-intrinsics: 1.1.0
2457
2458
+
get-proto@1.0.1:
2459
+
dependencies:
2460
+
dunder-proto: 1.0.1
2461
+
es-object-atoms: 1.1.1
2462
2463
+
get-symbol-description@1.1.0:
2464
dependencies:
2465
+
call-bound: 1.0.4
2466
+
es-errors: 1.3.0
2467
+
get-intrinsic: 1.3.0
2468
2469
glob-parent@5.1.2:
2470
dependencies:
···
2474
dependencies:
2475
is-glob: 4.0.3
2476
2477
+
globals@14.0.0: {}
2478
2479
+
globalthis@1.0.4:
2480
dependencies:
2481
define-properties: 1.2.1
2482
+
gopd: 1.2.0
2483
2484
+
gopd@1.2.0: {}
2485
2486
graphemer@1.4.0: {}
2487
2488
+
has-bigints@1.1.0: {}
2489
2490
has-flag@4.0.0: {}
2491
2492
+
has-property-descriptors@1.0.2:
2493
dependencies:
2494
+
es-define-property: 1.0.1
2495
2496
+
has-proto@1.2.0:
2497
+
dependencies:
2498
+
dunder-proto: 1.0.1
2499
2500
+
has-symbols@1.1.0: {}
2501
2502
+
has-tostringtag@1.0.2:
2503
dependencies:
2504
+
has-symbols: 1.1.0
2505
2506
+
hasown@2.0.2:
2507
dependencies:
2508
function-bind: 1.1.2
2509
2510
husky@8.0.3: {}
2511
2512
ieee754@1.2.1: {}
2513
2514
+
ignore@5.3.2: {}
2515
2516
import-fresh@3.3.0:
2517
dependencies:
···
2520
2521
imurmurhash@0.1.4: {}
2522
2523
+
internal-slot@1.1.0:
2524
dependencies:
2525
+
es-errors: 1.3.0
2526
+
hasown: 2.0.2
2527
+
side-channel: 1.1.0
2528
2529
+
is-array-buffer@3.0.5:
2530
dependencies:
2531
+
call-bind: 1.0.8
2532
+
call-bound: 1.0.4
2533
+
get-intrinsic: 1.3.0
2534
2535
+
is-async-function@2.1.1:
2536
dependencies:
2537
+
async-function: 1.0.0
2538
+
call-bound: 1.0.4
2539
+
get-proto: 1.0.1
2540
+
has-tostringtag: 1.0.2
2541
+
safe-regex-test: 1.1.0
2542
2543
+
is-bigint@1.1.0:
2544
dependencies:
2545
+
has-bigints: 1.1.0
2546
2547
+
is-boolean-object@1.2.2:
2548
dependencies:
2549
+
call-bound: 1.0.4
2550
+
has-tostringtag: 1.0.2
2551
2552
is-callable@1.2.7: {}
2553
2554
+
is-core-module@2.16.1:
2555
dependencies:
2556
+
hasown: 2.0.2
2557
2558
+
is-data-view@1.0.2:
2559
dependencies:
2560
+
call-bound: 1.0.4
2561
+
get-intrinsic: 1.3.0
2562
+
is-typed-array: 1.1.15
2563
2564
+
is-date-object@1.1.0:
2565
+
dependencies:
2566
+
call-bound: 1.0.4
2567
+
has-tostringtag: 1.0.2
2568
2569
is-extglob@2.1.1: {}
2570
2571
+
is-finalizationregistry@1.1.1:
2572
dependencies:
2573
+
call-bound: 1.0.4
2574
2575
+
is-generator-function@1.1.0:
2576
dependencies:
2577
+
call-bound: 1.0.4
2578
+
get-proto: 1.0.1
2579
+
has-tostringtag: 1.0.2
2580
+
safe-regex-test: 1.1.0
2581
2582
is-glob@4.0.3:
2583
dependencies:
2584
is-extglob: 2.1.1
2585
2586
+
is-map@2.0.3: {}
2587
2588
+
is-number-object@1.1.1:
2589
dependencies:
2590
+
call-bound: 1.0.4
2591
+
has-tostringtag: 1.0.2
2592
2593
is-number@7.0.0: {}
2594
2595
+
is-regex@1.2.1:
2596
dependencies:
2597
+
call-bound: 1.0.4
2598
+
gopd: 1.2.0
2599
+
has-tostringtag: 1.0.2
2600
+
hasown: 2.0.2
2601
2602
+
is-set@2.0.3: {}
2603
2604
+
is-shared-array-buffer@1.0.4:
2605
dependencies:
2606
+
call-bound: 1.0.4
2607
2608
+
is-string@1.1.1:
2609
dependencies:
2610
+
call-bound: 1.0.4
2611
+
has-tostringtag: 1.0.2
2612
2613
+
is-symbol@1.1.1:
2614
dependencies:
2615
+
call-bound: 1.0.4
2616
+
has-symbols: 1.1.0
2617
+
safe-regex-test: 1.1.0
2618
2619
+
is-typed-array@1.1.15:
2620
dependencies:
2621
+
which-typed-array: 1.1.19
2622
2623
+
is-weakmap@2.0.2: {}
2624
2625
+
is-weakref@1.1.1:
2626
dependencies:
2627
+
call-bound: 1.0.4
2628
2629
+
is-weakset@2.0.4:
2630
dependencies:
2631
+
call-bound: 1.0.4
2632
+
get-intrinsic: 1.3.0
2633
2634
isarray@2.0.5: {}
2635
2636
isexe@2.0.0: {}
2637
2638
+
iterator.prototype@1.1.5:
2639
dependencies:
2640
+
define-data-property: 1.1.4
2641
+
es-object-atoms: 1.1.1
2642
+
get-intrinsic: 1.3.0
2643
+
get-proto: 1.0.1
2644
+
has-symbols: 1.1.0
2645
+
set-function-name: 2.0.2
2646
+
2647
+
jiti@2.4.2: {}
2648
2649
js-tokens@4.0.0: {}
2650
···
2660
2661
jsx-ast-utils@3.3.5:
2662
dependencies:
2663
+
array-includes: 3.1.8
2664
+
array.prototype.flat: 1.3.3
2665
+
object.assign: 4.1.7
2666
+
object.values: 1.2.1
2667
2668
keyv@4.5.4:
2669
dependencies:
···
2684
dependencies:
2685
js-tokens: 4.0.0
2686
2687
+
math-intrinsics@1.1.0: {}
2688
2689
merge2@1.4.1: {}
2690
2691
meriyah@6.0.1: {}
2692
2693
+
microdiff@1.5.0: {}
2694
+
2695
+
micromatch@4.0.8:
2696
dependencies:
2697
+
braces: 3.0.3
2698
picomatch: 2.3.1
2699
2700
+
mimic-function@5.0.1: {}
2701
2702
minimatch@3.1.2:
2703
dependencies:
···
2707
dependencies:
2708
brace-expansion: 2.0.1
2709
2710
+
ms@2.1.3: {}
2711
2712
nanotar@0.1.1: {}
2713
2714
natural-compare@1.4.0: {}
2715
2716
+
node-fetch-native@1.6.6: {}
2717
2718
object-assign@4.1.1: {}
2719
2720
+
object-inspect@1.13.4: {}
2721
2722
object-keys@1.1.1: {}
2723
2724
+
object.assign@4.1.7:
2725
dependencies:
2726
+
call-bind: 1.0.8
2727
+
call-bound: 1.0.4
2728
define-properties: 1.2.1
2729
+
es-object-atoms: 1.1.1
2730
+
has-symbols: 1.1.0
2731
object-keys: 1.1.1
2732
2733
+
object.entries@1.1.9:
2734
dependencies:
2735
+
call-bind: 1.0.8
2736
+
call-bound: 1.0.4
2737
define-properties: 1.2.1
2738
+
es-object-atoms: 1.1.1
2739
2740
+
object.fromentries@2.0.8:
2741
dependencies:
2742
+
call-bind: 1.0.8
2743
define-properties: 1.2.1
2744
+
es-abstract: 1.23.9
2745
+
es-object-atoms: 1.1.1
2746
2747
+
object.values@1.2.1:
2748
dependencies:
2749
+
call-bind: 1.0.8
2750
+
call-bound: 1.0.4
2751
define-properties: 1.2.1
2752
+
es-object-atoms: 1.1.1
2753
2754
+
ofetch@1.4.1:
2755
dependencies:
2756
+
destr: 2.0.4
2757
+
node-fetch-native: 1.6.6
2758
+
ufo: 1.5.4
2759
2760
+
onetime@7.0.0:
2761
dependencies:
2762
+
mimic-function: 5.0.1
2763
2764
optionator@0.9.3:
2765
dependencies:
···
2770
prelude-ls: 1.2.1
2771
type-check: 0.4.0
2772
2773
+
own-keys@1.0.1:
2774
+
dependencies:
2775
+
get-intrinsic: 1.3.0
2776
+
object-keys: 1.1.1
2777
+
safe-push-apply: 1.0.0
2778
+
2779
p-limit@3.1.0:
2780
dependencies:
2781
yocto-queue: 0.1.0
···
2783
p-locate@5.0.0:
2784
dependencies:
2785
p-limit: 3.1.0
2786
+
2787
+
package-manager-detector@1.1.0: {}
2788
2789
parent-module@1.0.1:
2790
dependencies:
···
2792
2793
path-exists@4.0.0: {}
2794
2795
path-key@3.1.1: {}
2796
2797
path-parse@1.0.7: {}
2798
2799
+
pathe@2.0.3: {}
2800
2801
+
picomatch@2.3.1: {}
2802
+
2803
+
picomatch@4.0.2: {}
2804
+
2805
+
pnpm-workspace-yaml@0.3.1:
2806
+
dependencies:
2807
+
yaml: 2.7.1
2808
2809
+
possible-typed-array-names@1.1.0: {}
2810
2811
prelude-ls@1.2.1: {}
2812
···
2826
2827
punycode@2.3.1: {}
2828
2829
+
quansync@0.2.10: {}
2830
+
2831
queue-microtask@1.2.3: {}
2832
2833
react-is@16.13.1: {}
···
2840
process: 0.11.10
2841
string_decoder: 1.3.0
2842
2843
+
reflect.getprototypeof@1.0.10:
2844
dependencies:
2845
+
call-bind: 1.0.8
2846
define-properties: 1.2.1
2847
+
es-abstract: 1.23.9
2848
+
es-errors: 1.3.0
2849
+
es-object-atoms: 1.1.1
2850
+
get-intrinsic: 1.3.0
2851
+
get-proto: 1.0.1
2852
+
which-builtin-type: 1.2.1
2853
2854
+
regexp.prototype.flags@1.5.4:
2855
dependencies:
2856
+
call-bind: 1.0.8
2857
define-properties: 1.2.1
2858
+
es-errors: 1.3.0
2859
+
get-proto: 1.0.1
2860
+
gopd: 1.2.0
2861
+
set-function-name: 2.0.2
2862
2863
resolve-from@4.0.0: {}
2864
2865
resolve@2.0.0-next.5:
2866
dependencies:
2867
+
is-core-module: 2.16.1
2868
path-parse: 1.0.7
2869
supports-preserve-symlinks-flag: 1.0.0
2870
2871
+
restore-cursor@5.1.0:
2872
dependencies:
2873
+
onetime: 7.0.0
2874
+
signal-exit: 4.1.0
2875
2876
+
reusify@1.0.4: {}
2877
2878
run-parallel@1.2.0:
2879
dependencies:
2880
queue-microtask: 1.2.3
2881
2882
+
safe-array-concat@1.1.3:
2883
dependencies:
2884
+
call-bind: 1.0.8
2885
+
call-bound: 1.0.4
2886
+
get-intrinsic: 1.3.0
2887
+
has-symbols: 1.1.0
2888
isarray: 2.0.5
2889
2890
safe-buffer@5.2.1: {}
2891
2892
+
safe-push-apply@1.0.0:
2893
dependencies:
2894
+
es-errors: 1.3.0
2895
+
isarray: 2.0.5
2896
+
2897
+
safe-regex-test@1.1.0:
2898
+
dependencies:
2899
+
call-bound: 1.0.4
2900
+
es-errors: 1.3.0
2901
+
is-regex: 1.2.1
2902
2903
semver@6.3.1: {}
2904
2905
+
semver@7.7.1: {}
2906
2907
+
set-function-length@1.2.2:
2908
dependencies:
2909
+
define-data-property: 1.1.4
2910
+
es-errors: 1.3.0
2911
+
function-bind: 1.1.2
2912
+
get-intrinsic: 1.3.0
2913
+
gopd: 1.2.0
2914
+
has-property-descriptors: 1.0.2
2915
2916
+
set-function-name@2.0.2:
2917
dependencies:
2918
+
define-data-property: 1.1.4
2919
+
es-errors: 1.3.0
2920
functions-have-names: 1.2.3
2921
+
has-property-descriptors: 1.0.2
2922
+
2923
+
set-proto@1.0.0:
2924
+
dependencies:
2925
+
dunder-proto: 1.0.1
2926
+
es-errors: 1.3.0
2927
+
es-object-atoms: 1.1.1
2928
2929
shebang-command@2.0.0:
2930
dependencies:
···
2932
2933
shebang-regex@3.0.0: {}
2934
2935
+
side-channel-list@1.0.0:
2936
dependencies:
2937
+
es-errors: 1.3.0
2938
+
object-inspect: 1.13.4
2939
2940
+
side-channel-map@1.0.1:
2941
+
dependencies:
2942
+
call-bound: 1.0.4
2943
+
es-errors: 1.3.0
2944
+
get-intrinsic: 1.3.0
2945
+
object-inspect: 1.13.4
2946
2947
+
side-channel-weakmap@1.0.2:
2948
+
dependencies:
2949
+
call-bound: 1.0.4
2950
+
es-errors: 1.3.0
2951
+
get-intrinsic: 1.3.0
2952
+
object-inspect: 1.13.4
2953
+
side-channel-map: 1.0.1
2954
+
2955
+
side-channel@1.1.0:
2956
+
dependencies:
2957
+
es-errors: 1.3.0
2958
+
object-inspect: 1.13.4
2959
+
side-channel-list: 1.0.0
2960
+
side-channel-map: 1.0.1
2961
+
side-channel-weakmap: 1.0.2
2962
+
2963
+
signal-exit@4.1.0: {}
2964
2965
standalone-electron-types@1.0.0:
2966
dependencies:
2967
'@types/node': 18.17.17
2968
2969
+
string.prototype.matchall@4.0.12:
2970
dependencies:
2971
+
call-bind: 1.0.8
2972
+
call-bound: 1.0.4
2973
define-properties: 1.2.1
2974
+
es-abstract: 1.23.9
2975
+
es-errors: 1.3.0
2976
+
es-object-atoms: 1.1.1
2977
+
get-intrinsic: 1.3.0
2978
+
gopd: 1.2.0
2979
+
has-symbols: 1.1.0
2980
+
internal-slot: 1.1.0
2981
+
regexp.prototype.flags: 1.5.4
2982
+
set-function-name: 2.0.2
2983
+
side-channel: 1.1.0
2984
2985
+
string.prototype.repeat@1.0.0:
2986
dependencies:
2987
define-properties: 1.2.1
2988
+
es-abstract: 1.23.9
2989
2990
+
string.prototype.trim@1.2.10:
2991
dependencies:
2992
+
call-bind: 1.0.8
2993
+
call-bound: 1.0.4
2994
+
define-data-property: 1.1.4
2995
define-properties: 1.2.1
2996
+
es-abstract: 1.23.9
2997
+
es-object-atoms: 1.1.1
2998
+
has-property-descriptors: 1.0.2
2999
3000
+
string.prototype.trimend@1.0.9:
3001
dependencies:
3002
+
call-bind: 1.0.8
3003
+
call-bound: 1.0.4
3004
define-properties: 1.2.1
3005
+
es-object-atoms: 1.1.1
3006
3007
+
string.prototype.trimstart@1.0.8:
3008
dependencies:
3009
+
call-bind: 1.0.8
3010
+
define-properties: 1.2.1
3011
+
es-object-atoms: 1.1.1
3012
3013
+
string_decoder@1.3.0:
3014
dependencies:
3015
+
safe-buffer: 5.2.1
3016
3017
strip-json-comments@3.1.1: {}
3018
···
3022
3023
supports-preserve-symlinks-flag@1.0.0: {}
3024
3025
+
synckit@0.11.1:
3026
dependencies:
3027
+
'@pkgr/core': 0.2.0
3028
+
tslib: 2.8.1
3029
3030
+
taze@19.0.4:
3031
+
dependencies:
3032
+
'@antfu/ni': 24.3.0
3033
+
cac: 6.7.14
3034
+
find-up-simple: 1.0.1
3035
+
ofetch: 1.4.1
3036
+
package-manager-detector: 1.1.0
3037
+
pathe: 2.0.3
3038
+
pnpm-workspace-yaml: 0.3.1
3039
+
restore-cursor: 5.1.0
3040
+
tinyexec: 1.0.1
3041
+
tinyglobby: 0.2.12
3042
+
unconfig: 7.3.1
3043
+
yaml: 2.7.1
3044
3045
+
tinyexec@1.0.1: {}
3046
+
3047
+
tinyglobby@0.2.12:
3048
+
dependencies:
3049
+
fdir: 6.4.3(picomatch@4.0.2)
3050
+
picomatch: 4.0.2
3051
3052
to-regex-range@5.0.1:
3053
dependencies:
3054
is-number: 7.0.0
3055
3056
+
ts-api-utils@2.1.0(typescript@5.8.2):
3057
dependencies:
3058
+
typescript: 5.8.2
3059
3060
+
tslib@2.8.1: {}
3061
3062
type-check@0.4.0:
3063
dependencies:
3064
prelude-ls: 1.2.1
3065
3066
+
typed-array-buffer@1.0.3:
3067
+
dependencies:
3068
+
call-bound: 1.0.4
3069
+
es-errors: 1.3.0
3070
+
is-typed-array: 1.1.15
3071
3072
+
typed-array-byte-length@1.0.3:
3073
dependencies:
3074
+
call-bind: 1.0.8
3075
+
for-each: 0.3.5
3076
+
gopd: 1.2.0
3077
+
has-proto: 1.2.0
3078
+
is-typed-array: 1.1.15
3079
3080
+
typed-array-byte-offset@1.0.4:
3081
dependencies:
3082
+
available-typed-arrays: 1.0.7
3083
+
call-bind: 1.0.8
3084
+
for-each: 0.3.5
3085
+
gopd: 1.2.0
3086
+
has-proto: 1.2.0
3087
+
is-typed-array: 1.1.15
3088
+
reflect.getprototypeof: 1.0.10
3089
3090
+
typed-array-length@1.0.7:
3091
dependencies:
3092
+
call-bind: 1.0.8
3093
+
for-each: 0.3.5
3094
+
gopd: 1.2.0
3095
+
is-typed-array: 1.1.15
3096
+
possible-typed-array-names: 1.1.0
3097
+
reflect.getprototypeof: 1.0.10
3098
3099
+
typescript-eslint@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2):
3100
dependencies:
3101
+
'@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
3102
+
'@typescript-eslint/parser': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
3103
+
'@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
3104
+
eslint: 9.23.0(jiti@2.4.2)
3105
+
typescript: 5.8.2
3106
+
transitivePeerDependencies:
3107
+
- supports-color
3108
+
3109
+
typescript@5.8.2: {}
3110
3111
+
ufo@1.5.4: {}
3112
3113
+
unbox-primitive@1.1.0:
3114
dependencies:
3115
+
call-bound: 1.0.4
3116
+
has-bigints: 1.1.0
3117
+
has-symbols: 1.1.0
3118
+
which-boxed-primitive: 1.1.1
3119
3120
+
unconfig@7.3.1:
3121
+
dependencies:
3122
+
'@quansync/fs': 0.1.2
3123
+
defu: 6.1.4
3124
+
jiti: 2.4.2
3125
+
quansync: 0.2.10
3126
+
3127
+
undici-types@6.20.0: {}
3128
3129
+
undici-types@6.21.0: {}
3130
3131
uri-js@4.4.1:
3132
dependencies:
3133
punycode: 2.3.1
3134
3135
+
utilium@1.10.1:
3136
dependencies:
3137
eventemitter3: 5.0.1
3138
+
optionalDependencies:
3139
+
'@xterm/xterm': 5.5.0
3140
3141
+
which-boxed-primitive@1.1.1:
3142
dependencies:
3143
+
is-bigint: 1.1.0
3144
+
is-boolean-object: 1.2.2
3145
+
is-number-object: 1.1.1
3146
+
is-string: 1.1.1
3147
+
is-symbol: 1.1.1
3148
3149
+
which-builtin-type@1.2.1:
3150
dependencies:
3151
+
call-bound: 1.0.4
3152
+
function.prototype.name: 1.1.8
3153
+
has-tostringtag: 1.0.2
3154
+
is-async-function: 2.1.1
3155
+
is-date-object: 1.1.0
3156
+
is-finalizationregistry: 1.1.1
3157
+
is-generator-function: 1.1.0
3158
+
is-regex: 1.2.1
3159
+
is-weakref: 1.1.1
3160
isarray: 2.0.5
3161
+
which-boxed-primitive: 1.1.1
3162
+
which-collection: 1.0.2
3163
+
which-typed-array: 1.1.19
3164
3165
+
which-collection@1.0.2:
3166
dependencies:
3167
+
is-map: 2.0.3
3168
+
is-set: 2.0.3
3169
+
is-weakmap: 2.0.2
3170
+
is-weakset: 2.0.4
3171
3172
+
which-typed-array@1.1.19:
3173
dependencies:
3174
+
available-typed-arrays: 1.0.7
3175
+
call-bind: 1.0.8
3176
+
call-bound: 1.0.4
3177
+
for-each: 0.3.5
3178
+
get-proto: 1.0.1
3179
+
gopd: 1.2.0
3180
+
has-tostringtag: 1.0.2
3181
3182
which@2.0.2:
3183
dependencies:
3184
isexe: 2.0.0
3185
3186
+
yaml@2.7.1: {}
3187
3188
yocto-queue@0.1.0: {}
3189
+
3190
+
zustand@5.0.3(@types/react@18.3.20):
3191
+
optionalDependencies:
3192
+
'@types/react': 18.3.20
+31
-1
pnpm-workspace.yaml
+31
-1
pnpm-workspace.yaml
···
1
packages:
2
+
- packages/*
3
+
4
+
catalogs:
5
+
dev:
6
+
esbuild: ^0.19.3
7
+
esbuild-copy-static-files: ^0.1.0
8
+
"@types/node": ^22.14.0
9
+
"@moonlight-mod/eslint-config": "github:moonlight-mod/eslint-config"
10
+
eslint: ^9.12.0
11
+
"@types/chrome": ^0.0.313
12
+
husky: ^8.0.3
13
+
prettier: ^3.1.0
14
+
typescript: ^5.3.3
15
+
taze: ^19.0.4
16
+
prod:
17
+
"@moonlight-mod/lunast": ^1.0.1
18
+
"@moonlight-mod/mappings": ^1.1.25
19
+
"@moonlight-mod/moonmap": ^1.0.5
20
+
microdiff: ^1.5.0
21
+
nanotar: ^0.1.1
22
+
"@zenfs/core": ^2.0.0
23
+
"@zenfs/dom": ^1.1.3
24
+
25
+
onlyBuiltDependencies:
26
+
- esbuild
27
+
28
+
engineStrict: true
29
+
strictSsl: true
30
+
strictDepBuilds: true
31
+
packageManagerStrict: true
32
+
registry: https://registry.npmjs.org/
-70
scripts/link.js
-70
scripts/link.js
···
1
-
// Janky script to get around pnpm link issues
2
-
// Probably don't use this. Probably
3
-
/* eslint-disable no-console */
4
-
const fs = require("fs");
5
-
const path = require("path");
6
-
const child_process = require("child_process");
7
-
8
-
const onDisk = {
9
-
"@moonlight-mod/lunast": "../lunast",
10
-
"@moonlight-mod/moonmap": "../moonmap",
11
-
"@moonlight-mod/mappings": "../mappings"
12
-
};
13
-
14
-
function exec(cmd, dir) {
15
-
child_process.execSync(cmd, { cwd: dir, stdio: "inherit" });
16
-
}
17
-
18
-
function getDeps(packageJSON) {
19
-
const ret = {};
20
-
Object.assign(ret, packageJSON.dependencies || {});
21
-
Object.assign(ret, packageJSON.devDependencies || {});
22
-
Object.assign(ret, packageJSON.peerDependencies || {});
23
-
return ret;
24
-
}
25
-
26
-
function link(dir) {
27
-
const packageJSON = JSON.parse(
28
-
fs.readFileSync(path.join(dir, "package.json"), "utf8")
29
-
);
30
-
const deps = getDeps(packageJSON);
31
-
32
-
for (const [dep, path] of Object.entries(onDisk)) {
33
-
if (deps[dep]) {
34
-
exec(`pnpm link ${path}`, dir);
35
-
}
36
-
}
37
-
}
38
-
39
-
function undo(dir) {
40
-
exec("pnpm unlink", dir);
41
-
try {
42
-
exec("git restore pnpm-lock.yaml", dir);
43
-
} catch {
44
-
// ignored
45
-
}
46
-
}
47
-
48
-
const shouldUndo = process.argv.includes("--undo");
49
-
const packages = fs.readdirSync("./packages");
50
-
51
-
for (const path of Object.values(onDisk)) {
52
-
console.log(path);
53
-
if (shouldUndo) {
54
-
undo(path);
55
-
} else {
56
-
link(path);
57
-
}
58
-
}
59
-
60
-
if (shouldUndo) {
61
-
const dir = __dirname;
62
-
console.log(dir);
63
-
undo(dir);
64
-
} else {
65
-
for (const pkg of packages) {
66
-
const dir = path.join(__dirname, "packages", pkg);
67
-
console.log(dir);
68
-
link(dir);
69
-
}
70
-
}
···
+78
scripts/link.mjs
+78
scripts/link.mjs
···
···
1
+
// Janky script to get around pnpm link issues
2
+
// Probably don't use this. Probably
3
+
/* eslint-disable no-console */
4
+
const fs = require("fs");
5
+
const path = require("path");
6
+
const child_process = require("child_process");
7
+
8
+
const cwd = process.cwd();
9
+
const onDisk = {
10
+
//"@moonlight-mod/lunast": "../lunast",
11
+
//"@moonlight-mod/moonmap": "../moonmap",
12
+
"@moonlight-mod/mappings": "../mappings"
13
+
};
14
+
15
+
function exec(cmd, dir) {
16
+
child_process.execSync(cmd, { cwd: dir, stdio: "inherit" });
17
+
}
18
+
19
+
function getDeps(packageJSON) {
20
+
const ret = {};
21
+
Object.assign(ret, packageJSON.dependencies || {});
22
+
Object.assign(ret, packageJSON.devDependencies || {});
23
+
Object.assign(ret, packageJSON.peerDependencies || {});
24
+
return ret;
25
+
}
26
+
27
+
function link(dir) {
28
+
const packageJSONPath = path.join(dir, "package.json");
29
+
if (!fs.existsSync(packageJSONPath)) return;
30
+
const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, "utf8"));
31
+
const deps = getDeps(packageJSON);
32
+
33
+
for (const [dep, relativePath] of Object.entries(onDisk)) {
34
+
const fullPath = path.join(cwd, relativePath);
35
+
if (deps[dep]) {
36
+
exec(`pnpm link ${fullPath}`, dir);
37
+
}
38
+
}
39
+
}
40
+
41
+
function undo(dir) {
42
+
exec("pnpm unlink", dir);
43
+
try {
44
+
if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) {
45
+
exec("git restore pnpm-lock.yaml", dir);
46
+
}
47
+
} catch {
48
+
// ignored
49
+
}
50
+
}
51
+
52
+
const shouldUndo = process.argv.includes("--undo");
53
+
const packages = fs.readdirSync("./packages");
54
+
55
+
for (const path of Object.values(onDisk)) {
56
+
console.log(path);
57
+
if (shouldUndo) {
58
+
undo(path);
59
+
} else {
60
+
link(path);
61
+
}
62
+
}
63
+
64
+
if (shouldUndo) {
65
+
console.log(cwd);
66
+
undo(cwd);
67
+
for (const pkg of packages) {
68
+
const dir = path.join(cwd, "packages", pkg);
69
+
console.log(dir);
70
+
undo(dir);
71
+
}
72
+
} else {
73
+
for (const pkg of packages) {
74
+
const dir = path.join(cwd, "packages", pkg);
75
+
console.log(dir);
76
+
link(dir);
77
+
}
78
+
}
-31
scripts/update.js
-31
scripts/update.js
···
1
-
// Update dependencies in all packages
2
-
/* eslint-disable no-console */
3
-
const fs = require("fs");
4
-
const path = require("path");
5
-
const child_process = require("child_process");
6
-
7
-
const packageToUpdate = process.argv[2];
8
-
9
-
function getDeps(packageJSON) {
10
-
const ret = {};
11
-
Object.assign(ret, packageJSON.dependencies || {});
12
-
Object.assign(ret, packageJSON.devDependencies || {});
13
-
Object.assign(ret, packageJSON.peerDependencies || {});
14
-
return ret;
15
-
}
16
-
17
-
function exec(cmd, dir) {
18
-
child_process.execSync(cmd, { cwd: dir, stdio: "inherit" });
19
-
}
20
-
21
-
for (const package of fs.readdirSync("./packages")) {
22
-
const packageJSON = JSON.parse(
23
-
fs.readFileSync(path.join("./packages", package, "package.json"), "utf8")
24
-
);
25
-
26
-
const deps = getDeps(packageJSON);
27
-
if (Object.keys(deps).includes(packageToUpdate)) {
28
-
console.log(`Updating ${packageToUpdate} in ${package}`);
29
-
exec(`pnpm update ${packageToUpdate}`, path.join("./packages", package));
30
-
}
31
-
}
···
+35
tsconfig.base.json
+35
tsconfig.base.json
···
···
1
+
{
2
+
"$schema": "https://json.schemastore.org/tsconfig.json",
3
+
"display": "Base",
4
+
"_version": "1.0.0",
5
+
"compilerOptions": {
6
+
"incremental": true,
7
+
"target": "ES2022",
8
+
"jsx": "react",
9
+
"lib": ["ESNext", "ESNext.Disposable", "DOM", "DOM.Iterable"],
10
+
"module": "ES2020",
11
+
"moduleResolution": "Bundler",
12
+
"resolveJsonModule": true,
13
+
"allowArbitraryExtensions": false,
14
+
"allowImportingTsExtensions": true,
15
+
"allowJs": true,
16
+
"strict": true,
17
+
"strictNullChecks": true,
18
+
19
+
// disable unreachable code detection because it breaks with esbuild labels
20
+
"allowUnreachableCode": true,
21
+
"noFallthroughCasesInSwitch": true,
22
+
"noImplicitReturns": true,
23
+
"declaration": true,
24
+
"declarationMap": true,
25
+
"outDir": "dist",
26
+
"sourceMap": true,
27
+
"stripInternal": true,
28
+
"esModuleInterop": true,
29
+
"forceConsistentCasingInFileNames": true,
30
+
"noErrorTruncation": true,
31
+
"verbatimModuleSyntax": false,
32
+
// meriyah has a broken import lol
33
+
"skipLibCheck": true
34
+
}
35
+
}
+7
-16
tsconfig.json
+7
-16
tsconfig.json
···
1
{
2
"compilerOptions": {
3
-
"target": "es2022",
4
-
"module": "es6",
5
-
"esModuleInterop": true,
6
-
"forceConsistentCasingInFileNames": true,
7
-
"strict": true,
8
-
"moduleResolution": "bundler",
9
"baseUrl": "./packages/",
10
-
"jsx": "react",
11
-
"noEmit": true,
12
-
13
-
// meriyah has a broken import lol
14
-
"skipLibCheck": true,
15
-
16
-
// disable unreachable code detection because it breaks with esbuild labels
17
-
"allowUnreachableCode": true
18
},
19
-
"include": ["./packages/**/*", "./env.d.ts"],
20
-
"exclude": ["node_modules"]
21
}