this repo has no description

Merge pull request #154 from moonlight-mod/kasimir/moonbase-changes

Various changes

authored by notnite.com and committed by GitHub abe6bec3 fc58490d

Changed files
+438 -134
packages
core
src
core-extensions
src
appPanels
common
contextMenu
devToolsExtensions
disableSentry
experiments
markdown
moonbase
nativeFixes
noHideToken
noTrack
notices
quietLoggers
rocketship
settings
spacepack
injector
src
types
+1
packages/core-extensions/package.json
··· 4 4 "dependencies": { 5 5 "@moonlight-mod/core": "workspace:*", 6 6 "@moonlight-mod/types": "workspace:*", 7 + "microdiff": "^1.5.0", 7 8 "nanotar": "^0.1.1" 8 9 } 9 10 }
+1
packages/core-extensions/src/appPanels/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "appPanels", 3 4 "apiLevel": 2, 4 5 "meta": {
+1
packages/core-extensions/src/common/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "common", 3 4 "apiLevel": 2, 4 5 "meta": {
+1
packages/core-extensions/src/contextMenu/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "contextMenu", 3 4 "apiLevel": 2, 4 5 "meta": {
+1
packages/core-extensions/src/devToolsExtensions/manifest.json
··· 13 13 }, 14 14 "settings": { 15 15 "paths": { 16 + "advice": "restart", 16 17 "displayName": "Extension Paths", 17 18 "type": "list" 18 19 }
+1
packages/core-extensions/src/disableSentry/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "disableSentry", 3 4 "apiLevel": 2, 4 5 "meta": {
-3
packages/core-extensions/src/experiments/index.ts
··· 17 17 }, 18 18 19 19 // Enable staff help menu 20 - // FIXME: either make this actually work live (needs a state hook) or just 21 - // wait for #122 22 20 { 23 21 find: ".HEADER_BAR)", 24 22 replace: { ··· 29 27 }, 30 28 31 29 // Enable further staff-locked options 32 - // FIXME: #122, this doesn't work live 33 30 { 34 31 find: "shouldShowLurkerModeUpsellPopout:", 35 32 replace: {
+3
packages/core-extensions/src/experiments/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "experiments", 3 4 "apiLevel": 2, 4 5 "meta": { ··· 9 10 }, 10 11 "settings": { 11 12 "devtools": { 13 + "advice": "reload", 12 14 "displayName": "Enable staff help menu (DevTools)", 13 15 "type": "boolean", 14 16 "default": false 15 17 }, 16 18 "staffSettings": { 19 + "advice": "reload", 17 20 "displayName": "Allow access to other staff settings elsewhere", 18 21 "type": "boolean", 19 22 "default": false
+1
packages/core-extensions/src/markdown/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "markdown", 3 4 "apiLevel": 2, 4 5 "meta": {
+77
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 + 6 + const natives = getNatives(); 7 + 8 + const confirm = (action: string) => 9 + electron.dialog 10 + .showMessageBox({ 11 + title: "Are you sure?", 12 + message: `Are you sure? This will ${action} and restart Discord.`, 13 + type: "warning", 14 + buttons: ["OK", "Cancel"] 15 + }) 16 + .then((r) => r.response === 0); 17 + 18 + async function updateAndRestart() { 19 + if (!(await confirm("update moonlight"))) return; 20 + const newVersion = await natives.checkForMoonlightUpdate(); 21 + 22 + if (newVersion === null) { 23 + electron.dialog.showMessageBox({ message: "You are already on the latest version of moonlight." }); 24 + return; 25 + } 26 + 27 + try { 28 + await natives.updateMoonlight(); 29 + await electron.dialog.showMessageBox({ message: "Update successful, restarting Discord." }); 30 + electron.app.relaunch(); 31 + electron.app.exit(0); 32 + } catch { 33 + await electron.dialog.showMessageBox({ 34 + message: "Failed to update moonlight. Please use the installer instead.", 35 + type: "error" 36 + }); 37 + } 38 + } 39 + 40 + async function resetConfig() { 41 + if (!(await confirm("reset your configuration"))) return; 42 + 43 + const config = await moonlightHost.getConfigPath(); 44 + const dir = path.dirname(config); 45 + const branch = path.basename(config, ".json"); 46 + await fs.rename(config, path.join(dir, `${branch}-backup-${Math.floor(Date.now() / 1000)}.json`)); 47 + 48 + await electron.dialog.showMessageBox({ message: "Configuration reset, restarting Discord." }); 49 + electron.app.relaunch(); 50 + electron.app.exit(0); 51 + } 52 + 53 + function showAbout() { 54 + electron.dialog.showMessageBox({ 55 + title: "About moonlight", 56 + message: `moonlight ${moonlightHost.branch} ${moonlightHost.version}` 57 + }); 58 + } 59 + 60 + electron.app.whenReady().then(() => { 61 + const original = electron.Menu.buildFromTemplate; 62 + electron.Menu.buildFromTemplate = function (entries) { 63 + const i = entries.findIndex((e) => e.label === "Check for Updates..."); 64 + if (i === -1) return original.call(this, entries); 65 + 66 + entries.splice(i + 1, 0, { 67 + label: "moonlight", 68 + submenu: [ 69 + { label: "Update and restart", click: updateAndRestart }, 70 + { label: "Reset config", click: resetConfig }, 71 + { label: "About", click: showAbout } 72 + ] 73 + }); 74 + 75 + return original.call(this, entries); 76 + }; 77 + });
+7 -2
packages/core-extensions/src/moonbase/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "moonbase", 3 4 "apiLevel": 2, 4 5 "meta": { ··· 9 10 "dependencies": ["spacepack", "settings", "common", "notices"], 10 11 "settings": { 11 12 "sections": { 13 + "advice": "reload", 12 14 "displayName": "Split into sections", 13 15 "description": "Show the Moonbase tabs as separate sections", 14 16 "type": "boolean" 15 17 }, 16 18 "saveFilter": { 19 + "advice": "none", 17 20 "displayName": "Persist filter", 18 21 "description": "Save extension filter in config", 19 22 "type": "boolean" 20 23 }, 21 24 "updateChecking": { 25 + "advice": "none", 22 26 "displayName": "Automatic update checking", 23 27 "description": "Checks for updates to moonlight", 24 28 "type": "boolean", 25 - "default": "true" 29 + "default": true 26 30 }, 27 31 "updateBanner": { 32 + "advice": "none", 28 33 "displayName": "Show update banner", 29 34 "description": "Shows a banner for moonlight and extension updates", 30 35 "type": "boolean", 31 - "default": "true" 36 + "default": true 32 37 } 33 38 }, 34 39 "cors": [
+14 -12
packages/core-extensions/src/moonbase/native.ts
··· 4 4 import { distDir, repoUrlFile, installedVersionFile } from "@moonlight-mod/types/constants"; 5 5 import { parseTarGzip } from "nanotar"; 6 6 7 + const moonlightGlobal = globalThis.moonlightHost ?? globalThis.moonlightNode; 8 + 7 9 const githubRepo = "moonlight-mod/moonlight"; 8 10 const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`; 9 11 const artifactName = "dist.tar.gz"; ··· 11 13 const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; 12 14 const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz"; 13 15 14 - export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; 16 + export const userAgent = `moonlight/${moonlightGlobal.version} (https://github.com/moonlight-mod/moonlight)`; 15 17 16 18 async function getStableRelease(): Promise<{ 17 19 name: string; ··· 30 32 } 31 33 32 34 export default function getNatives(): MoonbaseNatives { 33 - const logger = moonlightNode.getLogger("moonbase/natives"); 35 + const logger = moonlightGlobal.getLogger("moonbase/natives"); 34 36 35 37 return { 36 38 async checkForMoonlightUpdate() { 37 39 try { 38 - if (moonlightNode.branch === MoonlightBranch.STABLE) { 40 + if (moonlightGlobal.branch === MoonlightBranch.STABLE) { 39 41 const json = await getStableRelease(); 40 - return json.name !== moonlightNode.version ? json.name : null; 41 - } else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) { 42 + return json.name !== moonlightGlobal.version ? json.name : null; 43 + } else if (moonlightGlobal.branch === MoonlightBranch.NIGHTLY) { 42 44 const req = await fetch(nightlyRefUrl, { 43 45 cache: "no-store", 44 46 headers: { ··· 46 48 } 47 49 }); 48 50 const ref = (await req.text()).split("\n")[0]; 49 - return ref !== moonlightNode.version ? ref : null; 51 + return ref !== moonlightGlobal.version ? ref : null; 50 52 } 51 53 52 54 return null; ··· 96 98 } 97 99 98 100 const [tar, ref] = 99 - moonlightNode.branch === MoonlightBranch.STABLE 101 + moonlightGlobal.branch === MoonlightBranch.STABLE 100 102 ? await downloadStable() 101 - : moonlightNode.branch === MoonlightBranch.NIGHTLY 103 + : moonlightGlobal.branch === MoonlightBranch.NIGHTLY 102 104 ? await downloadNightly() 103 105 : [null, null]; 104 106 105 107 if (!tar || !ref) return; 106 108 107 - const dist = moonlightNodeSandboxed.fs.join(moonlightNode.getMoonlightDir(), distDir); 109 + const dist = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), distDir); 108 110 if (await moonlightNodeSandboxed.fs.exists(dist)) await moonlightNodeSandboxed.fs.rmdir(dist); 109 111 await moonlightNodeSandboxed.fs.mkdir(dist); 110 112 ··· 122 124 } 123 125 124 126 logger.debug("Writing version file:", ref); 125 - const versionFile = moonlightNodeSandboxed.fs.join(moonlightNode.getMoonlightDir(), installedVersionFile); 127 + const versionFile = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), installedVersionFile); 126 128 await moonlightNodeSandboxed.fs.writeFileString(versionFile, ref.trim()); 127 129 128 130 logger.debug("Update extracted"); ··· 157 159 } 158 160 }); 159 161 160 - const dir = moonlightNode.getExtensionDir(manifest.id); 162 + const dir = moonlightGlobal.getExtensionDir(manifest.id); 161 163 // remake it in case of updates 162 164 if (await moonlightNodeSandboxed.fs.exists(dir)) await moonlightNodeSandboxed.fs.rmdir(dir); 163 165 await moonlightNodeSandboxed.fs.mkdir(dir); ··· 176 178 }, 177 179 178 180 async deleteExtension(id) { 179 - const dir = moonlightNode.getExtensionDir(id); 181 + const dir = moonlightGlobal.getExtensionDir(id); 180 182 await moonlightNodeSandboxed.fs.rmdir(dir); 181 183 } 182 184 };
+12
packages/core-extensions/src/moonbase/style.css
··· 7 7 margin-top: 0px; 8 8 } 9 9 10 + .moonbase-retry-button { 11 + padding: 8px; 12 + margin-right: 8px; 13 + } 14 + 10 15 textarea.moonbase-resizeable { 11 16 resize: vertical; 12 17 } ··· 37 42 display: flex; 38 43 flex-direction: row; 39 44 justify-content: space-between; 45 + } 46 + 47 + .moonbase-help-message-sticky { 48 + position: sticky; 49 + top: 24px; 50 + z-index: 10; 51 + background-color: var(--background-primary); 40 52 } 41 53 42 54 .moonbase-extension-update-section {
+8
packages/core-extensions/src/moonbase/types.ts
··· 38 38 Installed, 39 39 Failed 40 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 + }
+2 -3
packages/core-extensions/src/moonbase/webpackModules/settings.tsx
··· 1 1 import settings from "@moonlight-mod/wp/settings_settings"; 2 2 import React from "@moonlight-mod/wp/react"; 3 3 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 4 - import { Moonbase, pages } from "@moonlight-mod/wp/moonbase_ui"; 4 + import { Moonbase, pages, RestartAdviceMessage, Update } from "@moonlight-mod/wp/moonbase_ui"; 5 5 6 6 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 7 7 import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 8 - 9 - import Update from "./ui/update"; 10 8 11 9 const { MenuItem, Text, Breadcrumbs } = Components; 12 10 ··· 72 70 {page.name} 73 71 </Breadcrumbs> 74 72 73 + <RestartAdviceMessage /> 75 74 <Update /> 76 75 77 76 <page.element />
+168 -72
packages/core-extensions/src/moonbase/webpackModules/stores.ts
··· 1 - import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; 2 - import { ExtensionState, MoonbaseExtension, MoonbaseNatives, RepositoryManifest } from "../types"; 1 + import { Config, ExtensionEnvironment, ExtensionLoadSource, ExtensionSettingsAdvice } from "@moonlight-mod/types"; 2 + import { ExtensionState, MoonbaseExtension, MoonbaseNatives, RepositoryManifest, RestartAdvice } from "../types"; 3 3 import { Store } from "@moonlight-mod/wp/discord/packages/flux"; 4 4 import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; 5 5 import getNatives from "../native"; ··· 7 7 import { checkExtensionCompat, ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 8 8 import { CustomComponent } from "@moonlight-mod/types/coreExtensions/moonbase"; 9 9 import { getConfigOption, setConfigOption } from "@moonlight-mod/core/util/config"; 10 + import diff from "microdiff"; 10 11 11 12 const logger = moonlight.getLogger("moonbase"); 12 13 ··· 14 15 if (moonlightNode.isBrowser) natives = getNatives(); 15 16 16 17 class MoonbaseSettingsStore extends Store<any> { 17 - private origConfig: Config; 18 + private initialConfig: Config; 19 + private savedConfig: Config; 18 20 private config: Config; 19 21 private extensionIndex: number; 20 22 private configComponents: Record<string, Record<string, CustomComponent>> = {}; ··· 35 37 return this.#showOnlyUpdateable; 36 38 } 37 39 40 + restartAdvice = RestartAdvice.NotNeeded; 41 + 38 42 extensions: { [id: number]: MoonbaseExtension }; 39 43 updates: { 40 44 [id: number]: { ··· 47 51 constructor() { 48 52 super(Dispatcher); 49 53 50 - this.origConfig = moonlightNode.config; 51 - this.config = this.clone(this.origConfig); 54 + this.initialConfig = moonlightNode.config; 55 + this.savedConfig = moonlightNode.config; 56 + this.config = this.clone(this.savedConfig); 52 57 this.extensionIndex = 0; 53 58 54 59 this.modified = false; ··· 71 76 }; 72 77 } 73 78 74 - natives! 75 - .fetchRepositories(this.config.repositories) 76 - .then((ret) => { 77 - for (const [repo, exts] of Object.entries(ret)) { 78 - try { 79 - for (const ext of exts) { 80 - const uniqueId = this.extensionIndex++; 81 - const extensionData = { 82 - id: ext.id, 83 - uniqueId, 84 - manifest: ext, 85 - source: { type: ExtensionLoadSource.Normal, url: repo }, 86 - state: ExtensionState.NotDownloaded, 87 - compat: ExtensionCompat.Compatible, 88 - hasUpdate: false 89 - }; 79 + this.checkUpdates(); 80 + } 90 81 91 - // Don't present incompatible updates 92 - if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue; 82 + async checkUpdates() { 83 + await Promise.all([this.checkExtensionUpdates(), this.checkMoonlightUpdates()]); 84 + this.shouldShowNotice = this.newVersion != null || Object.keys(this.updates).length > 0; 85 + this.emitChange(); 86 + } 93 87 94 - const existing = this.getExisting(extensionData); 95 - if (existing != null) { 96 - // Make sure the download URL is properly updated 97 - for (const [id, e] of Object.entries(this.extensions)) { 98 - if (e.id === ext.id && e.source.url === repo) { 99 - this.extensions[parseInt(id)].manifest = { 100 - ...e.manifest, 101 - download: ext.download 102 - }; 103 - break; 104 - } 105 - } 88 + private async checkExtensionUpdates() { 89 + const repositories = await natives!.fetchRepositories(this.savedConfig.repositories); 106 90 107 - if (this.hasUpdate(extensionData)) { 108 - this.updates[existing.uniqueId] = { 109 - version: ext.version!, 110 - download: ext.download, 111 - updateManifest: ext 112 - }; 113 - existing.hasUpdate = true; 114 - existing.changelog = ext.meta?.changelog; 115 - } 91 + // Reset update state 92 + for (const id in this.extensions) { 93 + const ext = this.extensions[id]; 94 + ext.hasUpdate = false; 95 + ext.changelog = undefined; 96 + } 97 + this.updates = {}; 116 98 117 - continue; 118 - } 99 + for (const [repo, exts] of Object.entries(repositories)) { 100 + for (const ext of exts) { 101 + const uniqueId = this.extensionIndex++; 102 + const extensionData = { 103 + id: ext.id, 104 + uniqueId, 105 + manifest: ext, 106 + source: { type: ExtensionLoadSource.Normal, url: repo }, 107 + state: ExtensionState.NotDownloaded, 108 + compat: ExtensionCompat.Compatible, 109 + hasUpdate: false 110 + }; 119 111 120 - this.extensions[uniqueId] = extensionData; 121 - } 122 - } catch (e) { 123 - logger.error(`Error processing repository ${repo}`, e); 112 + // Don't present incompatible updates 113 + if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue; 114 + 115 + const existing = this.getExisting(extensionData); 116 + if (existing != null) { 117 + // Make sure the download URL is properly updated 118 + existing.manifest = { 119 + ...existing.manifest, 120 + download: ext.download 121 + }; 122 + 123 + if (this.hasUpdate(extensionData)) { 124 + this.updates[existing.uniqueId] = { 125 + version: ext.version!, 126 + download: ext.download, 127 + updateManifest: ext 128 + }; 129 + existing.hasUpdate = true; 130 + existing.changelog = ext.meta?.changelog; 124 131 } 132 + } else { 133 + this.extensions[uniqueId] = extensionData; 125 134 } 135 + } 136 + } 137 + } 126 138 127 - this.emitChange(); 128 - }) 129 - .then(() => 130 - this.getExtensionConfigRaw("moonbase", "updateChecking", true) 131 - ? natives!.checkForMoonlightUpdate() 132 - : new Promise<null>((resolve) => resolve(null)) 133 - ) 134 - .then((version) => { 135 - this.newVersion = version; 136 - this.emitChange(); 137 - }) 138 - .then(() => { 139 - this.shouldShowNotice = this.newVersion != null || Object.keys(this.updates).length > 0; 140 - this.emitChange(); 141 - }); 139 + private async checkMoonlightUpdates() { 140 + this.newVersion = this.getExtensionConfigRaw("moonbase", "updateChecking", true) 141 + ? await natives!.checkForMoonlightUpdate() 142 + : null; 142 143 } 143 144 144 145 private getExisting(ext: MoonbaseExtension) { ··· 154 155 155 156 // Jank 156 157 private isModified() { 157 - const orig = JSON.stringify(this.origConfig); 158 + const orig = JSON.stringify(this.savedConfig); 158 159 const curr = JSON.stringify(this.config); 159 160 return orig !== curr; 160 161 } ··· 269 270 } 270 271 271 272 if (update != null) { 272 - this.extensions[uniqueId].settingsOverride = update.updateManifest.settings; 273 - this.extensions[uniqueId].compat = checkExtensionCompat(update.updateManifest); 273 + const existing = this.extensions[uniqueId]; 274 + existing.settingsOverride = update.updateManifest.settings; 275 + existing.compat = checkExtensionCompat(update.updateManifest); 276 + existing.manifest = update.updateManifest; 277 + existing.hasUpdate = false; 278 + existing.changelog = update.updateManifest.meta?.changelog; 274 279 } 275 280 276 281 delete this.updates[uniqueId]; ··· 279 284 } 280 285 281 286 this.installing = false; 287 + this.restartAdvice = this.#computeRestartAdvice(); 282 288 this.emitChange(); 283 289 } 284 290 ··· 310 316 const aRank = this.getRank(a); 311 317 const bRank = this.getRank(b); 312 318 if (aRank === bRank) { 313 - const repoIndex = this.config.repositories.indexOf(a.source.url!); 314 - const otherRepoIndex = this.config.repositories.indexOf(b.source.url!); 319 + const repoIndex = this.savedConfig.repositories.indexOf(a.source.url!); 320 + const otherRepoIndex = this.savedConfig.repositories.indexOf(b.source.url!); 315 321 return repoIndex - otherRepoIndex; 316 322 } else { 317 323 return bRank - aRank; ··· 335 341 } 336 342 337 343 this.installing = false; 344 + this.restartAdvice = this.#computeRestartAdvice(); 338 345 this.emitChange(); 339 346 } 340 347 ··· 366 373 return this.configComponents[ext]?.[name]; 367 374 } 368 375 376 + #computeRestartAdvice() { 377 + const i = this.initialConfig; // Initial config, from startup 378 + const n = this.config; // New config about to be saved 379 + 380 + let returnedAdvice = RestartAdvice.NotNeeded; 381 + const updateAdvice = (r: RestartAdvice) => (returnedAdvice < r ? (returnedAdvice = r) : returnedAdvice); 382 + 383 + // Top-level keys, repositories is not needed here because Moonbase handles it. 384 + if (i.patchAll !== n.patchAll) updateAdvice(RestartAdvice.ReloadNeeded); 385 + if (i.loggerLevel !== n.loggerLevel) updateAdvice(RestartAdvice.ReloadNeeded); 386 + if (diff(i.devSearchPaths ?? [], n.devSearchPaths ?? [], { cyclesFix: false }).length !== 0) 387 + return updateAdvice(RestartAdvice.RestartNeeded); 388 + 389 + // Extension specific logic 390 + for (const id in n.extensions) { 391 + // Installed extension (might not be detected yet) 392 + const ext = Object.values(this.extensions).find((e) => e.id === id && e.state !== ExtensionState.NotDownloaded); 393 + // Installed and detected extension 394 + const detected = moonlightNode.extensions.find((e) => e.id === id); 395 + 396 + // If it's not installed at all, we don't care 397 + if (!ext) continue; 398 + 399 + const initState = i.extensions[id]; 400 + const newState = n.extensions[id]; 401 + 402 + const newEnabled = typeof newState === "boolean" ? newState : newState.enabled; 403 + // If it's enabled but not detected yet, restart. 404 + if (newEnabled && !detected) { 405 + return updateAdvice(RestartAdvice.RestartNeeded); 406 + continue; 407 + } 408 + 409 + // Toggling extensions specifically wants to rely on the initial state, 410 + // that's what was considered when loading extensions. 411 + const initEnabled = initState && (typeof initState === "boolean" ? initState : initState.enabled); 412 + if (initEnabled !== newEnabled) { 413 + // If we have the extension locally, we confidently know if it has host/preload scripts. 414 + // If not, we have to respect the environment specified in the manifest. 415 + // If that is the default, we can't know what's needed. 416 + 417 + if (detected?.scripts.hostPath || detected?.scripts.nodePath) { 418 + return updateAdvice(RestartAdvice.RestartNeeded); 419 + } 420 + 421 + switch (ext.manifest.environment) { 422 + case ExtensionEnvironment.Both: 423 + case ExtensionEnvironment.Web: 424 + updateAdvice(RestartAdvice.ReloadNeeded); 425 + continue; 426 + case ExtensionEnvironment.Desktop: 427 + return updateAdvice(RestartAdvice.RestartNeeded); 428 + default: 429 + updateAdvice(RestartAdvice.ReloadNeeded); 430 + continue; 431 + } 432 + } 433 + 434 + const initConfig = typeof initState === "boolean" ? {} : initState.config ?? {}; 435 + const newConfig = typeof newState === "boolean" ? {} : newState.config ?? {}; 436 + 437 + const def = ext.manifest.settings; 438 + if (!def) continue; 439 + 440 + const changedKeys = diff(initConfig, newConfig, { cyclesFix: false }).map((c) => c.path[0]); 441 + for (const key in def) { 442 + if (!changedKeys.includes(key)) continue; 443 + 444 + const advice = def[key].advice; 445 + switch (advice) { 446 + case ExtensionSettingsAdvice.None: 447 + updateAdvice(RestartAdvice.NotNeeded); 448 + continue; 449 + case ExtensionSettingsAdvice.Reload: 450 + updateAdvice(RestartAdvice.ReloadNeeded); 451 + continue; 452 + case ExtensionSettingsAdvice.Restart: 453 + updateAdvice(RestartAdvice.RestartNeeded); 454 + continue; 455 + default: 456 + updateAdvice(RestartAdvice.ReloadSuggested); 457 + } 458 + } 459 + } 460 + 461 + return returnedAdvice; 462 + } 463 + 369 464 writeConfig() { 370 465 this.submitting = true; 466 + this.restartAdvice = this.#computeRestartAdvice(); 371 467 372 468 moonlightNode.writeConfig(this.config); 373 - this.origConfig = this.clone(this.config); 469 + this.savedConfig = this.clone(this.config); 374 470 375 471 this.submitting = false; 376 472 this.modified = false; ··· 380 476 reset() { 381 477 this.submitting = false; 382 478 this.modified = false; 383 - this.config = this.clone(this.origConfig); 479 + this.config = this.clone(this.savedConfig); 384 480 this.emitChange(); 385 481 } 386 482
+4 -2
packages/core-extensions/src/moonbase/webpackModules/ui/HelpMessage.tsx
··· 11 11 className, 12 12 text, 13 13 icon, 14 - children 14 + children, 15 + type = "info" 15 16 }: { 16 17 className?: string; 17 18 text: string; 18 19 icon: React.ComponentType<any>; 20 + type?: "warning" | "positive" | "error" | "info"; 19 21 children?: React.ReactNode; 20 22 }) { 21 23 return ( 22 24 <div 23 - className={`${Margins.marginBottom20} ${HelpMessageClasses.info} ${HelpMessageClasses.container} moonbase-help-message ${className}`} 25 + className={`${Margins.marginBottom20} ${HelpMessageClasses[type]} ${HelpMessageClasses.container} moonbase-help-message ${className}`} 24 26 > 25 27 <Flex direction={Flex.Direction.HORIZONTAL}> 26 28 <div
+47
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 * as Components 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 { Button } = Components; 9 + 10 + const strings: Record<RestartAdvice, string> = { 11 + [RestartAdvice.NotNeeded]: "how did you even", 12 + [RestartAdvice.ReloadSuggested]: "A reload might be needed to apply some of the changed options.", 13 + [RestartAdvice.ReloadNeeded]: "A reload is needed to apply some of the changed options.", 14 + [RestartAdvice.RestartNeeded]: "A restart is needed to apply some of the changed options." 15 + }; 16 + 17 + const buttonStrings: Record<RestartAdvice, string> = { 18 + [RestartAdvice.NotNeeded]: "huh?", 19 + [RestartAdvice.ReloadSuggested]: "Reload", 20 + [RestartAdvice.ReloadNeeded]: "Reload", 21 + [RestartAdvice.RestartNeeded]: "Restart" 22 + }; 23 + 24 + const actions: Record<RestartAdvice, () => void> = { 25 + [RestartAdvice.NotNeeded]: () => {}, 26 + [RestartAdvice.ReloadSuggested]: () => window.location.reload(), 27 + [RestartAdvice.ReloadNeeded]: () => window.location.reload(), 28 + [RestartAdvice.RestartNeeded]: () => MoonbaseSettingsStore.restartDiscord() 29 + }; 30 + 31 + const { CircleWarningIcon } = Components; 32 + 33 + export default function RestartAdviceMessage() { 34 + const restartAdvice = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.restartAdvice); 35 + 36 + if (restartAdvice === RestartAdvice.NotNeeded) return null; 37 + 38 + return ( 39 + <div className="moonbase-help-message-sticky"> 40 + <HelpMessage text={strings[restartAdvice]} icon={CircleWarningIcon} type="warning"> 41 + <Button color={Button.Colors.YELLOW} size={Button.Sizes.TINY} onClick={actions[restartAdvice]}> 42 + {buttonStrings[restartAdvice]} 43 + </Button> 44 + </HelpMessage> 45 + </div> 46 + ); 47 + }
+2 -17
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
··· 23 23 24 24 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 25 25 26 - const { BeakerIcon, DownloadIcon, TrashIcon, AngleBracketsIcon, CircleWarningIcon, Tooltip } = Components; 26 + const { BeakerIcon, DownloadIcon, TrashIcon, AngleBracketsIcon, Tooltip } = Components; 27 27 28 28 const PanelButton = spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.Z; 29 29 const TabBarClasses = spacepack.findByExports("tabBar", "tabBarItem", "headerContentWrapper")[0].exports; ··· 40 40 41 41 export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { 42 42 const [tab, setTab] = React.useState(ExtensionPage.Info); 43 - const [restartNeeded, setRestartNeeded] = React.useState(false); 44 43 45 - const { ext, enabled, busy, update, conflicting, showingNotice } = useStateFromStores([MoonbaseSettingsStore], () => { 44 + const { ext, enabled, busy, update, conflicting } = useStateFromStores([MoonbaseSettingsStore], () => { 46 45 return { 47 46 ext: MoonbaseSettingsStore.getExtension(uniqueId), 48 47 enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId), 49 48 busy: MoonbaseSettingsStore.busy, 50 - showingNotice: MoonbaseSettingsStore.showNotice(), 51 49 update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId), 52 50 conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId) 53 51 }; ··· 111 109 /> 112 110 )} 113 111 114 - {restartNeeded && !showingNotice && ( 115 - <PanelButton 116 - icon={() => <CircleWarningIcon color={Components.tokens.colors.STATUS_DANGER} />} 117 - onClick={() => MoonbaseSettingsStore.restartDiscord()} 118 - tooltipText="You will need to reload/restart your client for this extension to work properly." 119 - /> 120 - )} 121 - 122 112 {ext.state === ExtensionState.NotDownloaded ? ( 123 113 <Tooltip 124 114 text={conflicting ? CONFLICTING_TEXT : COMPAT_TEXT_MAP[ext.compat]} ··· 141 131 if (!ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) { 142 132 MoonbaseSettingsStore.setExtensionEnabled(uniqueId, true); 143 133 } 144 - 145 - setRestartNeeded(true); 146 134 }} 147 135 > 148 136 Install ··· 157 145 tooltipText="Delete" 158 146 onClick={() => { 159 147 MoonbaseSettingsStore.deleteExtension(uniqueId); 160 - setRestartNeeded(true); 161 148 }} 162 149 /> 163 150 )} ··· 168 155 tooltipText="Update" 169 156 onClick={() => { 170 157 MoonbaseSettingsStore.installExtension(uniqueId); 171 - setRestartNeeded(true); 172 158 }} 173 159 /> 174 160 )} ··· 190 176 onChange={() => { 191 177 const toggle = () => { 192 178 MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled); 193 - setRestartNeeded(true); 194 179 }; 195 180 196 181 if (enabled && constants.builtinExtensions.includes(ext.id)) {
+33 -2
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
··· 16 16 MenuItem 17 17 } from "@moonlight-mod/wp/discord/components/common/index"; 18 18 import * as Components from "@moonlight-mod/wp/discord/components/common/index"; 19 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 19 20 20 21 export enum Filter { 21 22 Core = 1 << 0, ··· 44 45 const TagItem = spacepack.findByCode('"forum-tag-"')[0].exports.Z; 45 46 46 47 // FIXME: type component keys 47 - const { ChevronSmallDownIcon, ChevronSmallUpIcon, ArrowsUpDownIcon } = Components; 48 + const { ChevronSmallDownIcon, ChevronSmallUpIcon, ArrowsUpDownIcon, RetryIcon, Tooltip } = Components; 48 49 49 50 function toggleTag(selectedTags: Set<string>, setSelectedTags: (tags: Set<string>) => void, tag: string) { 50 51 const newState = new Set(selectedTags); ··· 211 212 } 212 213 } 213 214 setTagsButtonOffset(offset); 214 - }, [windowSize]); 215 + }, [windowSize, tagsContainer.current, tagListInner.current, tagListInner.current?.getBoundingClientRect()?.width]); 215 216 216 217 return ( 217 218 <div ··· 221 222 }} 222 223 className={`${FilterBarClasses.tagsContainer} ${Margins.marginBottom8}`} 223 224 > 225 + <Tooltip text="Refresh updates" position="top"> 226 + {(props: any) => ( 227 + <Button 228 + {...props} 229 + size={Button.Sizes.MIN} 230 + color={Button.Colors.CUSTOM} 231 + className={`${FilterBarClasses.sortDropdown} moonbase-retry-button`} 232 + innerClassName={FilterBarClasses.sortDropdownInner} 233 + onClick={() => MoonbaseSettingsStore.checkUpdates()} 234 + > 235 + <RetryIcon size={"custom"} width={16} /> 236 + </Button> 237 + )} 238 + </Tooltip> 224 239 <Popout 225 240 renderPopout={({ closePopout }: any) => ( 226 241 <FilterButtonPopout filter={filter} setFilter={setFilter} closePopout={closePopout} /> ··· 305 320 </Button> 306 321 )} 307 322 </Popout> 323 + <Button 324 + size={Button.Sizes.MIN} 325 + color={Button.Colors.CUSTOM} 326 + className={`${FilterBarClasses.tagsButton} ${FilterBarClasses.tagsButtonPlaceholder}`} 327 + innerClassName={FilterBarClasses.tagsButtonInner} 328 + > 329 + {selectedTags.size > 0 ? ( 330 + <div style={{ boxSizing: "content-box" }} className={FilterBarClasses.countContainer}> 331 + <Text className={FilterBarClasses.countText} color="none" variant="text-xs/medium"> 332 + {selectedTags.size} 333 + </Text> 334 + </div> 335 + ) : null} 336 + 337 + <ChevronSmallUpIcon size={"custom"} width={20} /> 338 + </Button> 308 339 </div> 309 340 ); 310 341 }
+9 -13
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
··· 13 13 import HelpMessage from "../HelpMessage"; 14 14 15 15 const SearchBar: any = Object.values(spacepack.findByCode("hideSearchIcon")[0].exports)[0]; 16 - const { CircleInformationIcon, XSmallIcon } = Components; 16 + const { FormDivider, CircleInformationIcon, XSmallIcon } = Components; 17 17 const PanelButton = spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.Z; 18 18 19 19 export default function ExtensionsPage() { ··· 77 77 78 78 // Prioritize extensions with updates 79 79 const filteredWithUpdates = filtered.filter((ext) => ext!.hasUpdate); 80 - const filteredWithoutUpdates = filtered.filter((ext) => !ext!.hasUpdate); 81 - const { FormDivider } = Components; 80 + const filterUpdates = showOnlyUpdateable && filteredWithUpdates.length > 0; 81 + const filteredWithoutUpdates = filterUpdates ? [] : filtered.filter((ext) => !ext!.hasUpdate); 82 82 83 83 return ( 84 84 <> ··· 97 97 /> 98 98 <FilterBar filter={filter} setFilter={setFilter} selectedTags={selectedTags} setSelectedTags={setSelectedTags} /> 99 99 100 - {showOnlyUpdateable && ( 100 + {filterUpdates && ( 101 101 <HelpMessage 102 102 icon={CircleInformationIcon} 103 103 text="Only displaying updates" ··· 117 117 {filteredWithUpdates.map((ext) => ( 118 118 <ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} /> 119 119 ))} 120 - {!showOnlyUpdateable && ( 121 - <> 122 - {filteredWithUpdates.length > 0 && filteredWithoutUpdates.length > 0 && ( 123 - <FormDivider className="moonbase-update-divider" /> 124 - )} 125 - {filteredWithoutUpdates.map((ext) => ( 126 - <ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} /> 127 - ))} 128 - </> 120 + {filteredWithUpdates.length > 0 && filteredWithoutUpdates.length > 0 && ( 121 + <FormDivider className="moonbase-update-divider" /> 129 122 )} 123 + {filteredWithoutUpdates.map((ext) => ( 124 + <ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} /> 125 + ))} 130 126 </> 131 127 ); 132 128 }
+4
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
··· 8 8 import ConfigPage from "./config"; 9 9 import Update from "./update"; 10 10 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 11 + import RestartAdviceMessage from "./RestartAdvice"; 11 12 12 13 const { Divider } = spacepack.findByCode(".forumOrHome]:")[0].exports.Z; 13 14 const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0].exports; ··· 66 67 </TabBar> 67 68 </div> 68 69 70 + <RestartAdviceMessage /> 69 71 <Update /> 70 72 71 73 {React.createElement(pages[subsection].element)} 72 74 </> 73 75 ); 74 76 } 77 + 78 + export { RestartAdviceMessage, Update };
+7
packages/core-extensions/src/nativeFixes/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "nativeFixes", 3 4 "meta": { 4 5 "name": "Native Fixes", ··· 6 7 "authors": ["Cynosphere", "adryd"], 7 8 "tags": ["fixes"] 8 9 }, 10 + "environment": "desktop", 9 11 "settings": { 10 12 "devtoolsThemeFix": { 13 + "advice": "restart", 11 14 "displayName": "Devtools Theme Fix", 12 15 "description": "Temporary workaround for devtools defaulting to light theme on Electron 32", 13 16 "type": "boolean", 14 17 "default": true 15 18 }, 16 19 "disableRendererBackgrounding": { 20 + "advice": "restart", 17 21 "displayName": "Disable Renderer Backgrounding", 18 22 "description": "This is enabled by default as a power saving measure, but it breaks screensharing and websocket connections fairly often", 19 23 "type": "boolean", 20 24 "default": true 21 25 }, 22 26 "linuxAutoscroll": { 27 + "advice": "restart", 23 28 "displayName": "Enable middle click autoscroll on Linux", 24 29 "description": "Requires manual configuration of your system to disable middle click paste, has no effect on other operating systems", 25 30 "type": "boolean", 26 31 "default": false 27 32 }, 28 33 "linuxSpeechDispatcher": { 34 + "advice": "restart", 29 35 "displayName": "Enable speech-dispatcher for TTS on Linux", 30 36 "description": "Fixes text-to-speech. Has no effect on other operating systems", 31 37 "type": "boolean", 32 38 "default": true 33 39 }, 34 40 "vaapi": { 41 + "advice": "restart", 35 42 "displayName": "Enable VAAPI features on Linux", 36 43 "description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems", 37 44 "type": "boolean",
+1
packages/core-extensions/src/noHideToken/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "noHideToken", 3 4 "apiLevel": 2, 4 5 "meta": {
+1
packages/core-extensions/src/noTrack/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "noTrack", 3 4 "apiLevel": 2, 4 5 "meta": {
+1
packages/core-extensions/src/notices/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "notices", 3 4 "apiLevel": 2, 4 5 "meta": {
+2
packages/core-extensions/src/quietLoggers/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "quietLoggers", 3 4 "apiLevel": 2, 4 5 "meta": { ··· 9 10 }, 10 11 "settings": { 11 12 "xssDefensesOnly": { 13 + "advice": "reload", 12 14 "displayName": "Only hide self-XSS", 13 15 "description": "Only disable self XSS prevention log", 14 16 "type": "boolean",
+2
packages/core-extensions/src/rocketship/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "rocketship", 3 4 "apiLevel": 2, 5 + "environment": "desktop", 4 6 "meta": { 5 7 "name": "Rocketship", 6 8 "tagline": "Adds new features when using rocketship",
+1
packages/core-extensions/src/settings/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "settings", 3 4 "apiLevel": 2, 4 5 "meta": {
+2
packages/core-extensions/src/spacepack/manifest.json
··· 1 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 2 3 "id": "spacepack", 3 4 "apiLevel": 2, 4 5 "meta": { ··· 9 10 }, 10 11 "settings": { 11 12 "addToGlobalScope": { 13 + "advice": "reload", 12 14 "displayName": "Add to global scope", 13 15 "description": "Populates window.spacepack for easier usage in DevTools", 14 16 "type": "boolean",
+1 -1
packages/core/src/patch.ts
··· 101 101 } 102 102 } 103 103 104 - let modified = false; 105 104 for (const [id, func] of Object.entries(entry)) { 106 105 if (func.__moonlight === true) continue; 107 106 ··· 110 109 let moduleString = origModuleString; 111 110 const patchedStr = []; 112 111 const mappedName = moonlight.moonmap.modules[id]; 112 + let modified = false; 113 113 114 114 for (let i = 0; i < patches.length; i++) { 115 115 const patch = patches[i];
+2 -2
packages/injector/src/index.ts
··· 15 15 import persist from "@moonlight-mod/core/persist"; 16 16 import createFS from "@moonlight-mod/core/fs"; 17 17 import { getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config"; 18 - import { getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data"; 18 + import { getConfigPath, getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data"; 19 19 20 20 const logger = new Logger("injector"); 21 21 ··· 223 223 if (val == null || typeof val === "boolean") return undefined; 224 224 return val.config; 225 225 } 226 - 227 226 global.moonlightHost = { 228 227 get config() { 229 228 return config; ··· 237 236 branch: MOONLIGHT_BRANCH as MoonlightBranch, 238 237 239 238 getConfig, 239 + getConfigPath, 240 240 getConfigOption(ext, name) { 241 241 const manifest = getManifest(extensions, ext); 242 242 return getConfigOption(ext, name, config, manifest?.settings);
+7
packages/types/src/config.ts
··· 81 81 default?: any; 82 82 }; 83 83 84 + export enum ExtensionSettingsAdvice { 85 + None = "none", 86 + Reload = "reload", 87 + Restart = "restart" 88 + } 89 + 84 90 export type ExtensionSettingsManifest = { 85 91 displayName?: string; 86 92 description?: string; 93 + advice?: ExtensionSettingsAdvice; 87 94 } & ( 88 95 | BooleanSettingType 89 96 | NumberSettingType
+1
packages/types/src/globals.ts
··· 18 18 branch: MoonlightBranch; 19 19 20 20 getConfig: (ext: string) => ConfigExtension["config"]; 21 + getConfigPath: () => Promise<string>; 21 22 getConfigOption: <T>(ext: string, name: string) => T | undefined; 22 23 setConfigOption: <T>(ext: string, name: string, value: T) => void; 23 24 writeConfig: (config: Config) => Promise<void>;
+13 -5
pnpm-lock.yaml
··· 10 10 devDependencies: 11 11 '@moonlight-mod/eslint-config': 12 12 specifier: github:moonlight-mod/eslint-config 13 - version: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/7eb7bd7c51fe0e3ee9d2a0baf149212d2bb893af(eslint@9.12.0)(prettier@3.1.0)(typescript@5.3.2) 13 + version: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9(eslint@9.12.0)(prettier@3.1.0)(typescript@5.3.2) 14 14 esbuild: 15 15 specifier: ^0.19.3 16 16 version: 0.19.3 ··· 62 62 '@moonlight-mod/types': 63 63 specifier: workspace:* 64 64 version: link:../types 65 + microdiff: 66 + specifier: ^1.5.0 67 + version: 1.5.0 65 68 nanotar: 66 69 specifier: ^0.1.1 67 70 version: 0.1.1 ··· 311 314 resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} 312 315 engines: {node: '>=18.18'} 313 316 314 - '@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/7eb7bd7c51fe0e3ee9d2a0baf149212d2bb893af': 315 - resolution: {tarball: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/7eb7bd7c51fe0e3ee9d2a0baf149212d2bb893af} 316 - version: 1.0.0 317 + '@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9': 318 + resolution: {tarball: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9} 319 + version: 1.0.1 317 320 peerDependencies: 318 321 eslint: '>= 9' 319 322 typescript: '>= 5.3' ··· 1003 1006 meriyah@6.0.1: 1004 1007 resolution: {integrity: sha512-OyvYIOgpzXREySYJ1cqEb2pOKdeQMTfF9M8dRU6nC4hi/GXMmNpe9ssZCrSoTHazu05BSAoRBN/uYeco+ymfOg==} 1005 1008 engines: {node: '>=18.0.0'} 1009 + 1010 + microdiff@1.5.0: 1011 + resolution: {integrity: sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==} 1006 1012 1007 1013 micromatch@4.0.8: 1008 1014 resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} ··· 1433 1439 1434 1440 '@humanwhocodes/retry@0.3.1': {} 1435 1441 1436 - '@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/7eb7bd7c51fe0e3ee9d2a0baf149212d2bb893af(eslint@9.12.0)(prettier@3.1.0)(typescript@5.3.2)': 1442 + '@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9(eslint@9.12.0)(prettier@3.1.0)(typescript@5.3.2)': 1437 1443 dependencies: 1438 1444 '@eslint/js': 9.12.0 1439 1445 eslint: 9.12.0 ··· 2293 2299 merge2@1.4.1: {} 2294 2300 2295 2301 meriyah@6.0.1: {} 2302 + 2303 + microdiff@1.5.0: {} 2296 2304 2297 2305 micromatch@4.0.8: 2298 2306 dependencies: