diff --git a/.vscode/settings.json b/.vscode/settings.json index b47ac46d..d2600188 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,5 +25,11 @@ ], "css.validate": false, "tailwindCSS.validate": true, - "tailwindCSS.emmetCompletions": true + "tailwindCSS.emmetCompletions": true, + "typescript.preferences.autoImportSpecifierExcludeRegexes": [ + "^server/" + ], + "javascript.preferences.autoImportSpecifierExcludeRegexes": [ + "^server/" + ] } \ No newline at end of file diff --git a/application/electron.vite.config.ts b/application/electron.vite.config.ts index 4995788d..401c3f36 100644 --- a/application/electron.vite.config.ts +++ b/application/electron.vite.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ '@': resolve(__dirname, 'src'), }, }, - plugins: [externalizeDepsPlugin({ exclude: ['ogi-addon', 'webtorrent'] })], + plugins: [externalizeDepsPlugin({ exclude: ['ogi-addon'] })], build: { rollupOptions: { input: { diff --git a/application/package.json b/application/package.json index 9ec40d39..cd7699f4 100644 --- a/application/package.json +++ b/application/package.json @@ -32,19 +32,20 @@ }, "scripts": { "build": "electron-vite build", + "build:executor": "cd ../packages/executor && bun run build", "build:ogiaddon": "cd ../packages/ogi-addon/ && bun x tsdown && cd ../real-debrid/ && bun x tsdown && cd ../all-debrid/ && bun x tsdown", - "dev": "bun run build:ogiaddon && ELECTRON_EXEC_PATH=\"$(node -p \"require('electron')\" )\" electron-vite dev", + "dev": "bun run build:executor && bun run build:ogiaddon && ELECTRON_EXEC_PATH=\"$(node -p \"require('electron')\" )\" electron-vite dev", "preview": "electron-vite preview", "dev:server": "nodemon --watch server --ext ts,json --exec \"tsc --p tsconfig.addonserver.json && bun run --bun build-addons/dev-server.js\"", - "preelectron-pack": "bun run build:ogiaddon && bun run build", + "preelectron-pack": "bun run build:executor && bun run build:ogiaddon && bun run build", "electron-pack": "electron-builder", "format": "prettier --write \"src/**/*.{ts,svelte,json}\"", "electron-pack:linux": "electron-builder -l", - "check": "bun run build:ogiaddon && svelte-check --tsconfig ./tsconfig.svelte.json", + "check": "bun run build:executor && bun run build:ogiaddon && svelte-check --tsconfig ./tsconfig.svelte.json", "typecheck": "bun run typecheck:svelte && bun run typecheck:electron && bun run typecheck:addonserver", - "typecheck:svelte": "svelte-check --tsconfig ./tsconfig.svelte.json", - "typecheck:electron": "tsc -p tsconfig.electron.json --noEmit", - "typecheck:addonserver": "tsc -p tsconfig.addonserver.json --noEmit", + "typecheck:svelte": "svelte-check --tsconfig ./tsconfig.svelte.json --threshold error", + "typecheck:electron": "tsc -p tsconfig.electron.json --noEmit --pretty false", + "typecheck:addonserver": "tsc -p tsconfig.addonserver.json --noEmit --pretty false", "rebuild": "npm rebuild" }, "dependencies": { @@ -62,7 +63,9 @@ "node-datachannel": "^0.32.0", "ogi-addon": "workspace:*", "parse-torrent": "^11.0.19", - "real-debrid-js": "*", + "real-debrid-js": "workspace:*", + "@ogi-sdk/addon-server": "workspace:*", + "@ogi-sdk/executor": "workspace:*", "sanitize-html": "^2.17.0", "semver": "^7.7.3", "shell-quote": "^1.8.3", diff --git a/application/src/electron/handlers/handler.addon.ts b/application/src/electron/handlers/handler.addon.ts index d81e0418..e77c775e 100644 --- a/application/src/electron/handlers/handler.addon.ts +++ b/application/src/electron/handlers/handler.addon.ts @@ -2,16 +2,16 @@ import { BrowserWindow, ipcMain } from 'electron'; import fs from 'fs'; import { join } from 'path'; import { exec, spawn } from 'child_process'; -import { - processes, - setupAddon, - startAddon, -} from '@/electron/manager/manager.addon.js'; +import { Addon } from '@/electron/manager/manager.addon.js'; import { __dirname } from '@/electron/manager/manager.paths.js'; -import { server, clients, port } from '@/electron/server/addon-server.js'; +import { + addonServer, + port, + startAddonServer, +} from '@/electron/server/addon-server.js'; import { sendNotification } from '@/electron/main.js'; import axios from 'axios'; -import { AddonConnection } from '@/electron/server/AddonConnection.js'; +import { AddonConnection } from '@ogi-sdk/addon-server'; export async function startAddons(): Promise { // start all of the addons @@ -48,7 +48,15 @@ export async function startAddons(): Promise { } console.log(`Starting addon ${addonPath}`); - promises.push(startAddon(addonPath, addon)); + promises.push( + (async () => { + const instance = await Addon.load(addonPath); + if (!instance) { + return undefined; + } + return instance.startRegistered(addon); + })() + ); } await Promise.allSettled(promises); console.log('All addons started'); @@ -57,24 +65,16 @@ export async function startAddons(): Promise { export async function restartAddonServer(): Promise { // stop the server console.log('Stopping server...'); - server.close(); - clients.clear(); + addonServer.stop(); // stop all of the addons - for (const process of Object.keys(processes)) { - console.log(`Killing process ${process}`); - const killed = processes[process].kill('SIGKILL'); - console.log(`Killed process ${process}: ${killed}`); + for (const instance of [...Addon.running.values()]) { + console.log(`Stopping addon ${instance.config.path}`); + instance.stop(); } // start the server and wait for it to be listening before starting addons - await new Promise((resolve, reject) => { - server.once('error', reject); - server.listen(port, () => { - server.removeListener('error', reject); - console.log(`Addon Server is running on http://localhost:${port}`); - console.log(`Server is being executed by electron!`); - resolve(); - }); - }); + await startAddonServer(); + console.log(`Addon Server is running on http://localhost:${port}`); + console.log(`Server is being executed by electron!`); await startAddons(); sendNotification({ @@ -185,7 +185,8 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) { }); } - const hasAddonBeenSetup = await setupAddon(addonPath); + const instance = await Addon.load(addonPath); + const hasAddonBeenSetup = instance ? await instance.install() : false; if (!hasAddonBeenSetup) { sendNotification({ message: `An error occurred when setting up ${addonName}`, @@ -209,10 +210,9 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) { }); ipcMain.handle('clean-addons', async (_) => { - // stop all of the addons - for (const process of Object.keys(processes)) { - console.log(`Killing process ${process}`); - processes[process].kill('SIGKILL'); + for (const instance of [...Addon.running.values()]) { + console.log(`Stopping addon ${instance.config.path}`); + instance.stop(); } // delete all of the addons @@ -243,10 +243,9 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) { return; } - // stop all of the addons - for (const process of Object.keys(processes)) { - console.log(`Killing process ${process}`); - processes[process].kill('SIGKILL'); + for (const instance of [...Addon.running.values()]) { + console.log(`Stopping addon ${instance.config.path}`); + instance.stop(); } // pull all of the addons @@ -322,9 +321,13 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) { }); mainWindow!!.webContents.send('addon:updated', addon); - // setup the addon - setupAddon(addonPath) - .then((success) => { + void Addon.load(addonPath).then(async (instance) => { + if (!instance) { + reject(new Error(`Failed to load addon ${addon}`)); + return; + } + try { + const success = await instance.install(); if (!success) { sendNotification({ message: `An error occurred when setting up ${addon}`, @@ -336,15 +339,15 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) { } console.log(`Addon ${addon} updated successfully.`); resolve(); - }) - .catch((setupErr) => { + } catch (setupErr) { sendNotification({ message: `An error occurred when setting up ${addon}`, id: Math.random().toString(36).substring(7), type: 'error', }); reject(setupErr); - }); + } + }); } ); }); diff --git a/application/src/electron/handlers/handler.app.ts b/application/src/electron/handlers/handler.app.ts index 37633d54..d4751abd 100644 --- a/application/src/electron/handlers/handler.app.ts +++ b/application/src/electron/handlers/handler.app.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { ipcMain, app } from 'electron'; -import { currentScreens } from '@/electron/main.js'; +import { currentScreens, screenInputCallbacks } from '@/electron/main.js'; import * as fs from 'fs'; import { join } from 'path'; import * as os from 'os'; @@ -8,12 +8,12 @@ import * as path from 'path'; import { createReadStream, createWriteStream } from 'fs'; import { isDev } from '@/electron/manager/manager.paths.js'; import { __dirname } from '@/electron/manager/manager.paths.js'; -import { clients } from '@/electron/server/addon-server.js'; import { registerSteamHandlers } from '@/electron/handlers/handler.steam.js'; import { registerLibraryHandlers } from '@/electron/handlers/handler.library.js'; import { registerRedistributableHandlers } from '@/electron/handlers/handler.redists.js'; import { getCurrentUsername } from './helpers.app/platform.js'; import { getEffectiveOnlineState } from '@/electron/lib/online.js'; +import { addonServer } from '@/electron/server/addon-server.js'; /** * Escapes a string for safe use in shell commands by escaping special characters @@ -189,6 +189,11 @@ export default function handler(mainWindow: Electron.BrowserWindow) { }); ipcMain.handle('app:screen-input', async (_, data) => { currentScreens.set(data.id, data.data); + const callback = screenInputCallbacks.get(data.id); + if (callback) { + callback(data.data); + screenInputCallbacks.delete(data.id); + } return; }); @@ -198,14 +203,14 @@ export default function handler(mainWindow: Electron.BrowserWindow) { // Addon helpers ipcMain.handle('app:get-addon-path', async (_, addonID: string) => { - let client = clients.get(addonID); + let client = addonServer.getClient(addonID); if (!client || !client.filePath) { return null; } return client.filePath; }); ipcMain.handle('app:get-addon-icon', async (_, addonID: string) => { - let client = clients.get(addonID); + let client = addonServer.getClient(addonID); if (!client || !client.filePath) { return null; } @@ -239,9 +244,9 @@ export default function handler(mainWindow: Electron.BrowserWindow) { join(__dirname, 'public'), join(__dirname, 'config'), // for each addon, add the basedir of the addon to the allowed dirs - ...Array.from(clients.values()) - .filter((client) => client.filePath) - .map((client) => client.filePath + '/'), + ...Array.from(addonServer.getConnections().values()) + .filter((connection) => connection.filePath) + .map((connection) => connection.filePath + '/'), ]; let realPath: string; diff --git a/application/src/electron/handlers/handler.rest.ts b/application/src/electron/handlers/handler.rest.ts index 454e257e..6e68a9f7 100644 --- a/application/src/electron/handlers/handler.rest.ts +++ b/application/src/electron/handlers/handler.rest.ts @@ -1,6 +1,6 @@ import { ipcMain } from 'electron'; -import { addonServer } from '@/electron/server/addon-server.js'; -import { requestSchema } from '@/electron/server/serve.js'; +import { addonIPC } from '@/electron/server/addon-server.js'; +import { requestSchema } from '@/electron/server/ipc.js'; export default function handler() { ipcMain.handle('addon:request', async (_, request) => { @@ -12,7 +12,7 @@ export default function handler() { } const parsedRequest = parsedRequestSafe.data; - const response = await addonServer.handleRequest(parsedRequest); + const response = await addonIPC.handleRequest(parsedRequest); if (response.tag === 'defer') { return { status: response.status, diff --git a/application/src/electron/main.ts b/application/src/electron/main.ts index 5ed6a5a0..610337df 100644 --- a/application/src/electron/main.ts +++ b/application/src/electron/main.ts @@ -1,16 +1,19 @@ import { join } from 'path'; import { quote as shellQuote } from 'shell-quote'; import { - addonServer, registerInstanceBridgeHandlers, + registerAddonServerUIHandlers, server, port, type LaunchForwardPayload, + addonIPC, + isSecurityCheckEnabled, + startAddonServer, + addonServer, } from '@/electron/server/addon-server.js'; -import { applicationAddonSecret } from '@/electron/server/constants.js'; import { app, BrowserWindow, globalShortcut, ipcMain, shell } from 'electron'; import fs, { existsSync, readFileSync } from 'fs'; -import { processes } from '@/electron/manager/manager.addon.js'; +import { Addon } from '@/electron/manager/manager.addon.js'; import { stopClient } from '@/electron/manager/manager.webtorrent.js'; import type { ConfigurationFile } from 'ogi-addon/config'; import AppEventHandler from '@/electron/handlers/handler.app.js'; @@ -25,6 +28,7 @@ import { checkForAddonUpdates, convertLibrary, IS_NIXOS, + startupEnvironmentReady, startUmuBackgroundUpdater, stopUmuBackgroundUpdater, STEAMTINKERLAUNCH_PATH, @@ -198,8 +202,8 @@ async function handleLaunchHooks( // Load the main app with game ID and hook flags const baseUrl = isDev() - ? `http://localhost:8080/?secret=${applicationAddonSecret}` - : `file://${join(app.getAppPath(), 'out', 'renderer', 'index.html')}?secret=${applicationAddonSecret}`; + ? `http://localhost:8080` + : `file://${join(app.getAppPath(), 'out', 'renderer', 'index.html')}`; // Add flags to indicate this is a hook-only launch const launchUrl = `${baseUrl}&launchGameId=${gameId}&hookType=${hookType}&noLaunch=true`; @@ -234,8 +238,8 @@ async function launchGameById(gameId: number, wrapperCommand?: string | null) { // Load the main app with the game ID in the query params // The Svelte frontend will detect this and show the GameLaunchOverlay const baseUrl = isDev() - ? `http://localhost:8080/?secret=${applicationAddonSecret}` - : `file://${join(app.getAppPath(), 'out', 'renderer', 'index.html')}?secret=${applicationAddonSecret}`; + ? `http://localhost:8080` + : `file://${join(app.getAppPath(), 'out', 'renderer', 'index.html')}`; console.log('Direct wrapper command: ' + wrapperCommand); @@ -255,20 +259,6 @@ async function launchGameById(gameId: number, wrapperCommand?: string | null) { export const VERSION = app.getVersion(); -export let isSecurityCheckEnabled = true; -if (existsSync(join(__dirname, 'config/option/developer.json'))) { - const developerConfig = JSON.parse( - readFileSync(join(__dirname, 'config/option/developer.json'), 'utf-8') - ); - isSecurityCheckEnabled = developerConfig.disableSecretCheck !== true; - if (!isSecurityCheckEnabled) { - for (let i = 0; i < 10; i++) { - console.warn( - 'WARNING Security check is disabled. THIS IS A MAJOR SECURITY RISK. PLEASE ENABLE DURING NORMAL USE.' - ); - } - } -} // check if NixOS using command -v nixos-rebuild console.log('continuing launch...'); console.log('NIXOS: ' + IS_NIXOS); @@ -372,11 +362,14 @@ export let currentScreens = new Map< { [key: string]: string | boolean | number } | undefined >(); +export let screenInputCallbacks = new Map void>(); + export function sendAskForInput( id: string, config: ConfigurationFile, name: string, - description: string + description: string, + callback: (result: any) => void ) { if (!mainWindow) { console.error('Main window is not ready yet. Cannot send ask for input.'); @@ -390,8 +383,14 @@ export function sendAskForInput( } mainWindow.webContents.send('input-asked', { id, config, name, description }); currentScreens.set(id, undefined); + screenInputCallbacks.set(id, callback); } +registerAddonServerUIHandlers({ + onInputAsked: sendAskForInput, + onNotification: sendNotification, +}); + /** * Single-window flow for Steam Deck / Game Mode: one BrowserWindow shows splash first, then the main app. * This avoids Steam focusing a separate splash window and leaving the main window black. @@ -436,26 +435,20 @@ function registerClientReadyListener() { async function ensureAddonServerRunning() { if (server.listening) return; - await new Promise((resolve, reject) => { - const onError = (error: NodeJS.ErrnoException) => { - if (error.code === 'EADDRINUSE') { - console.warn( - `[addon-server] Port ${port} is already in use, continuing startup` - ); - resolve(); - return; - } - reject(error); - }; - - server.once('error', onError); - server.listen(port, () => { - server.removeListener('error', onError); - console.log(`Addon Server is running on http://localhost:${port}`); - console.log(`Server is being executed by electron!`); - resolve(); - }); - }); + try { + await startAddonServer(); + console.log(`Addon Server is running on http://localhost:${port}`); + console.log(`Server is being executed by electron!`); + } catch (error) { + const err = error as NodeJS.ErrnoException; + if (err.code === 'EADDRINUSE') { + console.warn( + `[addon-server] Port ${port} is already in use, continuing startup` + ); + return; + } + throw error; + } } async function startAddonRuntime() { @@ -576,7 +569,10 @@ function createWindow(options: { gameLaunchMode?: boolean } = {}) { // Load splash first so there is only one window (fixes Steam Deck Game Mode black screen) mainWindow.loadURL( - 'file://' + join(app.getAppPath(), 'public', 'splash.html') + 'file://' + + join(app.getAppPath(), 'public', 'splash.html') + + '?secret=' + + addonServer.getSecret() ); mainWindow.on('closed', function () { @@ -604,14 +600,14 @@ async function startAppFlow(win: BrowserWindow) { // Load the main app into the same window (replaces splash) if (win && !win.isDestroyed()) { if (isDev()) { - win.loadURL('http://localhost:8080/?secret=' + applicationAddonSecret); + win.loadURL('http://localhost:8080'); console.log('Running in development'); } else { win.loadURL( 'file://' + join(app.getAppPath(), 'out', 'renderer', 'index.html') + '?secret=' + - applicationAddonSecret + addonServer.getSecret() ); } win.once('ready-to-show', onMainAppReady); @@ -662,7 +658,7 @@ async function runAddonLaunchEvent( return { success: false, error: 'Game not found in library' }; } - const response = await addonServer.handleRequest({ + const response = await addonIPC.handleRequest({ method: 'launchApp', params: { libraryInfo, @@ -792,6 +788,7 @@ registerInstanceBridgeHandlers({ // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', async () => { + await startupEnvironmentReady; registerClientReadyListener(); // Check if we're launching a specific game (--game-id flag from Steam) @@ -900,10 +897,9 @@ app.on('window-all-closed', async function () { console.log('Stopping torrent client...'); await stopClient(); - // stop all of the addons - for (const process of Object.keys(processes)) { - console.log(`Killing process ${process}`); - processes[process].kill('SIGKILL'); + for (const instance of [...Addon.running.values()]) { + console.log(`Stopping addon ${instance.config.path}`); + instance.stop(); } // stopping all of the torrent intervals diff --git a/application/src/electron/manager/manager.addon.ts b/application/src/electron/manager/manager.addon.ts index 251ec7e2..bd08c781 100644 --- a/application/src/electron/manager/manager.addon.ts +++ b/application/src/electron/manager/manager.addon.ts @@ -1,323 +1,175 @@ import { readFile, writeFile } from 'fs/promises'; import { join } from 'path'; import { sendNotification } from '@/electron/main.js'; -import z from 'zod'; -import exec from 'child_process'; -import { addonSecret } from '@/electron/server/constants.js'; -import { clients } from '@/electron/server/addon-server.js'; -import { AddonConnection } from '@/electron/server/AddonConnection.js'; - -export let processes: { - [key: string]: exec.ChildProcess; -} = {}; +import { + Addon as ExecutorAddon, + AddonFileConfigurationSchema, +} from '@ogi-sdk/executor'; +import type { AddonConnection } from '@ogi-sdk/addon-server'; +import { addonServer, port } from '@/electron/server/addon-server.js'; + +export class Addon extends ExecutorAddon { + /** Paths of addons currently started by the host (for shutdown / restart). */ + static readonly running = new Map(); + + private static stripAnsi(input: string): string { + return input.replace(/\x1b\[[0-9;]*m/g, ''); + } -export const AddonFileConfigurationSchema = z.object({ - author: z.string(), - scripts: z.object({ - setup: z.string().optional(), - run: z.string(), - preSetup: z.string().optional(), - postSetup: z.string().optional(), - }), -}); + static async load(addonPath: string): Promise { + const addonName = + addonPath.replace(/\/$/, '').split(/[/\\]/).pop() ?? 'unknown-addon'; -function stripAnsiCodes(input: string): string { - // Regular expression to match ANSI escape codes - const ansiRegex = /\x1b\[[0-9;]*m/g; - return input.replace(ansiRegex, ''); -} + let addonConfig: string; + try { + addonConfig = await readFile(join(addonPath, 'addon.json'), 'utf-8'); + } catch (err: any) { + if (err.code === 'ENOENT') { + sendNotification({ + type: 'error', + message: `Addon configuration not found for ${addonName}`, + id: Math.random().toString(36).substring(7), + }); + } else { + sendNotification({ + type: 'error', + message: `Error reading addon configuration for ${addonName}: ${err.message}`, + id: Math.random().toString(36).substring(7), + }); + } + return null; + } -async function loadAddonConfig( - addonPath: string, - addonName: string -): Promise | null> { - let addonConfig: string; - try { - addonConfig = await readFile(join(addonPath, 'addon.json'), 'utf-8'); - } catch (err: any) { - if (err.code === 'ENOENT') { + let addonJSON: unknown; + try { + addonJSON = JSON.parse(addonConfig); + } catch (err: any) { sendNotification({ type: 'error', - message: `Addon configuration not found for ${addonName}`, + message: `Failed to parse addon configuration JSON for ${addonName}: ${err.message}`, id: Math.random().toString(36).substring(7), }); - } else { + return null; + } + + try { + const parsed = AddonFileConfigurationSchema.parse(addonJSON); + return new Addon({ + port, + secret: addonServer.getSecret(), + path: addonPath, + name: addonName, + scripts: parsed.scripts, + }); + } catch (err: any) { sendNotification({ type: 'error', - message: `Error reading addon configuration for ${addonName}: ${err.message}`, + message: `Addon configuration validation failed for ${addonName}: ${err.message}`, id: Math.random().toString(36).substring(7), }); + return null; } - return null; } - let addonJSON: any; - try { - addonJSON = JSON.parse(addonConfig); - } catch (err: any) { - sendNotification({ - type: 'error', - message: `Failed to parse addon configuration JSON for ${addonName}: ${err.message}`, - id: Math.random().toString(36).substring(7), - }); - return null; + override stop(): void { + super.stop(); + Addon.running.delete(this.config.path); } - try { - return AddonFileConfigurationSchema.parse(addonJSON); - } catch (err: any) { + async install(): Promise { sendNotification({ - type: 'error', - message: `Addon configuration validation failed for ${addonName}: ${err.message}`, + type: 'info', + message: 'Setting up ' + this.config.name, id: Math.random().toString(36).substring(7), }); - return null; - } -} -export async function setupAddon(addonPath: string): Promise { - const addonName = addonPath.split(/\/|\\/).pop() ?? 'unknown-addon'; - const addon = await loadAddonConfig(addonPath, addonName); - if (!addon) { - return false; - } - - let setupLogs = ''; - sendNotification({ - type: 'info', - message: 'Setting up ' + addonName, - id: Math.random().toString(36).substring(7), - }); - if (addon.scripts.preSetup) { try { - setupLogs += ` -Running pre-setup script for ${addonName}... -> ${addon.scripts.preSetup} - `; - setupLogs += await executeScript( - 'pre-setup', - addon.scripts.preSetup, - addonPath, - addonName + const setupLogs = await this.setup.collectSetupLog(); + await writeFile( + join(this.config.path, 'installation.log'), + Addon.stripAnsi(setupLogs) ); - } catch (e) { + return true; + } catch { sendNotification({ type: 'error', - message: 'Error running pre-setup script for ' + addonName, + message: 'Error running setup scripts for ' + this.config.name, id: Math.random().toString(36).substring(7), }); return false; } } - if (addon.scripts.setup) { + async startRegistered(addonLink: string): Promise { + const addonName = this.config.name; + const addonPath = this.config.path; + try { - setupLogs += ` -Running setup script for ${addonName}... -> ${addon.scripts.setup} - `; - setupLogs += await executeScript( - 'setup', - addon.scripts.setup, - addonPath, - addonName - ); - } catch (e) { - sendNotification({ - type: 'error', - message: 'Error running setup script for ' + addonName, - id: Math.random().toString(36).substring(7), + this.start(); + + const child = this.getChildProcess(); + if (!child) { + return; + } + Addon.running.set(addonPath, this); + + let attempts = 0; + const success = await new Promise((resolve) => { + const interval = setInterval(() => { + if (attempts > 10) { + clearInterval(interval); + console.error( + 'Addon ' + + addonName + + ' not found in clients. Cannot attach path nor link.' + ); + resolve(false); + return; + } + + if (addonServer.getClient(addonName)) { + clearInterval(interval); + resolve(true); + return; + } + attempts++; + }, 500); }); - return false; - } - } - if (addon.scripts.postSetup) { - try { - setupLogs += ` -Running post-setup script for ${addonName}... -> ${addon.scripts.postSetup} - `; - setupLogs += await executeScript( - 'post-setup', - addon.scripts.postSetup, - addonPath, - addonName - ); + if (!success) { + return; + } + + const client = addonServer.getClient(addonName); + if (client) { + client.filePath = addonPath; + client.addonLink = addonLink; + console.log( + 'Registered addon identifier path for ' + + addonName + + ' to ' + + addonPath + + ' with link ' + + addonLink + ); + } + return client; } catch (e) { + console.error(e); + + const errorMessage = e instanceof Error ? e.message : String(e); + await writeFile( + join(addonPath, 'run-crash.log'), + Addon.stripAnsi(errorMessage) + ); + sendNotification({ type: 'error', - message: 'Error running post-setup script for ' + addonName, + message: + 'Error running run script for ' + addonPath.split(/[/\\]/).pop(), id: Math.random().toString(36).substring(7), }); - return false; - } - } - - await writeFile( - join(addonPath, 'installation.log'), - stripAnsiCodes(setupLogs) - ); - return true; -} - -export async function startAddon( - addonPath: string, - addonLink: string -): Promise { - // remove any trailing slashes - const addonName = - addonPath.replace(/\/$/, '').split(/\/|\\/).pop() ?? 'unknown-addon'; - - const addon = await loadAddonConfig(addonPath, addonName); - if (!addon) { - return; - } - try { - executeScript( - 'run', - addon.scripts.run + ' --addonSecret=' + addonSecret, - addonPath, - addonName - ); - let attempts = 0; - const success = await new Promise((resolve) => { - const interval = setInterval(() => { - if (attempts > 10) { - clearInterval(interval); - console.error( - 'Addon ' + - addonName + - ' not found in clients. Cannot attach path nor link.' - ); - resolve(false); - return; - } - - if (clients.has(addonName!)) { - clearInterval(interval); - resolve(true); - return; - } - attempts++; - }, 500); - }); - if (!success) { return; } - let client = clients.get(addonName!); - if (client) { - client.filePath = addonPath; - client.addonLink = addonLink; - console.log( - 'Registered addon identifier path for ' + - addonName + - ' to ' + - addonPath + - ' with link ' + - addonLink - ); - } - return client; - } catch (e) { - console.error(e); - - // write to the run-crash.log file - const errorMessage = e instanceof Error ? e.message : String(e); - await writeFile( - join(addonPath, 'run-crash.log'), - stripAnsiCodes(errorMessage) - ); - - sendNotification({ - type: 'error', - message: 'Error running run script for ' + addonPath.split(/[/\\]/).pop(), - id: Math.random().toString(36).substring(7), - }); - return; } } - -async function executeScript( - scriptName: string, - script: string, - addonPath: string, - addonName: string -): Promise { - return new Promise((resolve, reject) => { - let bunPath = ''; - // if on windows, then use C:\Users\username\.bun\bin\bun.exe - // if on linux, then use ~/.bun/bin/bun - if (process.platform === 'win32') { - if (!process.env.USERPROFILE) { - sendNotification({ - type: 'error', - message: 'USERPROFILE is not set. Cannot run scripts.', - id: Math.random().toString(36).substring(7), - }); - return reject(); - } - bunPath = join(process.env.USERPROFILE || '', '.bun', 'bin', 'bun.exe'); - } else { - if (!process.env.HOME) { - sendNotification({ - type: 'error', - message: 'HOME is not set. Cannot run scripts.', - id: Math.random().toString(36).substring(7), - }); - return reject(); - } - bunPath = join(process.env.HOME || '', '.bun', 'bin', 'bun'); - } - - let finalScript = script.trim(); - // handles bun, ./bun, .\\bun, bun.exe and replaces it with the full path to bun - // using quotes to handle spaces in the path - finalScript = finalScript.replace( - /^(\.?[\\/]?bun(?:.exe)?)\b/, - `"${bunPath}"` - ); - - const child = exec.exec(finalScript, { - cwd: addonPath, - }); - - let stdout = ''; - let stderr = ''; - - processes[addonPath] = child; - - child.stdout?.on('data', (data: Buffer) => { - console.log('[' + addonName + '@' + scriptName + '] ' + data.toString()); - stdout += data.toString(); - }); - - child.stderr?.on('data', (data: Buffer) => { - console.error( - '[' + addonName + '@' + scriptName + '] ' + data.toString() - ); - stderr += data.toString(); - }); - - child.on('close', (code: number) => { - if (code !== 0) { - // write the error to a log file - console.error( - '[' + addonName + '@' + scriptName + '] Exited with error: ' + code - ); - reject( - new Error( - 'Addon ' + addonName + ' exited with error: ' + code + '\n' + stderr - ) - ); - return; - } - resolve(stdout); - }); - - child.on('error', (err: Error) => { - console.error(err); - reject(err); - }); - }); -} diff --git a/application/src/electron/server/AddonConnection.ts b/application/src/electron/server/AddonConnection.ts deleted file mode 100644 index 714bd026..00000000 --- a/application/src/electron/server/AddonConnection.ts +++ /dev/null @@ -1,505 +0,0 @@ -import wsLib from 'ws'; -import type { - ClientSentEventTypes, - OGIAddonConfiguration, - OGIAddonEvent, - StoreData, - WebsocketMessageClient, - WebsocketMessageServer, -} from 'ogi-addon'; -import type { ConfigurationFile } from 'ogi-addon/config'; -import { clients } from '@/electron/server/addon-server.js'; -import { addonSecret } from '@/electron/server/constants.js'; -import { - currentScreens, - isSecurityCheckEnabled, - sendAskForInput, - sendIPCMessage, - sendNotification, -} from '@/electron/main.js'; -import { - DeferrableTask, - DeferredTasks, -} from '@/electron/server/DeferrableTask.js'; -import { supportsStorefront } from '@/lib/storefronts.js'; - -export class AddonConnection { - public addonInfo: OGIAddonConfiguration | undefined; - public ws: InstanceType; - public configTemplate: ConfigurationFile | undefined; - public filePath: string | undefined; - public addonLink: string | undefined; - public eventsAvailable: OGIAddonEvent[] = []; - private pendingResponses: Map< - string, - { - resolve: (value: WebsocketMessageClient) => void; - reject: (reason?: any) => void; - } - > = new Map(); - private messageHandler: ((message: string | Buffer) => void) | null = null; - constructor(ws: InstanceType) { - this.ws = ws; - } - - public async setupWebsocket(): Promise { - return new Promise((resolve, _) => { - const authenticationTimeout = setTimeout(() => { - this.ws.close(1008, 'Authentication timeout'); - console.error('Client kicked due to authentication timeout'); - resolve(false); - }, 1000); - - // Set up persistent message handler - this.messageHandler = async (message: string | Buffer) => { - let data: WebsocketMessageClient; - try { - data = JSON.parse(message.toString()); - } catch (err) { - console.error('Failed to parse websocket message:', err); - this.ws.close(1008, 'Invalid JSON message'); - return; - } - - // Check if this is a response to a pending request - if ( - data.event === 'response' && - data.id && - this.pendingResponses.has(data.id) - ) { - const pending = this.pendingResponses.get(data.id)!; - this.pendingResponses.delete(data.id); - if (!data.args || data.statusError) { - if (!data.args && !data.statusError) { - pending.resolve({ - event: 'response', - args: undefined, - id: data.id, - }); - } else { - pending.reject(data.statusError); - } - return; - } - pending.resolve(data); - return; - } - - // Handle other message types - switch (data.event) { - case 'notification': { - sendNotification(data.args[0]); - break; - } - case 'authenticate': { - clearTimeout(authenticationTimeout); - - // authentication - const addonInfo = data.args as OGIAddonConfiguration; - this.addonInfo = addonInfo; - if ( - isSecurityCheckEnabled && - (!data.args.secret || data.args.secret !== addonSecret) - ) { - console.error( - 'Client attempted to authenticate with an invalid secret' - ); - this.ws.close( - 1008, - 'Client attempted to authenticate with an invalid secret' - ); - resolve(false); - break; - } - - // if (addonInfo.version !== ogiAddonVERSION) { - // sendNotification({ - // type: 'error', - // message: 'Client attempted to authenticate with an addon version that is not compatible with the OGI Addon Server', - // id: 'addon-version-mismatch' - // }); - // console.error('Client attempted to authenticate with an addon version that is not compatible with the OGI Addon Server'); - // this.ws.close(1008, 'Client attempted to authenticate with an addon version that is not compatible with the OGI Addon Server'); - // resolve(false) - // break; - // } - if (clients.has(addonInfo.id)) { - console.error( - 'Client attempted to authenticate with an ID that is already in use' - ); - this.ws.close( - 1008, - 'Client attempted to authenticate with an ID that is already in use' - ); - resolve(false); - break; - } - console.log('Client authenticated:', data.args.name); - clients.set(addonInfo.id, this); - sendIPCMessage('addon-connected', addonInfo.id); - resolve(true); - break; - } - case 'configure': { - if (!this.addonInfo) { - console.error( - 'Client attempted to send config before authentication' - ); - this.ws.close( - 1008, - 'Client attempted to send config before authentication' - ); - return; - } - this.configTemplate = data.args; - break; - } - case 'defer-update': { - if (!this.addonInfo) { - console.error( - 'Client attempted to send defer-update before authentication' - ); - this.ws.close( - 1008, - 'Client attempted to send defer-update before authentication' - ); - return; - } - if (!data.args) return; - - if (!data.args.deferID) { - console.error( - 'Client attempted to send defer-update without an ID' - ); - this.ws.close( - 1008, - 'Client attempted to send defer-update without an ID' - ); - return; - } - const deferredTask = DeferredTasks.getTasks()[data.args.deferID]; - if (!deferredTask) { - console.error( - 'Client attempted to send defer-update with an invalid ID' - ); - this.ws.close( - 1008, - 'Client attempted to send defer-update with an invalid ID' - ); - return; - } - if (deferredTask.addonOwner !== this.addonInfo!.id) { - console.error( - 'Client attempted to send defer-update with an ID that does not belong to them' - ); - this.ws.close( - 1008, - 'Client attempted to send defer-update with an ID that does not belong to them' - ); - return; - } - deferredTask.logs = data.args.logs; - deferredTask.progress = data.args.progress; - if (data.args.failed) { - deferredTask.failed = data.args.failed; - deferredTask.finished = true; - } - break; - } - case 'input-asked': { - if (!this.addonInfo) { - console.error( - 'Client attempted to send input-asked before authentication' - ); - this.ws.close( - 1008, - 'Client attempted to send input-asked before authentication' - ); - return; - } - if (!data.args) return; - if ( - !data.args.config || - !data.args.name || - !data.args.description - ) { - console.error( - 'Client attempted to send input-asked without a configuration' - ); - this.ws.close( - 1008, - 'Client attempted to send input-asked without a configuration' - ); - return; - } - - if (!data.id) { - console.error( - 'Client attempted to send input-asked without an ID' - ); - this.ws.close( - 1008, - 'Client attempted to send input-asked without an ID' - ); - return; - } - const configurationAsked = data.args.config as - | ConfigurationFile - | undefined; - const name = data.args.name as string; - const description = data.args.description as string; - if (!configurationAsked || !name || !description) { - console.error( - 'Client attempted to send input-asked without a configuration' - ); - this.ws.close( - 1008, - 'Client attempted to send input-asked without a configuration' - ); - return; - } - - sendAskForInput(data.id, configurationAsked, name, description); - const waitForClient = setInterval(() => { - const screenData = currentScreens.get(data.id!!); - if (screenData) { - clearInterval(waitForClient); - currentScreens.delete(data.id!!); - this.sendEventMessage( - { event: 'response', args: screenData, id: data.id!! }, - false - ); - } - }, 100); - - break; - } - case 'task-update': { - if (!this.addonInfo) { - console.error( - 'Client attempted to send task-update before authentication' - ); - this.ws.close( - 1008, - 'Client attempted to send task-update before authentication' - ); - return; - } - if (!data.args.id) { - console.error( - 'Client attempted to send task-update without an ID' - ); - this.ws.close( - 1008, - 'Client attempted to send task-update without an ID' - ); - return; - } - const taskUpdate = data.args as ClientSentEventTypes['task-update']; - let task = DeferredTasks.getTasks()[data.args.id]; - - if (!task) { - task = new DeferrableTask(async () => { - return null; - }, this.addonInfo!.id); - DeferredTasks.getTasks()[data.args.id] = task; - // sendNotification({ - // type: 'info', - // message: 'Task started by ' + this.addonInfo.name, - // id: data.args.id, - // }); - } - task.progress = taskUpdate.progress; - task.logs = taskUpdate.logs; - task.finished = taskUpdate.finished; - task.failed = taskUpdate.failed; - - if (taskUpdate.failed) { - task.finished = true; - // sendNotification({ - // type: 'error', - // message: 'Task failed by ' + this.addonInfo.name, - // id: data.args.id, - // }); - // Don't delete the task immediately for failed tasks so users can see the error - break; - } - - if (taskUpdate.finished && !taskUpdate.failed) { - DeferredTasks.removeTask(data.args.id); - // sendNotification({ - // type: 'success', - // message: 'Task finished by ' + this.addonInfo.name, - // id: data.args.id, - // }); - } - break; - } - case 'get-app-details': { - if (!this.addonInfo) { - console.error( - 'Client attempted to send get-app-details before authentication' - ); - this.ws.close( - 1008, - 'Client attempted to send get-app-details before authentication' - ); - return; - } - const { - appID, - storefront, - }: ClientSentEventTypes['get-app-details'] = data.args; - // query all of the clients for the app details - const clientsWithStorefront = Array.from(clients.values()).filter( - (client) => - supportsStorefront(client.addonInfo?.storefronts, storefront) && - client.eventsAvailable.includes('game-details') - ); - // find a storefront that gives app details that isn't undefined - let appDetails: StoreData | undefined; - for (const client of clientsWithStorefront) { - const response = await client.sendEventMessage( - { - event: 'game-details', - args: { appID, storefront }, - }, - true - ); - if (response.args) { - appDetails = response.args; - break; - } - } - if (!appDetails) { - console.error('No app details found for client'); - this.sendEventMessage( - { - event: 'response', - args: undefined, - id: data.id, - }, - false - ); - return; - } - this.sendEventMessage( - { - event: 'response', - args: appDetails, - id: data.id, - }, - false - ); - console.log('Sent app details to client'); - break; - } - case 'search-app-name': { - if (!this.addonInfo) { - console.error( - 'Client attempted to send search-app-name before authentication' - ); - this.ws.close( - 1008, - 'Client attempted to send search-app-name before authentication' - ); - return; - } - const { - query, - storefront, - }: ClientSentEventTypes['search-app-name'] = data.args; - const clientsWithStorefront = Array.from(clients.values()).filter( - (client) => - supportsStorefront(client.addonInfo?.storefronts, storefront) && - client.eventsAvailable.includes('library-search') - ); - const searchResult: StoreData[] = []; - for (const client of clientsWithStorefront) { - const response = await client.sendEventMessage( - { event: 'library-search', args: query }, - true - ); - if (response.args) { - searchResult.push(...response.args); - } - } - this.sendEventMessage( - { event: 'response', args: searchResult, id: data.id }, - false - ); - break; - } - case 'flag': { - if (!this.addonInfo) { - console.error( - 'Client attempted to send flag before authentication' - ); - this.ws.close( - 1008, - 'Client attempted to send flag before authentication' - ); - return; - } - if (data.args.flag === 'events-available') { - console.log( - 'Setting events-available to', - data.args.value, - 'for addon', - this.addonInfo!.id - ); - this.eventsAvailable = data.args.value as OGIAddonEvent[]; - } - break; - } - } - }; - - this.ws.on('message', this.messageHandler); - - // Clean up pending responses on close/error - this.ws.on('close', () => { - for (const [_, pending] of this.pendingResponses.entries()) { - pending.reject(new Error('Websocket closed')); - } - this.pendingResponses.clear(); - }); - - this.ws.on('error', () => { - for (const [_, pending] of this.pendingResponses.entries()) { - pending.reject(new Error('Websocket error')); - } - this.pendingResponses.clear(); - }); - }); - } - public sendEventMessage( - message: WebsocketMessageServer, - expectResponse: boolean = true - ): Promise { - if (expectResponse) { - message.id = Math.random().toString(36).substring(7); - } - return new Promise((resolve, reject) => { - // CLOSED state is 3 - if (this.ws.readyState === 3) { - reject(new Error('Websocket closed')); - return; - } - - this.ws.send(JSON.stringify(message), (err: Error | null | undefined) => { - if (err) { - reject(err); - return; - } - }); - - if (expectResponse && message.id) { - // Store the pending response handler - this.pendingResponses.set(message.id, { resolve, reject }); - } else { - resolve({ event: 'response', args: 'OK' }); - } - }); - } -} diff --git a/application/src/electron/server/addon-server.ts b/application/src/electron/server/addon-server.ts index 4142f15f..c25cf29e 100644 --- a/application/src/electron/server/addon-server.ts +++ b/application/src/electron/server/addon-server.ts @@ -1,18 +1,16 @@ import express, { type Request } from 'express'; -import cors from 'cors'; const port = 7654; import http from 'http'; -import { WebSocketServer } from 'ws'; +import { AddonServer } from '@ogi-sdk/addon-server'; import addonProcedures from '@/electron/server/api/addons.js'; import deferProcedures from '@/electron/server/api/defer.js'; -import { AddonConnection } from '@/electron/server/AddonConnection.js'; -import { AddonServer } from '@/electron/server/serve.js'; +import { AddonIPC } from '@/electron/server/ipc.js'; import { z } from 'zod'; +import type { ConfigurationFile } from 'ogi-addon/config'; +import { join } from 'path'; +import { existsSync, readFileSync } from 'fs'; const app = express(); const server = http.createServer(app); -const wss = new WebSocketServer({ server }); - -const clients: Map = new Map(); export const launchForwardPayloadSchema = z.object({ gameId: z.number().int().nonnegative(), @@ -35,6 +33,22 @@ type LaunchRequestHandler = ( let focusRequestHandler: FocusRequestHandler | null = null; let launchRequestHandler: LaunchRequestHandler | null = null; +type AddonServerNotification = { + message: string; + id: string; + type: 'info' | 'error' | 'success' | 'warning'; +}; +type InputAskedHandler = ( + id: string, + configuration: ConfigurationFile, + title: string, + description: string, + callback: (result: any) => void +) => void; +type NotificationHandler = (notification: AddonServerNotification) => void; + +let inputAskedHandler: InputAskedHandler | null = null; +let notificationHandler: NotificationHandler | null = null; export function registerInstanceBridgeHandlers(handlers: { onFocus?: FocusRequestHandler; @@ -44,6 +58,14 @@ export function registerInstanceBridgeHandlers(handlers: { launchRequestHandler = handlers.onLaunch ?? null; } +export function registerAddonServerUIHandlers(handlers: { + onInputAsked?: InputAskedHandler; + onNotification?: NotificationHandler; +}) { + inputAskedHandler = handlers.onInputAsked ?? null; + notificationHandler = handlers.onNotification ?? null; +} + function isLoopbackAddress(address: string | undefined): boolean { if (!address) return false; const normalized = address.startsWith('::ffff:') @@ -56,39 +78,8 @@ function isLocalOnlyRequest(request: Request): boolean { return isLoopbackAddress(request.socket.remoteAddress ?? undefined); } -wss.on('connection', async (ws) => { - const connection = new AddonConnection(ws); - const connected = await connection.setupWebsocket(); - if (!connected) return; - - ws.on('close', () => { - console.log('Client disconnected', connection.addonInfo?.id); - if (connection.addonInfo) { - clients.delete(connection.addonInfo.id); - } - }); - - // Client is registered in AddonConnection.authenticate (clients.set + sendIPCMessage) -}); - -app.all('*', (_, res, next) => { - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Credentials', 'true'); - next(); -}); -// allow cors for localhost:8080 and file urls -app.use( - cors({ - origin: ['http://localhost:8080', 'file://'], - }) -); - app.use(express.json()); -app.get('/', (_, res) => { - res.send('Hello World!'); -}); - app.use('/internal', (req, res, next) => { if (!isLocalOnlyRequest(req)) { res.status(403).json({ success: false, error: 'Local access only' }); @@ -162,9 +153,101 @@ app.post('/internal/launch', async (req, res) => { } }); -const addonServer = new AddonServer({ +const addonIPC = new AddonIPC({ ...addonProcedures, ...deferProcedures, }); -export { port, server, wss, clients, addonServer }; +let isSecurityCheckEnabled = true; +if (existsSync(join(__dirname, 'config/option/developer.json'))) { + const developerConfig = JSON.parse( + readFileSync(join(__dirname, 'config/option/developer.json'), 'utf-8') + ); + isSecurityCheckEnabled = developerConfig.disableSecretCheck !== true; + if (!isSecurityCheckEnabled) { + for (let i = 0; i < 10; i++) { + console.warn( + 'WARNING Security check is disabled. THIS IS A MAJOR SECURITY RISK. PLEASE ENABLE DURING NORMAL USE.' + ); + } + } +} + +const addonServer = new AddonServer({ + port, + securityCheck: isSecurityCheckEnabled, +}); + +addonServer.extend(server); + +addonServer.on('input-asked', (title, description, configuration, reply) => { + inputAskedHandler?.( + Math.random().toString(36).substring(7), + configuration, + title, + description, + (result) => { + reply(result); + } + ); +}); + +addonServer.on('notification', (notification) => { + notificationHandler?.(notification); +}); + +addonServer.on('disconnect', (reason) => { + notificationHandler?.({ + type: 'error', + message: reason, + id: 'addon-disconnect-' + Math.random().toString(36).substring(7), + }); +}); + +let addonServerStarting: Promise | null = null; + +function startAddonServer() { + if (server.listening) { + return Promise.resolve(); + } + if (addonServerStarting) { + return addonServerStarting; + } + + addonServerStarting = new Promise((resolve, reject) => { + const onListening = () => { + cleanup(); + addonServerStarting = null; + resolve(); + }; + const onError = (error: Error) => { + cleanup(); + addonServerStarting = null; + reject(error); + }; + const cleanup = () => { + server.removeListener('listening', onListening); + server.removeListener('error', onError); + }; + + server.once('listening', onListening); + server.once('error', onError); + + try { + void addonServer.start(); + } catch (error) { + onError(error as Error); + } + }); + + return addonServerStarting; +} + +export { + port, + server, + addonServer, + addonIPC, + isSecurityCheckEnabled, + startAddonServer, +}; diff --git a/application/src/electron/server/api/addons.ts b/application/src/electron/server/api/addons.ts index c6d2d27f..b64968ae 100644 --- a/application/src/electron/server/api/addons.ts +++ b/application/src/electron/server/api/addons.ts @@ -1,9 +1,7 @@ import { z } from 'zod'; -import { clients } from '@/electron/server/addon-server.js'; -import { DeferrableTask } from '@/electron/server/DeferrableTask.js'; +import { addonServer } from '@/electron/server/addon-server.js'; +import { DeferrableTask } from '@ogi-sdk/addon-server'; -// Invariant: clients only contains connections with addonInfo set (added in AddonConnection.authenticate). -// We still guard below for robustness in case the map is accessed before auth or after disconnect. import sanitize from 'sanitize-html'; import { type Procedure, @@ -11,14 +9,15 @@ import { ProcedureError, ProcedureJSON, ProcedureDeferTask, -} from '@/electron/server/serve.js'; +} from '@/electron/server/ipc.js'; import * as fs from 'fs/promises'; import { join } from 'path'; import { restartAddonServer } from '@/electron/handlers/handler.addon.js'; import { __dirname } from '@/electron/manager/manager.paths.js'; -import type { StoreData } from 'ogi-addon'; +import type { LibraryInfo, SearchResult, StoreData } from 'ogi-addon'; import { ZodLibraryInfo } from 'ogi-addon'; import { supportsStorefront } from '@/lib/storefronts.js'; +import type { ConfigurationFile } from 'ogi-addon/config'; const procedures: Record> = { // Get all addon info @@ -26,7 +25,7 @@ const procedures: Record> = { .input(z.object({})) .handler(async () => { let info = []; - for (const client of clients.values()) { + for (const client of addonServer.getConnections().values()) { if (client.addonInfo) { info.push({ ...client.addonInfo, @@ -46,15 +45,14 @@ const procedures: Record> = { }) ) .handler(async (input) => { - const client = clients.get(input.addonID); + const client = addonServer.getClient(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); - const response = await client.sendEventMessage({ - event: 'config-update', - args: input.config, - }); + const response = await client.events.configUpdate( + input.config as ConfigurationFile + ); if (response.args && response.args.success) { return new ProcedureJSON(200, { success: true }); @@ -78,7 +76,7 @@ const procedures: Record> = { }) ) .handler(async (input) => { - const client = clients.get(input.addonID); + const client = addonServer.getClient(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); @@ -87,14 +85,11 @@ const procedures: Record> = { } const deferrableTask = new DeferrableTask(async () => { - const event = await client.sendEventMessage({ - event: 'search', - args: { - appID: input.appID, - storefront: input.storefront, - for: input.for, - libraryInfo: input.libraryInfo, - }, + const event = await client.events.search({ + appID: input.appID, + storefront: input.storefront, + for: input.for, + libraryInfo: input.libraryInfo as LibraryInfo, }); console.log('searchComplete', event.args); return event.args; @@ -112,7 +107,7 @@ const procedures: Record> = { }) ) .handler(async (input) => { - const client = clients.get(input.addonID); + const client = addonServer.getClient(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); @@ -125,10 +120,7 @@ const procedures: Record> = { } const deferrableTask = new DeferrableTask(async () => { - const event = await client.sendEventMessage({ - event: 'library-search', - args: input.query, - }); + const event = await client.events.librarySearch(input.query); return event.args; }, client.addonInfo.id); @@ -145,7 +137,7 @@ const procedures: Record> = { }) ) .handler(async (input) => { - const client = clients.get(input.addonID); + const client = addonServer.getClient(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); @@ -155,11 +147,11 @@ const procedures: Record> = { } const deferrableTask = new DeferrableTask(async () => { - const data = await client.sendEventMessage({ - event: 'request-dl', - args: { appID: input.appID, info: input.info }, - }); - return data.args; + const data = await client.events.requestDl( + input.appID as number, + input.info as SearchResult + ); + return data; }, client.addonInfo.id); return new ProcedureDeferTask(200, deferrableTask); @@ -173,7 +165,7 @@ const procedures: Record> = { }) ) .handler(async (input) => { - const client = clients.get(input.addonID); + const client = addonServer.getClient(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); @@ -183,11 +175,8 @@ const procedures: Record> = { } const deferrableTask = new DeferrableTask(async () => { - const data = await client.sendEventMessage({ - event: 'catalog', - args: {}, - }); - return data.args; + const data = await client.events.catalog(); + return data; }, client.addonInfo.id); return new ProcedureDeferTask(200, deferrableTask); @@ -220,7 +209,7 @@ const procedures: Record> = { ) .handler(async (input) => { console.log('setupApp', input); - const client = clients.get(input.addonID); + const client = addonServer.getClient(input.addonID); if (!client) { console.error('Client not found'); return new ProcedureError(404, 'Client not found'); @@ -234,22 +223,18 @@ const procedures: Record> = { } const deferrableTask = new DeferrableTask(async () => { - const data = await client.sendEventMessage({ - event: 'setup', - args: { - path: input.path, - appID: input.appID, - type: input.type, - usedRealDebrid: input.usedRealDebrid, - clearOldFilesBeforeUpdate: input.clearOldFilesBeforeUpdate, - storefront: input.storefront, - name: input.name, - multiPartFiles: input.multiPartFiles, - currentLibraryInfo: input.currentLibraryInfo, - for: input.for, - deferID: deferrableTask.id!!, - manifest: input.manifest, - }, + const data = await client.events.setup({ + path: input.path, + appID: input.appID as number, + type: input.type as 'direct' | 'torrent' | 'magnet' | 'empty', + usedRealDebrid: input.usedRealDebrid, + clearOldFilesBeforeUpdate: input.clearOldFilesBeforeUpdate, + storefront: input.storefront, + name: input.name, + multiPartFiles: input.multiPartFiles, + currentLibraryInfo: input.currentLibraryInfo as LibraryInfo, + for: input.for, + manifest: input.manifest as Record, }); return data.args; }, client.addonInfo.id); @@ -266,7 +251,9 @@ const procedures: Record> = { }) ) .handler(async (input) => { - const clientsWithStorefront = Array.from(clients.values()).filter( + const clientsWithStorefront = Array.from( + addonServer.getConnections().values() + ).filter( (client) => supportsStorefront(client.addonInfo?.storefronts, input.storefront) && client.eventsAvailable.includes('game-details') @@ -282,9 +269,9 @@ const procedures: Record> = { // find a client that can serve this storefront let appDetails: StoreData | undefined; for (const client of clientsWithStorefront) { - const data = await client.sendEventMessage({ - event: 'game-details', - args: { appID: gameID, storefront: input.storefront }, + const data = await client.events.gameDetails({ + appID: gameID, + storefront: input.storefront, }); if (data.args) { appDetails = data.args; @@ -327,7 +314,7 @@ const procedures: Record> = { deleteAddon: procedure() .input(z.object({ addonID: z.string() })) .handler(async (input) => { - const client = clients.get(input.addonID); + const client = addonServer.getClient(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); @@ -399,22 +386,19 @@ const procedures: Record> = { }) ) .handler(async (input) => { - const client = clients.get(input.addonID); + const client = addonServer.getClient(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); const deferrableTask = new DeferrableTask(async () => { - const data = await client.sendEventMessage({ - event: 'task-run', - args: { - manifest: input.manifest, - downloadPath: input.downloadPath, - name: input.name, - taskName: input.taskName, - libraryInfo: input.libraryInfo, - deferID: deferrableTask.id!!, - }, + const data = await client.events.taskRun({ + manifest: input.manifest as Record, + downloadPath: input.downloadPath, + name: input.name, + taskName: input.taskName, + deferID: deferrableTask.id!!, + libraryInfo: input.libraryInfo as LibraryInfo, }); return data.args; }, client.addonInfo.id); @@ -431,7 +415,9 @@ const procedures: Record> = { }) ) .handler(async (input) => { - const clientsWithStorefront = Array.from(clients.values()).filter( + const clientsWithStorefront = Array.from( + addonServer.getConnections().values() + ).filter( (client) => supportsStorefront(client.addonInfo?.storefronts, input.storefront) && client.eventsAvailable.includes('check-for-updates') @@ -448,18 +434,15 @@ const procedures: Record> = { 'Multiple clients found to serve this storefront' ); } - const client = clientsWithStorefront[0]; + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); const deferrableTask = new DeferrableTask(async () => { - const data = await client.sendEventMessage({ - event: 'check-for-updates', - args: { - appID: input.appID, - storefront: input.storefront, - currentVersion: input.currentVersion, - }, + const data = await client.events.checkForUpdates({ + appID: input.appID, + storefront: input.storefront, + currentVersion: input.currentVersion, }); return data.args; }, client.addonInfo.id); @@ -474,9 +457,9 @@ const procedures: Record> = { }) ) .handler(async (input) => { - const clientsWithEvent = Array.from(clients.values()).filter((client) => - client.eventsAvailable.includes('launch-app') - ); + const clientsWithEvent = Array.from( + addonServer.getConnections().values() + ).filter((client) => client.eventsAvailable.includes('launch-app')); console.log( 'clientsWithEvent', @@ -490,12 +473,9 @@ const procedures: Record> = { // Fire off the event to all clients, and wait for all to finish const results = await Promise.all( clientsWithEvent.map((client) => - client.sendEventMessage({ - event: 'launch-app', - args: { - libraryInfo: input.libraryInfo, - launchType: input.launchType, - }, + client.events.launchApp({ + libraryInfo: input.libraryInfo as LibraryInfo, + launchType: input.launchType, }) ) ); diff --git a/application/src/electron/server/api/defer.ts b/application/src/electron/server/api/defer.ts index 0e7b6821..b55eac01 100644 --- a/application/src/electron/server/api/defer.ts +++ b/application/src/electron/server/api/defer.ts @@ -1,12 +1,11 @@ import { z } from 'zod'; -import { clients } from '@/electron/server/addon-server.js'; -import { DeferredTasks } from '@/electron/server/DeferrableTask.js'; +import { addonServer } from '@/electron/server/addon-server.js'; import { type Procedure, procedure, ProcedureError, ProcedureJSON, -} from '@/electron/server/serve.js'; +} from '@/electron/server/ipc.js'; export type ResponseDeferredTask = { id: string; @@ -23,7 +22,7 @@ const procedures: Record> = { .input(z.object({})) .handler(async () => { const tasks: ResponseDeferredTask[] = Object.values( - DeferredTasks.getTasks() + addonServer.getDeferredTasksManager().getTasks() ).map((task) => ({ name: `Task ${task.id}`, description: 'Background task', @@ -46,28 +45,32 @@ const procedures: Record> = { }) ) .handler(async (input) => { - if (DeferredTasks.getTasks()[input.taskID] === undefined) { - console.log('task not found @' + input.taskID + '@', DeferredTasks); + const deferredTasksManager = addonServer.getDeferredTasksManager(); + if (deferredTasksManager.getTasks()[input.taskID] === undefined) { + console.log( + 'task not found @' + input.taskID + '@', + deferredTasksManager.getTasks() + ); return new ProcedureError(404, 'Task not found'); } - const task = DeferredTasks.getTasks()[input.taskID]!!; + const task = deferredTasksManager.getTasks()[input.taskID]!!; // check if the addon is still running - const stillExists = clients.has(task.addonOwner); + const stillExists = addonServer.getClient(task.addonOwner) !== undefined; // when the addon owner is *, we don't need to check if it's still connected as it's a global task spawned by the server if (!stillExists && task.addonOwner !== '*') { - DeferredTasks.removeTask(input.taskID); + deferredTasksManager.removeTask(input.taskID); return new ProcedureError(410, 'Addon is no longer connected'); } if (task.failed) { - DeferredTasks.removeTask(input.taskID); + deferredTasksManager.removeTask(input.taskID); return new ProcedureError(500, task.failed); } if (task.finished) { - DeferredTasks.removeTask(input.taskID); + deferredTasksManager.removeTask(input.taskID); // Use the getSerializedData method to ensure data is properly serialized return new ProcedureJSON(200, { data: task.getSerializedData(), diff --git a/application/src/electron/server/constants.ts b/application/src/electron/server/constants.ts deleted file mode 100644 index 79ca1c41..00000000 --- a/application/src/electron/server/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const addonSecret = `${Math.floor(new Date().getTime() + Math.random() * 10000)}-${Math.floor(Math.random() * 10000)}`; -export const applicationAddonSecret = `${Math.floor(new Date().getTime() + Math.random() * 10000)}-${Math.floor(Math.random() * 10000)}`; diff --git a/application/src/electron/server/dev-server.ts b/application/src/electron/server/dev-server.ts deleted file mode 100644 index e51c09f5..00000000 --- a/application/src/electron/server/dev-server.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { server, port } from '@/electron/server/addon-server.js'; -import { - addonSecret, - applicationAddonSecret, -} from '@/electron/server/constants.js'; -console.log('Addon Secret is: ' + addonSecret); -console.log('Application Addon Secret is: ' + applicationAddonSecret); - -server.listen(port, () => { - console.log(`Server started on port ${port}`); -}); diff --git a/application/src/electron/server/serve.ts b/application/src/electron/server/ipc.ts similarity index 94% rename from application/src/electron/server/serve.ts rename to application/src/electron/server/ipc.ts index cafa9739..82d470e8 100644 --- a/application/src/electron/server/serve.ts +++ b/application/src/electron/server/ipc.ts @@ -1,8 +1,6 @@ +import { DeferrableTask } from '@ogi-sdk/addon-server'; import { z } from 'zod'; -import { - DeferrableTask, - DeferredTasks, -} from '@/electron/server/DeferrableTask.js'; +import { addonServer } from './addon-server'; export class ProcedureReturnType { public tag: tag; @@ -80,7 +78,7 @@ export const procedure = () => { return new Procedure(); }; -export class AddonServer { +export class AddonIPC { private procedures: Record = {}; public registerProcedure(name: string, proc: Procedure) { @@ -115,7 +113,7 @@ export class AddonServer { if (result.tag === 'defer') { // add the task to the deferred tasks console.log('adding task', result.deferrableTask.id); - DeferredTasks.addTask(result.deferrableTask); + addonServer.getDeferredTasksManager().addTask(result.deferrableTask); result.deferrableTask.run(); return new ProcedureDeferTask(result.status, result.deferrableTask); } else if (result.tag === 'json') { diff --git a/application/src/electron/startup.ts b/application/src/electron/startup.ts index 3201fd21..05116bdd 100644 --- a/application/src/electron/startup.ts +++ b/application/src/electron/startup.ts @@ -16,7 +16,7 @@ import type { LibraryInfo } from 'ogi-addon'; import { app, BrowserWindow } from 'electron'; import { sendNotification } from '@/electron/main.js'; import semver from 'semver'; -import { setupAddon } from '@/electron/manager/manager.addon.js'; +import { Addon } from '@/electron/manager/manager.addon.js'; const UMU_RELEASES_URL = 'https://api.github.com/repos/Open-Wine-Components/umu-launcher/releases/latest'; @@ -283,32 +283,28 @@ export function stopUmuBackgroundUpdater() { } // check if NixOS using command -v nixos-rebuild -export const IS_NIXOS = await (() => { +export let IS_NIXOS = false; +export let STEAMTINKERLAUNCH_PATH = join( + __dirname, + 'bin/steamtinkerlaunch/steamtinkerlaunch' +); + +function detectNixOS(): Promise { return new Promise((resolve) => { try { - exec('command -v nixos-rebuild', (error, stderr) => { + exec('command -v nixos-rebuild', (error, _stdout, stderr) => { if (error) { - console.error(`exec error: ${error}`); resolve(false); return; } - if (stderr.includes('nixos-rebuild')) { - resolve(true); - return; - } - resolve(false); + resolve(stderr.includes('nixos-rebuild')); }); - } catch (error) { - console.error(`exec error: ${error}`); + } catch { resolve(false); } }); -})(); -console.log('continuing launch...'); -export let STEAMTINKERLAUNCH_PATH = join( - __dirname, - 'bin/steamtinkerlaunch/steamtinkerlaunch' -); +} + async function fetch_STLPath() { return new Promise((resolve) => { exec('which steamtinkerlaunch', (error, stdout, stderr) => { @@ -330,17 +326,25 @@ async function fetch_STLPath() { }); }); } -console.log('NIXOS: ' + IS_NIXOS); -if (IS_NIXOS) await fetch_STLPath(); -if (STEAMTINKERLAUNCH_PATH === '') { - STEAMTINKERLAUNCH_PATH = join( - __dirname, - 'bin/steamtinkerlaunch/steamtinkerlaunch' - ); - console.error( - 'STEAMTINKERLAUNCH_PATH is empty. Using default path to prevent issues.' - ); -} + +export const startupEnvironmentReady = (async () => { + IS_NIXOS = await detectNixOS(); + console.log('NIXOS: ' + IS_NIXOS); + + if (IS_NIXOS) { + await fetch_STLPath(); + } + + if (STEAMTINKERLAUNCH_PATH === '') { + STEAMTINKERLAUNCH_PATH = join( + __dirname, + 'bin/steamtinkerlaunch/steamtinkerlaunch' + ); + console.error( + 'STEAMTINKERLAUNCH_PATH is empty. Using default path to prevent issues.' + ); + } +})(); // Directories to skip during restore (same as backup - node_modules will be reinstalled) const dirsToSkipRestore = ['node_modules']; @@ -655,7 +659,8 @@ export async function reinstallAddonDependencies( `[startup] Running setup for addon ${addonName} (${current}/${addons.length})` ); try { - const success = await setupAddon(addonPath); + const instance = await Addon.load(addonPath); + const success = instance ? await instance.install() : false; if (success) { console.log(`[startup] Successfully set up ${addonName}`); } else { diff --git a/application/src/frontend/App.svelte b/application/src/frontend/App.svelte index b58bc1d4..b3fc7110 100644 --- a/application/src/frontend/App.svelte +++ b/application/src/frontend/App.svelte @@ -36,6 +36,7 @@ showNotificationSideView, currentDownloads, headerBackButton, + clearHeaderBackButton, } from '@/frontend/store'; import StorePage from '@/frontend/components/StorePage.svelte'; import ConfigurationModal from '@/frontend/components/modal/ConfigurationModal.svelte'; @@ -560,8 +561,11 @@ {:else if os === ''} -
+
{:else if needsUmuSetup}
@@ -601,9 +604,9 @@ UMU Migration Recommended

- This game is still using legacy Proton prefix mode. Migrate it to - UMU for native OGI launch compatibility, including pre/post launch - events support. + This game is still using legacy Proton prefix mode. Migrate it + to UMU for native OGI launch compatibility, including pre/post + launch events support.

@@ -647,8 +650,8 @@ Steam Re-add Required

- This game has been updated. To continue playing, you must re-add it - to Steam. + This game has been updated. To continue playing, you must re-add + it to Steam.

@@ -747,7 +750,9 @@ ? 'border-b border-accent-light/40' : ''}" > - {task.name} + {task.name} - {#if isWin32Only} - - {/if} + {/if} diff --git a/application/src/frontend/tsconfig.json b/application/src/frontend/tsconfig.json index 729bd423..3c3acae2 100644 --- a/application/src/frontend/tsconfig.json +++ b/application/src/frontend/tsconfig.json @@ -27,5 +27,5 @@ "forceConsistentCasingInFileNames": true, "skipLibCheck": true }, - "include": ["."] + "include": [".", "../lib/storefronts.ts", "../lib/storefronts.ts"] } diff --git a/application/src/frontend/views/ClientOptionsView.svelte b/application/src/frontend/views/ClientOptionsView.svelte index b6e36a23..17af4655 100644 --- a/application/src/frontend/views/ClientOptionsView.svelte +++ b/application/src/frontend/views/ClientOptionsView.svelte @@ -466,11 +466,6 @@ await window.electronAPI.restartAddonServer(); isRestartingServer = false; fetchAddonsWithConfigure(); - createNotification({ - id: Math.random().toString(36).substring(7), - message: 'Addon server restarted', - type: 'success', - }); } let showPassword: { [key: string]: boolean } = $state({}); diff --git a/application/src/frontend/views/FocusedAddonView.svelte b/application/src/frontend/views/FocusedAddonView.svelte index d93649d2..ee990c8a 100644 --- a/application/src/frontend/views/FocusedAddonView.svelte +++ b/application/src/frontend/views/FocusedAddonView.svelte @@ -53,6 +53,7 @@ let deleteConfirmationModalOpen: boolean = $state(false); let backConfirmationModalOpen: boolean = $state(false); let selectedValues: Record = $state({}); + let runningActions: Record = $state({}); onMount(() => { safeFetch('getAllAddons', {}).then((data) => { @@ -348,6 +349,8 @@ async function handleActionClick(key: string) { if (!selectedAddon) return; + if (runningActions[key]) return; + const option = selectedAddon.configTemplate[key]; if (!isActionOption(option)) return; @@ -358,6 +361,7 @@ ...actionOption.manifest, }; + runningActions = { ...runningActions, [key]: true }; try { await runTask( { @@ -382,6 +386,8 @@ message: `Failed to run action: ${error}`, }, ]); + } finally { + runningActions = { ...runningActions, [key]: false }; } } @@ -603,10 +609,16 @@ key ] as ActionOption} {/if}

= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Vrx8sBnvq8squS/3yNBzR1jBXI+SgmnmvwawPjNuEHndUe5l1jV2Gp6JJ4ulDkEB8On6bWmmuyPpA+bq4t+WYg=="], + "emmet": ["emmet@2.4.11", "", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -1310,8 +1368,14 @@ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + "es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="], + + "es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="], + "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -1322,6 +1386,8 @@ "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], + "esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], "esrap": ["esrap@2.2.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ=="], @@ -1330,18 +1396,24 @@ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + "event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="], + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], + "exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="], + "execa": ["execa@7.2.0", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.1", "human-signals": "^4.3.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^3.0.7", "strip-final-newline": "^3.0.0" } }, "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA=="], "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], "express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="], + "ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], @@ -1352,6 +1424,8 @@ "extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="], + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], @@ -1372,6 +1446,8 @@ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "file-type": ["file-type@22.0.1", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.5", "token-types": "^6.1.2", "uint8array-extras": "^1.5.0" } }, "sha512-ww5Mhre0EE+jmBvOXTmXAbEMuZE7uX4a3+oRCQFNj8w++g3ev913N6tXQz0XTXbueQ5TWQfm6BdaViEHHn8bhA=="], + "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="], "filename-reserved-regex": ["filename-reserved-regex@3.0.0", "", {}, "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw=="], @@ -1440,7 +1516,7 @@ "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], - "get-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="], + "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], @@ -1494,7 +1570,7 @@ "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], - "hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="], + "hookable": ["hookable@6.1.1", "", {}, "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ=="], "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], @@ -1532,7 +1608,7 @@ "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], - "import-without-cache": ["import-without-cache@0.2.5", "", {}, "sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A=="], + "import-without-cache": ["import-without-cache@0.3.3", "", {}, "sha512-bDxwDdF04gm550DfZHgffvlX+9kUlcz32UD0AeBTmVPFiWkrexF2XVmiuFFbDhiFuP8fQkrkvI2KdSNPYWAXkQ=="], "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], @@ -1584,6 +1660,8 @@ "is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], + "is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="], + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], @@ -1746,6 +1824,8 @@ "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], + "memory-chunk-store": ["memory-chunk-store@1.3.5", "", { "dependencies": { "queue-microtask": "^1.2.3" } }, "sha512-E1Xc1U4ifk/FkC2ZsWhCaW1xg9HbE/OBmQTLe2Tr9c27YPSLbW7kw1cnb3kQWD1rDtErFJHa7mB9EVrs7aTx9g=="], "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], @@ -1862,6 +1942,8 @@ "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], + "next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="], + "nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="], "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="], @@ -1914,6 +1996,8 @@ "oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "opengameinstaller-gui": ["opengameinstaller-gui@workspace:application"], "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], @@ -1964,7 +2048,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], "piece-length": ["piece-length@2.0.1", "", {}, "sha512-dBILiDmm43y0JPISWEmVGKBETQjwJe6mSU9GND+P9KW0SJGUwoU/odyH1nbalOP9i8WSYuqf1lQnaj92Bhw+Ug=="], @@ -2120,9 +2204,9 @@ "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], - "rolldown": ["rolldown@1.0.0-rc.3", "", { "dependencies": { "@oxc-project/types": "=0.112.0", "@rolldown/pluginutils": "1.0.0-rc.3" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-x64": "1.0.0-rc.3", "@rolldown/binding-freebsd-x64": "1.0.0-rc.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.3", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.3", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.3", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.3", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.3", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.3" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw=="], + "rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="], - "rolldown-plugin-dts": ["rolldown-plugin-dts@0.22.1", "", { "dependencies": { "@babel/generator": "8.0.0-rc.1", "@babel/helper-validator-identifier": "8.0.0-rc.1", "@babel/parser": "8.0.0-rc.1", "@babel/types": "8.0.0-rc.1", "ast-kit": "^3.0.0-beta.1", "birpc": "^4.0.0", "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.1", "obug": "^2.1.1" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-rc.3", "typescript": "^5.0.0", "vue-tsc": "~3.2.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-5E0AiM5RSQhU6cjtkDFWH6laW4IrMu0j1Mo8x04Xo1ALHmaRMs9/7zej7P3RrryVHW/DdZAp85MA7Be55p0iUw=="], + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.23.2", "", { "dependencies": { "@babel/generator": "8.0.0-rc.3", "@babel/helper-validator-identifier": "8.0.0-rc.3", "@babel/parser": "8.0.0-rc.3", "@babel/types": "8.0.0-rc.3", "ast-kit": "^3.0.0-beta.1", "birpc": "^4.0.0", "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.7", "obug": "^2.1.1", "picomatch": "^4.0.4" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20260325.1", "rolldown": "^1.0.0-rc.12", "typescript": "^5.0.0 || ^6.0.0", "vue-tsc": "~3.2.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-PbSqLawLgZBGcOGT3yqWBGn4cX+wh2nt5FuBGdcMHyOhoukmjbhYAl8NT9sE4U38Cm9tqLOIQeOrvzeayM0DLQ=="], "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], @@ -2152,7 +2236,7 @@ "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], - "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], @@ -2224,6 +2308,8 @@ "streamx": ["streamx@2.22.1", "", { "dependencies": { "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA=="], + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -2246,6 +2332,8 @@ "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="], + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], @@ -2288,9 +2376,9 @@ "timeout-refresh": ["timeout-refresh@1.0.3", "", {}, "sha512-Mz0CX4vBGM5lj8ttbIFt7o4ZMxk/9rgudJRh76EvB7xXZMur7T/cjRiH2w4Fmkq0zxf2QpM8IFvOSRn8FEu3gA=="], - "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="], @@ -2300,6 +2388,8 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], + "torrent-discovery": ["torrent-discovery@11.0.19", "", { "dependencies": { "bittorrent-dht": "^11.0.11", "bittorrent-lsd": "^2.0.0", "bittorrent-tracker": "^11.2.2", "debug": "^4.4.3", "run-parallel": "^1.2.0" } }, "sha512-BLhdj7o0px+u72UuhJmq6CB0LBkZOa1nwgbd5ktyTELJlvcRL8EoxSSmSpzMOIScLGgslh1uLaAy/POhLpagtg=="], "torrent-piece": ["torrent-piece@3.0.2", "", { "dependencies": { "uint8-util": "^2.1.9" } }, "sha512-K1A5tZ3BolFrUtnBpk9iDg8av1na0OgQ7E0IlA9tj0bcsPhLhzvln+oMtMmtkqAwmUsbNCilRm2ymUdZg0rVbQ=="], @@ -2320,16 +2410,20 @@ "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="], - "tsdown": ["tsdown@0.20.2", "", { "dependencies": { "ansis": "^4.2.0", "cac": "^6.7.14", "defu": "^6.1.4", "empathic": "^2.0.0", "hookable": "^6.0.1", "import-without-cache": "^0.2.5", "obug": "^2.1.1", "picomatch": "^4.0.3", "rolldown": "1.0.0-rc.3", "rolldown-plugin-dts": "^0.22.1", "semver": "^7.7.3", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig-core": "^7.4.2", "unrun": "^0.2.27" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@vitejs/devtools": "*", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "@vitejs/devtools", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-CBoA7rDtyuNkRcFsf8OgFQqBZjCC/ffbNHuS8aUE3HvTJDysWiW7REsQ/nqCYPqsCzy/MqA0tleJj8r+gfy97Q=="], + "tsdown": ["tsdown@0.21.10", "", { "dependencies": { "ansis": "^4.2.0", "cac": "^7.0.0", "defu": "^6.1.7", "empathic": "^2.0.0", "hookable": "^6.1.1", "import-without-cache": "^0.3.3", "obug": "^2.1.1", "picomatch": "^4.0.4", "rolldown": "1.0.0-rc.17", "rolldown-plugin-dts": "^0.23.2", "semver": "^7.7.4", "tinyexec": "^1.1.1", "tinyglobby": "^0.2.16", "tree-kill": "^1.2.2", "unconfig-core": "^7.5.0", "unrun": "^0.2.37" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.21.10", "@tsdown/exe": "0.21.10", "@vitejs/devtools": "*", "publint": "^0.3.0", "typescript": "^5.0.0 || ^6.0.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@vitejs/devtools", "publint", "typescript", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-3wk73yBhZe/wX7REqSdivNQ84TDs1mJ+IlnzrrEREP70xlJ/AEIzqaI04l/TzMKVIdkTdC3CPaADn2Lk/0SkdA=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + "type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="], + "type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="], "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + "typedarray-to-buffer": ["typedarray-to-buffer@3.1.5", "", { "dependencies": { "is-typedarray": "^1.0.0" } }, "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q=="], + "typesafe-path": ["typesafe-path@0.2.2", "", {}, "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -2342,7 +2436,7 @@ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], - "unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="], + "unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], "undefsafe": ["undefsafe@2.0.5", "", {}, "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="], @@ -2378,7 +2472,7 @@ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - "unrun": ["unrun@0.2.27", "", { "dependencies": { "rolldown": "1.0.0-rc.3" }, "peerDependencies": { "synckit": "^0.11.11" }, "optionalPeers": ["synckit"], "bin": { "unrun": "dist/cli.mjs" } }, "sha512-Mmur1UJpIbfxasLOhPRvox/QS4xBiDii71hMP7smfRthGcwFL2OAmYRgduLANOAU4LUkvVamuP+02U+c90jlrw=="], + "unrun": ["unrun@0.2.37", "", { "dependencies": { "rolldown": "1.0.0-rc.17" }, "peerDependencies": { "synckit": "^0.11.11" }, "optionalPeers": ["synckit"], "bin": { "unrun": "dist/cli.mjs" } }, "sha512-AA7vDuYsgeSYVzJMm16UKA+aXFKhy7nFqW9z5l7q44K4ppFWZAMqYS58ePRZbugMLPH0fwwMzD5A8nP0avxwZQ=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], @@ -2388,7 +2482,7 @@ "ut_pex": ["ut_pex@4.0.4", "", { "dependencies": { "bencode": "^4.0.0", "compact2string": "^1.4.1", "string2compact": "^2.0.1" } }, "sha512-isVTbp2TKGoMOu+4Zh/i6ijpYr0VG83xjRPgCXaUjKzgXXndjCMWg32/9kZjubD+kxEXcmXMkoS8IttS9FZE8g=="], - "utf-8-validate": ["utf-8-validate@6.0.6", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA=="], + "utf-8-validate": ["utf-8-validate@5.0.10", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ=="], "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="], @@ -2456,6 +2550,8 @@ "webrtc-polyfill": ["webrtc-polyfill@1.2.0", "", { "dependencies": { "node-datachannel": "^0.32.0" } }, "sha512-epaVJbKzWOY5Wf3k7DoZLNgHP/5IoALBvjvlZQgX+9vFnf9UfCHv+rc+r/vJ7jxQUwH3cIYx9blHfyWWxGbw1g=="], + "websocket": ["websocket@1.0.35", "", { "dependencies": { "bufferutil": "^4.0.1", "debug": "^2.2.0", "es5-ext": "^0.10.63", "typedarray-to-buffer": "^3.1.5", "utf-8-validate": "^5.0.2", "yaeti": "^0.0.6" } }, "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q=="], + "webtorrent": ["webtorrent@2.8.5", "", { "dependencies": { "@silentbot1/nat-api": "^0.4.9", "@thaunknown/simple-peer": "^10.0.11", "@webtorrent/http-node": "^1.3.0", "addr-to-ip-port": "^2.0.0", "bitfield": "^4.2.0", "bittorrent-dht": "^11.0.10", "bittorrent-protocol": "^4.1.20", "cache-chunk-store": "^3.2.2", "chunk-store-iterator": "^1.0.4", "cpus": "^1.0.3", "create-torrent": "^6.1.0", "cross-fetch-ponyfill": "^1.0.3", "debug": "^4.4.1", "escape-html": "^1.0.3", "fs-chunk-store": "^5.0.0", "fsa-chunk-store": "^1.3.0", "immediate-chunk-store": "^2.2.0", "join-async-iterator": "^1.1.1", "load-ip-set": "^3.0.1", "lt_donthave": "^2.0.5", "memory-chunk-store": "^1.3.5", "mime": "^3.0.0", "once": "^1.4.0", "parse-torrent": "^11.0.18", "pump": "^3.0.2", "queue-microtask": "^1.2.3", "random-iterate": "^1.0.1", "range-parser": "^1.2.1", "run-parallel": "^1.2.0", "run-parallel-limit": "^1.1.0", "speed-limiter": "^1.0.2", "streamx": "2.22.1", "throughput": "^1.0.2", "torrent-discovery": "^11.0.17", "torrent-piece": "^3.0.2", "uint8-util": "^2.2.5", "unordered-array-remove": "^1.0.2", "ut_metadata": "^4.0.3", "ut_pex": "^4.0.4" }, "optionalDependencies": { "utp-native": "^2.5.3" } }, "sha512-oIjpuBrypApJ+RCZ8RRaHEncVSkt2cd25/I4Trb2sk9nlaEF92Dg1u8BCwqA4eJR7wIZQM95GyO7Wo4QTbrUUA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -2474,7 +2570,7 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], "xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], @@ -2484,6 +2580,8 @@ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + "yaeti": ["yaeti@0.0.6", "", {}, "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug=="], + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], @@ -2506,7 +2604,7 @@ "zip-stream": ["zip-stream@4.1.1", "", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="], - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "zod": ["zod@4.4.2", "", {}, "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw=="], "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], @@ -2514,6 +2612,8 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@astrojs/language-server/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], "@babel/core/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], @@ -2522,7 +2622,7 @@ "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/generator/@babel/types": ["@babel/types@8.0.0-rc.1", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.1", "@babel/helper-validator-identifier": "^8.0.0-rc.1" } }, "sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ=="], + "@babel/generator/@babel/types": ["@babel/types@8.0.0-rc.3", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.3", "@babel/helper-validator-identifier": "^8.0.0-rc.3" } }, "sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], @@ -2530,7 +2630,7 @@ "@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - "@babel/parser/@babel/types": ["@babel/types@8.0.0-rc.1", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.1", "@babel/helper-validator-identifier": "^8.0.0-rc.1" } }, "sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ=="], + "@babel/parser/@babel/types": ["@babel/types@8.0.0-rc.3", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.3", "@babel/helper-validator-identifier": "^8.0.0-rc.3" } }, "sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q=="], "@babel/template/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], @@ -2542,8 +2642,6 @@ "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], - "@ctrl/qbittorrent/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], - "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], "@electron/asar/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], @@ -2570,8 +2668,14 @@ "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + "@parcel/watcher/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@rollup/pluginutils/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "@tailwindcss/node/tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], @@ -2594,18 +2698,26 @@ "@thaunknown/simple-websocket/streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="], + "@thaunknown/simple-websocket/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + "@types/babel__core/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], "@types/babel__template/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + "all-debrid-js/tsdown": ["tsdown@0.20.2", "", { "dependencies": { "ansis": "^4.2.0", "cac": "^6.7.14", "defu": "^6.1.4", "empathic": "^2.0.0", "hookable": "^6.0.1", "import-without-cache": "^0.2.5", "obug": "^2.1.1", "picomatch": "^4.0.3", "rolldown": "1.0.0-rc.3", "rolldown-plugin-dts": "^0.22.1", "semver": "^7.7.3", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig-core": "^7.4.2", "unrun": "^0.2.27" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@vitejs/devtools": "*", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "@vitejs/devtools", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-CBoA7rDtyuNkRcFsf8OgFQqBZjCC/ffbNHuS8aUE3HvTJDysWiW7REsQ/nqCYPqsCzy/MqA0tleJj8r+gfy97Q=="], + + "all-debrid-js/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "app-builder-lib/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "app-builder-lib/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "archiver-utils/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "archiver-utils/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], @@ -2614,16 +2726,28 @@ "asar/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "ast-kit/@babel/parser": ["@babel/parser@8.0.0-rc.1", "", { "dependencies": { "@babel/types": "^8.0.0-rc.1" }, "bin": "./bin/babel-parser.js" }, "sha512-6HyyU5l1yK/7h9Ki52i5h6mDAx4qJdiLQO4FdCyJNoB/gy3T3GGJdhQzzbZgvgZCugYBvwtQiWRt94QKedHnkA=="], + + "astro/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "astro/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "astro/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], "astro/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], "astro/vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + "astro/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "bare-stream/streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="], "bittorrent-protocol/streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="], + "bittorrent-tracker/utf-8-validate": ["utf-8-validate@6.0.6", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA=="], + + "bittorrent-tracker/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], @@ -2652,6 +2776,8 @@ "electron-publish/mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], + "electron-vite/cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "electron-vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], @@ -2660,6 +2786,8 @@ "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "external-editor/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], @@ -2680,6 +2808,8 @@ "glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "global-agent/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], @@ -2704,16 +2834,26 @@ "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "node-abi/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "nodemon/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + "nodemon/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "nodemon/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + "ogi-addon/tsdown": ["tsdown@0.20.2", "", { "dependencies": { "ansis": "^4.2.0", "cac": "^6.7.14", "defu": "^6.1.4", "empathic": "^2.0.0", "hookable": "^6.0.1", "import-without-cache": "^0.2.5", "obug": "^2.1.1", "picomatch": "^4.0.3", "rolldown": "1.0.0-rc.3", "rolldown-plugin-dts": "^0.22.1", "semver": "^7.7.3", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig-core": "^7.4.2", "unrun": "^0.2.27" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@vitejs/devtools": "*", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "@vitejs/devtools", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-CBoA7rDtyuNkRcFsf8OgFQqBZjCC/ffbNHuS8aUE3HvTJDysWiW7REsQ/nqCYPqsCzy/MqA0tleJj8r+gfy97Q=="], + + "ogi-addon/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "opengameinstaller-gui/electron-builder": ["electron-builder@23.6.0", "", { "dependencies": { "@types/yargs": "^17.0.1", "app-builder-lib": "23.6.0", "builder-util": "23.6.0", "builder-util-runtime": "9.1.1", "chalk": "^4.1.1", "dmg-builder": "23.6.0", "fs-extra": "^10.0.0", "is-ci": "^3.0.0", "lazy-val": "^1.0.5", "read-config-file": "6.2.0", "simple-update-notifier": "^1.0.7", "yargs": "^17.5.1" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-y8D4zO+HXGCNxFBV/JlyhFnoQ0Y0K7/sFH+XwIbj47pqaW8S6PGYQbjoObolKBR1ddQFPt4rwp4CnwMJrW3HAw=="], "opengameinstaller-gui/tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + "opengameinstaller-gui/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "ora/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], @@ -2738,9 +2878,13 @@ "readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "real-debrid-js/tsdown": ["tsdown@0.20.2", "", { "dependencies": { "ansis": "^4.2.0", "cac": "^6.7.14", "defu": "^6.1.4", "empathic": "^2.0.0", "hookable": "^6.0.1", "import-without-cache": "^0.2.5", "obug": "^2.1.1", "picomatch": "^4.0.3", "rolldown": "1.0.0-rc.3", "rolldown-plugin-dts": "^0.22.1", "semver": "^7.7.3", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig-core": "^7.4.2", "unrun": "^0.2.27" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@vitejs/devtools": "*", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "@vitejs/devtools", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-CBoA7rDtyuNkRcFsf8OgFQqBZjCC/ffbNHuS8aUE3HvTJDysWiW7REsQ/nqCYPqsCzy/MqA0tleJj8r+gfy97Q=="], + + "real-debrid-js/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], - "rolldown-plugin-dts/@babel/types": ["@babel/types@8.0.0-rc.1", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.1", "@babel/helper-validator-identifier": "^8.0.0-rc.1" } }, "sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ=="], + "rolldown-plugin-dts/@babel/types": ["@babel/types@8.0.0-rc.3", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.3", "@babel/helper-validator-identifier": "^8.0.0-rc.3" } }, "sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -2748,6 +2892,10 @@ "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + "sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "simple-update-notifier/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "speed-limiter/streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="], "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -2758,22 +2906,36 @@ "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "sucrase/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "test-addon/@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], + "tmp-promise/tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], "ts-node/arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="], "ts-node/diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="], + "typescript-auto-import-cache/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "vite/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "vite/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "volar-service-typescript/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], "wait-on/axios": ["axios@0.25.0", "", { "dependencies": { "follow-redirects": "^1.14.7" } }, "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g=="], + "websocket/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -2792,11 +2954,15 @@ "zip-stream/archiver-utils": ["archiver-utils@3.0.4", "", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="], - "@babel/generator/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.1", "", {}, "sha512-vi/pfmbrOtQmqgfboaBhaCU50G7mcySVu69VU8z+lYoPPB6WzI9VgV7WQfL908M4oeSH5fDkmoupIqoE0SdApw=="], + "zod-to-ts/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@astrojs/language-server/tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "@babel/generator/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.4", "", {}, "sha512-dluR3v287dp6YPF57kyKKrHPKffUeuxH1zQcF1WD30TeFzWXhDiVi1U6PkqaDB0++H1PeCwRhmYl4DvoerlPIw=="], "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.1", "", {}, "sha512-vi/pfmbrOtQmqgfboaBhaCU50G7mcySVu69VU8z+lYoPPB6WzI9VgV7WQfL908M4oeSH5fDkmoupIqoE0SdApw=="], + "@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.4", "", {}, "sha512-dluR3v287dp6YPF57kyKKrHPKffUeuxH1zQcF1WD30TeFzWXhDiVi1U6PkqaDB0++H1PeCwRhmYl4DvoerlPIw=="], "@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], @@ -2808,12 +2974,38 @@ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "all-debrid-js/tsdown/cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "all-debrid-js/tsdown/defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "all-debrid-js/tsdown/hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="], + + "all-debrid-js/tsdown/import-without-cache": ["import-without-cache@0.2.5", "", {}, "sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A=="], + + "all-debrid-js/tsdown/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "all-debrid-js/tsdown/rolldown": ["rolldown@1.0.0-rc.3", "", { "dependencies": { "@oxc-project/types": "=0.112.0", "@rolldown/pluginutils": "1.0.0-rc.3" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-x64": "1.0.0-rc.3", "@rolldown/binding-freebsd-x64": "1.0.0-rc.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.3", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.3", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.3", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.3", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.3", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.3" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw=="], + + "all-debrid-js/tsdown/rolldown-plugin-dts": ["rolldown-plugin-dts@0.22.1", "", { "dependencies": { "@babel/generator": "8.0.0-rc.1", "@babel/helper-validator-identifier": "8.0.0-rc.1", "@babel/parser": "8.0.0-rc.1", "@babel/types": "8.0.0-rc.1", "ast-kit": "^3.0.0-beta.1", "birpc": "^4.0.0", "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.1", "obug": "^2.1.1" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-rc.3", "typescript": "^5.0.0", "vue-tsc": "~3.2.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-5E0AiM5RSQhU6cjtkDFWH6laW4IrMu0j1Mo8x04Xo1ALHmaRMs9/7zej7P3RrryVHW/DdZAp85MA7Be55p0iUw=="], + + "all-debrid-js/tsdown/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "all-debrid-js/tsdown/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + + "all-debrid-js/tsdown/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "all-debrid-js/tsdown/unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="], + + "all-debrid-js/tsdown/unrun": ["unrun@0.2.27", "", { "dependencies": { "rolldown": "1.0.0-rc.3" }, "peerDependencies": { "synckit": "^0.11.11" }, "optionalPeers": ["synckit"], "bin": { "unrun": "dist/cli.mjs" } }, "sha512-Mmur1UJpIbfxasLOhPRvox/QS4xBiDii71hMP7smfRthGcwFL2OAmYRgduLANOAU4LUkvVamuP+02U+c90jlrw=="], + "app-builder-lib/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "archiver-utils/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "ast-kit/@babel/parser/@babel/types": ["@babel/types@8.0.0-rc.1", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.1", "@babel/helper-validator-identifier": "^8.0.0-rc.1" } }, "sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ=="], + "astro/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], "astro/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], @@ -2930,6 +3122,30 @@ "nodemon/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], + "ogi-addon/tsdown/cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "ogi-addon/tsdown/defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "ogi-addon/tsdown/hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="], + + "ogi-addon/tsdown/import-without-cache": ["import-without-cache@0.2.5", "", {}, "sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A=="], + + "ogi-addon/tsdown/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "ogi-addon/tsdown/rolldown": ["rolldown@1.0.0-rc.3", "", { "dependencies": { "@oxc-project/types": "=0.112.0", "@rolldown/pluginutils": "1.0.0-rc.3" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-x64": "1.0.0-rc.3", "@rolldown/binding-freebsd-x64": "1.0.0-rc.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.3", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.3", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.3", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.3", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.3", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.3" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw=="], + + "ogi-addon/tsdown/rolldown-plugin-dts": ["rolldown-plugin-dts@0.22.1", "", { "dependencies": { "@babel/generator": "8.0.0-rc.1", "@babel/helper-validator-identifier": "8.0.0-rc.1", "@babel/parser": "8.0.0-rc.1", "@babel/types": "8.0.0-rc.1", "ast-kit": "^3.0.0-beta.1", "birpc": "^4.0.0", "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.1", "obug": "^2.1.1" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-rc.3", "typescript": "^5.0.0", "vue-tsc": "~3.2.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-5E0AiM5RSQhU6cjtkDFWH6laW4IrMu0j1Mo8x04Xo1ALHmaRMs9/7zej7P3RrryVHW/DdZAp85MA7Be55p0iUw=="], + + "ogi-addon/tsdown/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "ogi-addon/tsdown/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + + "ogi-addon/tsdown/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "ogi-addon/tsdown/unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="], + + "ogi-addon/tsdown/unrun": ["unrun@0.2.27", "", { "dependencies": { "rolldown": "1.0.0-rc.3" }, "peerDependencies": { "synckit": "^0.11.11" }, "optionalPeers": ["synckit"], "bin": { "unrun": "dist/cli.mjs" } }, "sha512-Mmur1UJpIbfxasLOhPRvox/QS4xBiDii71hMP7smfRthGcwFL2OAmYRgduLANOAU4LUkvVamuP+02U+c90jlrw=="], + "opengameinstaller-gui/electron-builder/app-builder-lib": ["app-builder-lib@23.6.0", "", { "dependencies": { "7zip-bin": "~5.1.1", "@develar/schema-utils": "~2.6.5", "@electron/universal": "1.2.1", "@malept/flatpak-bundler": "^0.4.0", "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.9", "builder-util": "23.6.0", "builder-util-runtime": "9.1.1", "chromium-pickle-js": "^0.2.0", "debug": "^4.3.4", "ejs": "^3.1.7", "electron-osx-sign": "^0.6.0", "electron-publish": "23.6.0", "form-data": "^4.0.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "is-ci": "^3.0.0", "isbinaryfile": "^4.0.10", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "minimatch": "^3.1.2", "read-config-file": "6.2.0", "sanitize-filename": "^1.6.3", "semver": "^7.3.7", "tar": "^6.1.11", "temp-file": "^3.4.0" } }, "sha512-dQYDuqm/rmy8GSCE6Xl/3ShJg6Ab4bZJMT8KaTKGzT436gl1DN4REP3FCWfXoh75qGTJ+u+WsdnnpO9Jl8nyMA=="], "opengameinstaller-gui/electron-builder/builder-util": ["builder-util@23.6.0", "", { "dependencies": { "7zip-bin": "~5.1.1", "@types/debug": "^4.1.6", "@types/fs-extra": "^9.0.11", "app-builder-bin": "4.0.0", "bluebird-lst": "^1.0.9", "builder-util-runtime": "9.1.1", "chalk": "^4.1.1", "cross-spawn": "^7.0.3", "debug": "^4.3.4", "fs-extra": "^10.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-ci": "^3.0.0", "js-yaml": "^4.1.0", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0" } }, "sha512-QiQHweYsh8o+U/KNCZFSvISRnvRctb8m/2rB2I1JdByzvNKxPeFLlHFRPQRXab6aYeXc18j9LpsDLJ3sGQmWTQ=="], @@ -2946,7 +3162,31 @@ "readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "rolldown-plugin-dts/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.1", "", {}, "sha512-vi/pfmbrOtQmqgfboaBhaCU50G7mcySVu69VU8z+lYoPPB6WzI9VgV7WQfL908M4oeSH5fDkmoupIqoE0SdApw=="], + "real-debrid-js/tsdown/cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "real-debrid-js/tsdown/defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "real-debrid-js/tsdown/hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="], + + "real-debrid-js/tsdown/import-without-cache": ["import-without-cache@0.2.5", "", {}, "sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A=="], + + "real-debrid-js/tsdown/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "real-debrid-js/tsdown/rolldown": ["rolldown@1.0.0-rc.3", "", { "dependencies": { "@oxc-project/types": "=0.112.0", "@rolldown/pluginutils": "1.0.0-rc.3" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-x64": "1.0.0-rc.3", "@rolldown/binding-freebsd-x64": "1.0.0-rc.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.3", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.3", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.3", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.3", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.3", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.3" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw=="], + + "real-debrid-js/tsdown/rolldown-plugin-dts": ["rolldown-plugin-dts@0.22.1", "", { "dependencies": { "@babel/generator": "8.0.0-rc.1", "@babel/helper-validator-identifier": "8.0.0-rc.1", "@babel/parser": "8.0.0-rc.1", "@babel/types": "8.0.0-rc.1", "ast-kit": "^3.0.0-beta.1", "birpc": "^4.0.0", "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.1", "obug": "^2.1.1" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-rc.3", "typescript": "^5.0.0", "vue-tsc": "~3.2.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-5E0AiM5RSQhU6cjtkDFWH6laW4IrMu0j1Mo8x04Xo1ALHmaRMs9/7zej7P3RrryVHW/DdZAp85MA7Be55p0iUw=="], + + "real-debrid-js/tsdown/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "real-debrid-js/tsdown/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + + "real-debrid-js/tsdown/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "real-debrid-js/tsdown/unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="], + + "real-debrid-js/tsdown/unrun": ["unrun@0.2.27", "", { "dependencies": { "rolldown": "1.0.0-rc.3" }, "peerDependencies": { "synckit": "^0.11.11" }, "optionalPeers": ["synckit"], "bin": { "unrun": "dist/cli.mjs" } }, "sha512-Mmur1UJpIbfxasLOhPRvox/QS4xBiDii71hMP7smfRthGcwFL2OAmYRgduLANOAU4LUkvVamuP+02U+c90jlrw=="], + + "rolldown-plugin-dts/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.4", "", {}, "sha512-dluR3v287dp6YPF57kyKKrHPKffUeuxH1zQcF1WD30TeFzWXhDiVi1U6PkqaDB0++H1PeCwRhmYl4DvoerlPIw=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -2954,10 +3194,14 @@ "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "sucrase/tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "test-addon/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], @@ -3004,6 +3248,8 @@ "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "websocket/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "widest-line/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -3014,8 +3260,92 @@ "zip-stream/archiver-utils/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "all-debrid-js/tsdown/rolldown/@oxc-project/types": ["@oxc-project/types@0.112.0", "", {}, "sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.3", "", { "os": "android", "cpu": "arm64" }, "sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm" }, "sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.3", "", { "os": "none", "cpu": "arm64" }, "sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.3", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "x64" }, "sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + + "all-debrid-js/tsdown/rolldown-plugin-dts/@babel/generator": ["@babel/generator@8.0.0-rc.1", "", { "dependencies": { "@babel/parser": "^8.0.0-rc.1", "@babel/types": "^8.0.0-rc.1", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-3ypWOOiC4AYHKr8vYRVtWtWmyvcoItHtVqF8paFax+ydpmUdPsJpLBkBBs5ItmhdrwC3a0ZSqqFAdzls4ODP3w=="], + + "all-debrid-js/tsdown/rolldown-plugin-dts/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0-rc.1", "", {}, "sha512-I4YnARytXC2RzkLNVnf5qFNFMzp679qZpmtw/V3Jt2uGnWiIxyJtaukjG7R8pSx8nG2NamICpGfljQsogj+FbQ=="], + + "all-debrid-js/tsdown/rolldown-plugin-dts/@babel/parser": ["@babel/parser@8.0.0-rc.1", "", { "dependencies": { "@babel/types": "^8.0.0-rc.1" }, "bin": "./bin/babel-parser.js" }, "sha512-6HyyU5l1yK/7h9Ki52i5h6mDAx4qJdiLQO4FdCyJNoB/gy3T3GGJdhQzzbZgvgZCugYBvwtQiWRt94QKedHnkA=="], + + "all-debrid-js/tsdown/rolldown-plugin-dts/@babel/types": ["@babel/types@8.0.0-rc.1", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.1", "@babel/helper-validator-identifier": "^8.0.0-rc.1" } }, "sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ=="], + + "all-debrid-js/tsdown/rolldown-plugin-dts/get-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="], + + "ast-kit/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.1", "", {}, "sha512-vi/pfmbrOtQmqgfboaBhaCU50G7mcySVu69VU8z+lYoPPB6WzI9VgV7WQfL908M4oeSH5fDkmoupIqoE0SdApw=="], + + "ast-kit/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0-rc.1", "", {}, "sha512-I4YnARytXC2RzkLNVnf5qFNFMzp679qZpmtw/V3Jt2uGnWiIxyJtaukjG7R8pSx8nG2NamICpGfljQsogj+FbQ=="], + "nodemon/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "ogi-addon/tsdown/rolldown/@oxc-project/types": ["@oxc-project/types@0.112.0", "", {}, "sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.3", "", { "os": "android", "cpu": "arm64" }, "sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm" }, "sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.3", "", { "os": "none", "cpu": "arm64" }, "sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.3", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "x64" }, "sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A=="], + + "ogi-addon/tsdown/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + + "ogi-addon/tsdown/rolldown-plugin-dts/@babel/generator": ["@babel/generator@8.0.0-rc.1", "", { "dependencies": { "@babel/parser": "^8.0.0-rc.1", "@babel/types": "^8.0.0-rc.1", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-3ypWOOiC4AYHKr8vYRVtWtWmyvcoItHtVqF8paFax+ydpmUdPsJpLBkBBs5ItmhdrwC3a0ZSqqFAdzls4ODP3w=="], + + "ogi-addon/tsdown/rolldown-plugin-dts/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0-rc.1", "", {}, "sha512-I4YnARytXC2RzkLNVnf5qFNFMzp679qZpmtw/V3Jt2uGnWiIxyJtaukjG7R8pSx8nG2NamICpGfljQsogj+FbQ=="], + + "ogi-addon/tsdown/rolldown-plugin-dts/@babel/parser": ["@babel/parser@8.0.0-rc.1", "", { "dependencies": { "@babel/types": "^8.0.0-rc.1" }, "bin": "./bin/babel-parser.js" }, "sha512-6HyyU5l1yK/7h9Ki52i5h6mDAx4qJdiLQO4FdCyJNoB/gy3T3GGJdhQzzbZgvgZCugYBvwtQiWRt94QKedHnkA=="], + + "ogi-addon/tsdown/rolldown-plugin-dts/@babel/types": ["@babel/types@8.0.0-rc.1", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.1", "@babel/helper-validator-identifier": "^8.0.0-rc.1" } }, "sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ=="], + + "ogi-addon/tsdown/rolldown-plugin-dts/get-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="], + "opengameinstaller-gui/electron-builder/app-builder-lib/7zip-bin": ["7zip-bin@5.1.1", "", {}, "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ=="], "opengameinstaller-gui/electron-builder/app-builder-lib/@electron/universal": ["@electron/universal@1.2.1", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.1.0", "asar": "^3.1.0", "debug": "^4.3.1", "dir-compare": "^2.4.0", "fs-extra": "^9.0.1", "minimatch": "^3.0.4", "plist": "^3.0.4" } }, "sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ=="], @@ -3024,22 +3354,88 @@ "opengameinstaller-gui/electron-builder/app-builder-lib/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], + "opengameinstaller-gui/electron-builder/app-builder-lib/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "opengameinstaller-gui/electron-builder/builder-util/7zip-bin": ["7zip-bin@5.1.1", "", {}, "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ=="], "opengameinstaller-gui/electron-builder/simple-update-notifier/semver": ["semver@7.0.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A=="], + "real-debrid-js/tsdown/rolldown/@oxc-project/types": ["@oxc-project/types@0.112.0", "", {}, "sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.3", "", { "os": "android", "cpu": "arm64" }, "sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm" }, "sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.3", "", { "os": "none", "cpu": "arm64" }, "sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.3", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "x64" }, "sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + + "real-debrid-js/tsdown/rolldown-plugin-dts/@babel/generator": ["@babel/generator@8.0.0-rc.1", "", { "dependencies": { "@babel/parser": "^8.0.0-rc.1", "@babel/types": "^8.0.0-rc.1", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-3ypWOOiC4AYHKr8vYRVtWtWmyvcoItHtVqF8paFax+ydpmUdPsJpLBkBBs5ItmhdrwC3a0ZSqqFAdzls4ODP3w=="], + + "real-debrid-js/tsdown/rolldown-plugin-dts/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0-rc.1", "", {}, "sha512-I4YnARytXC2RzkLNVnf5qFNFMzp679qZpmtw/V3Jt2uGnWiIxyJtaukjG7R8pSx8nG2NamICpGfljQsogj+FbQ=="], + + "real-debrid-js/tsdown/rolldown-plugin-dts/@babel/parser": ["@babel/parser@8.0.0-rc.1", "", { "dependencies": { "@babel/types": "^8.0.0-rc.1" }, "bin": "./bin/babel-parser.js" }, "sha512-6HyyU5l1yK/7h9Ki52i5h6mDAx4qJdiLQO4FdCyJNoB/gy3T3GGJdhQzzbZgvgZCugYBvwtQiWRt94QKedHnkA=="], + + "real-debrid-js/tsdown/rolldown-plugin-dts/@babel/types": ["@babel/types@8.0.0-rc.1", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.1", "@babel/helper-validator-identifier": "^8.0.0-rc.1" } }, "sha512-ubmJ6TShyaD69VE9DQrlXcdkvJbmwWPB8qYj0H2kaJi29O7vJT9ajSdBd2W8CG34pwL9pYA74fi7RHC1qbLoVQ=="], + + "real-debrid-js/tsdown/rolldown-plugin-dts/get-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="], + "tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "all-debrid-js/tsdown/rolldown-plugin-dts/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.1", "", {}, "sha512-vi/pfmbrOtQmqgfboaBhaCU50G7mcySVu69VU8z+lYoPPB6WzI9VgV7WQfL908M4oeSH5fDkmoupIqoE0SdApw=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + + "ogi-addon/tsdown/rolldown-plugin-dts/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.1", "", {}, "sha512-vi/pfmbrOtQmqgfboaBhaCU50G7mcySVu69VU8z+lYoPPB6WzI9VgV7WQfL908M4oeSH5fDkmoupIqoE0SdApw=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "opengameinstaller-gui/electron-builder/app-builder-lib/@electron/universal/dir-compare": ["dir-compare@2.4.0", "", { "dependencies": { "buffer-equal": "1.0.0", "colors": "1.0.3", "commander": "2.9.0", "minimatch": "3.0.4" }, "bin": { "dircompare": "src/cli/dircompare.js" } }, "sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA=="], "opengameinstaller-gui/electron-builder/app-builder-lib/@electron/universal/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "opengameinstaller-gui/electron-builder/app-builder-lib/electron-publish/mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], + "real-debrid-js/tsdown/rolldown-plugin-dts/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.1", "", {}, "sha512-vi/pfmbrOtQmqgfboaBhaCU50G7mcySVu69VU8z+lYoPPB6WzI9VgV7WQfL908M4oeSH5fDkmoupIqoE0SdApw=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + "opengameinstaller-gui/electron-builder/app-builder-lib/@electron/universal/dir-compare/buffer-equal": ["buffer-equal@1.0.0", "", {}, "sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ=="], "opengameinstaller-gui/electron-builder/app-builder-lib/@electron/universal/dir-compare/commander": ["commander@2.9.0", "", { "dependencies": { "graceful-readlink": ">= 1.0.0" } }, "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A=="], "opengameinstaller-gui/electron-builder/app-builder-lib/@electron/universal/dir-compare/minimatch": ["minimatch@3.0.4", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + + "all-debrid-js/tsdown/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "ogi-addon/tsdown/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "real-debrid-js/tsdown/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], } } diff --git a/package.json b/package.json index 0c06b274..f949ccca 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,10 @@ "name": "ogi-monorepo", "private": true, "scripts": { - "release": "cd packages/ogi-addon && bun run release && cd ../real-debrid && bun run release && cd ../all-debrid && bun run release", - "dev": "cd application && bun run dev", + "release": "bun run --filter './packages/*' release", + "dev": "bun run --filter 'application' dev", "format": "prettier --write \"packages/**/*.{ts,svelte,json}\" \"application/**/*.{ts,svelte,json}\" \"web/**/*.{ts,svelte,json}\" \"test-addon/**/*.{ts,svelte,json}\"", - "typecheck": "bun run build:typecheck-deps && bun run typecheck:ogi-addon && bun run typecheck:real-debrid && bun run typecheck:all-debrid && bun run typecheck:application && bun run typecheck:web && bun run typecheck:test-addon", - "build:typecheck-deps": "cd packages/ogi-addon && bun run build && cd ../real-debrid && bun run build && cd ../all-debrid && bun run build", - "typecheck:application": "cd application && bun run typecheck", - "typecheck:web": "cd web && bun run typecheck", - "typecheck:ogi-addon": "cd packages/ogi-addon && bun run typecheck", - "typecheck:real-debrid": "cd packages/real-debrid && bun run typecheck", - "typecheck:all-debrid": "cd packages/all-debrid && bun run typecheck", - "typecheck:test-addon": "cd test-addon && bun run typecheck" + "typecheck": "bun run --filter './packages/*' build && bun run --filter '*' typecheck" }, "workspaces": [ "packages/*", diff --git a/packages/addon-server/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc b/packages/addon-server/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc new file mode 120000 index 00000000..6100270f --- /dev/null +++ b/packages/addon-server/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc @@ -0,0 +1 @@ +../../CLAUDE.md \ No newline at end of file diff --git a/packages/addon-server/.gitignore b/packages/addon-server/.gitignore new file mode 100644 index 00000000..3e8f6c35 --- /dev/null +++ b/packages/addon-server/.gitignore @@ -0,0 +1,35 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store +build/ \ No newline at end of file diff --git a/packages/addon-server/CLAUDE.md b/packages/addon-server/CLAUDE.md new file mode 100644 index 00000000..ebda9951 --- /dev/null +++ b/packages/addon-server/CLAUDE.md @@ -0,0 +1,111 @@ +--- +description: Use Bun instead of Node.js, npm, pnpm, or vite. +globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" +alwaysApply: false +--- + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; +import { createRoot } from "react-dom/client"; + +// import .css files directly and it works +import './index.css'; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. diff --git a/packages/addon-server/README.md b/packages/addon-server/README.md new file mode 100644 index 00000000..dcb99a0d --- /dev/null +++ b/packages/addon-server/README.md @@ -0,0 +1,15 @@ +# addon-server + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.9. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/packages/addon-server/lib/_generated/event-proxy.ts b/packages/addon-server/lib/_generated/event-proxy.ts new file mode 100644 index 00000000..8553c3d8 --- /dev/null +++ b/packages/addon-server/lib/_generated/event-proxy.ts @@ -0,0 +1,107 @@ +// This file is generated by scripts/generate-event-proxy.ts. Do not edit by hand. +import type { + EventListenerTypes, + LibraryInfo, + OGIAddonServerSentEvent, + WebsocketMessageClient, + WebsocketMessageServer, +} from 'ogi-addon'; +import type { ConfigurationFile } from 'ogi-addon/config'; + +type ArgsBeforeEventResponse = T extends [ + ...infer Args, + unknown, +] + ? Args + : T; + +type ListenerEventArgs = + ArgsBeforeEventResponse>; + +export type TaskRunArgs = { + manifest?: Record; + downloadPath?: string; + name?: string; + taskName?: string; + libraryInfo?: LibraryInfo; + deferID?: string; +}; + +export type ServerEventArgs = { + 'authenticate': [config?: unknown]; + 'configure': [config: ConfigurationFile]; + 'config-update': [config: ConfigurationFile]; + 'launch-app': ListenerEventArgs<'launch-app'>; + 'search': ListenerEventArgs<'search'>; + 'setup': ListenerEventArgs<'setup'>; + 'response': [id: string, response?: unknown, statusError?: string]; + 'library-search': ListenerEventArgs<'library-search'>; + 'check-for-updates': ListenerEventArgs<'check-for-updates'>; + 'task-run': [task: TaskRunArgs]; + 'game-details': ListenerEventArgs<'game-details'>; + 'request-dl': ListenerEventArgs<'request-dl'>; + 'catalog': ListenerEventArgs<'catalog'>; +}; + +type SendEventProxyMethod = ( + ...args: ServerEventArgs[Event] +) => Promise; + +type CamelCaseEvent = + Event extends `${infer Head}-${infer Tail}` + ? `${Head}${Capitalize>}` + : Event; + +type SendEventProxyMethods = { + [Event in OGIAddonServerSentEvent as CamelCaseEvent]: SendEventProxyMethod; +}; + +export type SendEventProxy = SendEventProxyMethods & { + noResponse: SendEventProxyMethods; +}; + +type EventMessagePackers = { + [Event in OGIAddonServerSentEvent]: ( + args: ServerEventArgs[Event] + ) => Omit; +}; + +const eventMessagePackers: EventMessagePackers = { + 'authenticate': ([config]) => ({ args: config }), + 'configure': ([config]) => ({ args: config }), + 'config-update': ([config]) => ({ args: config }), + 'launch-app': ([data]) => ({ args: data }), + 'search': ([query]) => ({ args: query }), + 'setup': ([data]) => ({ args: data }), + 'response': ([id, response, statusError]) => ({ id, args: response, statusError }), + 'library-search': ([query]) => ({ args: query }), + 'check-for-updates': ([data]) => ({ args: data }), + 'task-run': ([task]) => ({ args: task }), + 'game-details': ([details]) => ({ args: details }), + 'request-dl': ([appID, info]) => ({ args: { appID, info } }), + 'catalog': () => ({ args: undefined }), +}; + +const toCamelCaseEvent = (event: string): string => { + return event.replace(/-([a-z])/g, (_, letter: string) => + letter.toUpperCase() + ); +}; + +export const eventAliases = Object.keys(eventMessagePackers).reduce( + (aliases, event) => { + aliases[toCamelCaseEvent(event)] = event as OGIAddonServerSentEvent; + return aliases; + }, + {} as Record +); + +export const buildEventMessage = ( + event: Event, + args: ServerEventArgs[Event] +): WebsocketMessageServer => { + return { + event, + ...eventMessagePackers[event](args), + }; +}; diff --git a/packages/addon-server/lib/addon-connection.ts b/packages/addon-server/lib/addon-connection.ts new file mode 100644 index 00000000..5616558b --- /dev/null +++ b/packages/addon-server/lib/addon-connection.ts @@ -0,0 +1,197 @@ +import wsLib from 'ws'; +import type { + OGIAddonConfiguration, + OGIAddonEvent, + WebsocketMessageClient, + WebsocketMessageServer, +} from 'ogi-addon'; +import type { ConfigurationFile } from 'ogi-addon/config'; +import type { AddonConfig, AddonServer } from './addon'; +import { + buildEventMessage, + eventAliases, + type SendEventProxy, + type ServerEventArgs, +} from './_generated/event-proxy'; +import { createClientMessageHandlers } from './handlers/client-message-handlers'; +import type { ClientMessageHandlers } from './handlers/types'; + +export class AddonConnection { + public addonInfo: OGIAddonConfiguration | undefined; + public ws: InstanceType; + public configTemplate: ConfigurationFile | undefined; + public filePath: string | undefined; + public addonLink: string | undefined; + public eventsAvailable: OGIAddonEvent[] = []; + public readonly events: SendEventProxy; + private pendingResponses: Map< + string, + { + resolve: (value: WebsocketMessageClient) => void; + reject: (reason?: any) => void; + } + > = new Map(); + private messageHandler: ((message: string | Buffer) => void) | null = null; + private config: AddonConfig; + private server: AddonServer; + private clientEventHandlers: ClientMessageHandlers; + + constructor( + ws: InstanceType, + config: AddonConfig, + server: AddonServer + ) { + this.ws = ws; + this.config = config; + this.server = server; + this.events = this.createSendEventProxy(true); + this.clientEventHandlers = createClientMessageHandlers(); + } + + public configure(config: ConfigurationFile): void { + this.events.noResponse.configUpdate(config); + } + + public async setupWebsocket(): Promise { + return new Promise((resolve, _) => { + const authenticationTimeout = setTimeout(() => { + this.ws.close(1008, 'Authentication timeout'); + console.error('Client kicked due to authentication timeout'); + resolve(false); + }, 1000); + + this.messageHandler = async (message: string | Buffer) => { + const data = this.parseClientMessage(message); + if (!data) return; + + if (this.resolvePendingResponse(data)) return; + + const handler = this.clientEventHandlers[data.event]; + if (!handler) return; + + await handler( + { + connection: this, + config: this.config, + server: this.server, + authenticationTimeout, + resolveAuthentication: resolve, + }, + data + ); + }; + + this.ws.on('message', this.messageHandler); + + this.ws.on('close', () => + this.rejectPendingResponses('Websocket closed') + ); + this.ws.on('error', () => this.rejectPendingResponses('Websocket error')); + }); + } + + private parseClientMessage( + message: string | Buffer + ): WebsocketMessageClient | undefined { + try { + return JSON.parse(message.toString()); + } catch (err) { + console.error('Failed to parse websocket message:', err); + this.ws.close(1008, 'Invalid JSON message'); + return undefined; + } + } + + private resolvePendingResponse(data: WebsocketMessageClient): boolean { + if ( + data.event !== 'response' || + !data.id || + !this.pendingResponses.has(data.id) + ) { + return false; + } + + const pending = this.pendingResponses.get(data.id)!; + this.pendingResponses.delete(data.id); + if (!data.args || data.statusError) { + if (!data.args && !data.statusError) { + pending.resolve({ + event: 'response', + args: undefined, + id: data.id, + }); + } else { + pending.reject(data.statusError); + } + return true; + } + pending.resolve(data); + return true; + } + + private rejectPendingResponses(reason: string): void { + for (const pending of this.pendingResponses.values()) { + pending.reject(new Error(reason)); + } + this.pendingResponses.clear(); + } + + public sendEventMessage( + message: WebsocketMessageServer, + expectResponse: boolean = true + ): Promise { + if (expectResponse) { + message.id = Math.random().toString(36).substring(7); + } + return new Promise((resolve, reject) => { + // CLOSED state is 3 + if (this.ws.readyState === 3) { + reject(new Error('Websocket closed')); + return; + } + + this.ws.send(JSON.stringify(message), (err: Error | null | undefined) => { + if (err) { + reject(err); + return; + } + }); + + if (expectResponse && message.id) { + // Store the pending response handler + this.pendingResponses.set(message.id, { resolve, reject }); + } else { + resolve({ event: 'response', args: 'OK' }); + } + }); + } + + private createSendEventProxy(defaultExpectResponse: boolean): SendEventProxy { + return new Proxy( + {}, + { + get: (_, property) => { + if (property === 'noResponse') { + return this.createSendEventProxy(false); + } + + if (typeof property !== 'string') { + return undefined; + } + + const event = eventAliases[property]; + if (!event) { + return undefined; + } + + return (...args: ServerEventArgs[typeof event]) => { + return this.sendEventMessage( + buildEventMessage(event, args), + event === 'response' ? false : defaultExpectResponse + ); + }; + }, + } + ) as SendEventProxy; + } +} diff --git a/packages/addon-server/lib/addon.ts b/packages/addon-server/lib/addon.ts new file mode 100644 index 00000000..53e72f65 --- /dev/null +++ b/packages/addon-server/lib/addon.ts @@ -0,0 +1,152 @@ +import { AddonConnection } from './addon-connection'; +import { WebSocketServer } from 'ws'; +import http, { type IncomingMessage } from 'http'; +import type { Duplex } from 'stream'; +import { EventEmitter } from 'events'; +import type { Notification } from 'ogi-addon'; +import { DeferredTasksManager } from './deffered'; +import type { ConfigurationFile } from 'ogi-addon/config'; + +export type AddonConfig = { + securityCheck: boolean; + port: number; + secret?: string; +}; + +type AddonEvents = + | 'connect' + | 'disconnect' + | 'start' + | 'notification' + | 'input-asked'; + +type AddonEventListeners = { + connect: (connection: AddonConnection) => void; + disconnect: (reason: string) => void; + start: () => void; + notification: (notification: Notification) => void; + 'input-asked': ( + title: string, + description: string, + configuration: ConfigurationFile, + reply: (result: Record) => void + ) => void | Promise; +}; + +export class AddonServer { + private connections: Set = new Set(); + private clients: Map = new Map(); + + private server = http.createServer(); + private wss: WebSocketServer | undefined; + private upgradeListener?: ( + req: IncomingMessage, + socket: Duplex, + head: Buffer + ) => void; + + private deferredTasksManager: DeferredTasksManager = + new DeferredTasksManager(); + public constructor(private readonly config: AddonConfig) { + // create a random secret if not provided + if (config.securityCheck && !config.secret) { + config.secret = `${Math.floor(new Date().getTime() + Math.random() * 10000)}-${Math.floor(Math.random() * 10000)}`; + } + } + + private eventEmitter = new EventEmitter(); + + public emit( + event: T, + ...args: Parameters + ): this { + this.eventEmitter.emit(event, ...args); + return this; + } + + public getConnections(): Set { + return this.connections; + } + + public getClient(id: string): AddonConnection | undefined { + return this.clients.get(id); + } + + public addClient(id: string, connection: AddonConnection): void { + this.clients.set(id, connection); + } + + public getDeferredTasksManager(): DeferredTasksManager { + return this.deferredTasksManager; + } + + public on( + event: T, + listener: AddonEventListeners[T] + ): this { + this.eventEmitter.on(event, listener); + return this; + } + + public extend(server: http.Server): this { + this.server = server; + return this; + } + + public getSecret(): string { + return this.config.secret!; + } + + public stop(): void { + if (this.upgradeListener) { + this.server.removeListener('upgrade', this.upgradeListener); + this.upgradeListener = undefined; + } + this.connections.forEach((connection) => { + connection.ws.close(); + }); + this.clients.forEach((client) => { + client.ws.close(); + }); + this.wss?.close(); + this.wss = undefined; + this.server.close(); + this.connections.clear(); + this.clients.clear(); + } + + public async start(): Promise { + if (this.upgradeListener) { + this.server.removeListener('upgrade', this.upgradeListener); + this.upgradeListener = undefined; + } + this.wss?.close(); + this.wss = new WebSocketServer({ noServer: true }); + + this.upgradeListener = (req, socket, head) => { + const wss = this.wss; + if (!wss) return; + wss.handleUpgrade(req, socket, head, (ws) => { + wss.emit('connection', ws, req); + }); + }; + this.server.on('upgrade', this.upgradeListener); + + this.wss.on('connection', (ws) => { + const connection = new AddonConnection(ws, this.config, this); + this.connections.add(connection); + connection.setupWebsocket().then((success) => { + if (!success) { + this.connections.delete(connection); + this.eventEmitter.emit('disconnect', 'Failed to setup websocket'); + } else { + this.eventEmitter.emit('connect', connection); + } + }); + }); + + this.server.listen(this.config.port, () => { + this.eventEmitter.emit('start'); + }); + } +} diff --git a/application/src/electron/server/DeferrableTask.ts b/packages/addon-server/lib/deffered.ts similarity index 75% rename from application/src/electron/server/DeferrableTask.ts rename to packages/addon-server/lib/deffered.ts index c651db6b..0b3b21e1 100644 --- a/application/src/electron/server/DeferrableTask.ts +++ b/packages/addon-server/lib/deffered.ts @@ -48,18 +48,10 @@ export class DeferrableTask { } // Singleton pattern to ensure DeferredTasks maintains state across imports -class DeferredTasksManager { - private static instance: DeferredTasksManager; +export class DeferredTasksManager { private tasks: Record> = {}; - private constructor() {} - - public static getInstance(): DeferredTasksManager { - if (!DeferredTasksManager.instance) { - DeferredTasksManager.instance = new DeferredTasksManager(); - } - return DeferredTasksManager.instance; - } + public constructor() {} public getTasks(): Record> { return this.tasks; @@ -73,9 +65,3 @@ class DeferredTasksManager { delete this.tasks[id]; } } - -// Export the tasks object as a getter to maintain backward compatibility -export const DeferredTasks = DeferredTasksManager.getInstance(); - -// Also export with the old misspelled name for backward compatibility -export const DefferedTasks = DeferredTasks; diff --git a/packages/addon-server/lib/handlers/client-message-handlers.ts b/packages/addon-server/lib/handlers/client-message-handlers.ts new file mode 100644 index 00000000..a1406779 --- /dev/null +++ b/packages/addon-server/lib/handlers/client-message-handlers.ts @@ -0,0 +1,246 @@ +import type { + ClientSentEventTypes, + Notification, + OGIAddonConfiguration, + OGIAddonEvent, + StoreData, +} from 'ogi-addon'; +import type { ConfigurationFile } from 'ogi-addon/config'; +import { DeferrableTask } from '../deffered'; +import { + closeProtocolError, + getClientsSupporting, + requireAuthenticated, + requireMessageId, +} from './helpers'; +import type { ClientMessageHandler, ClientMessageHandlers } from './types'; + +const handleNotification: ClientMessageHandler = ({ server }, message) => { + server.emit('notification', message.args[0] as Notification); +}; + +const handleAuthenticate: ClientMessageHandler = (context, message) => { + const { connection, config, server } = context; + clearTimeout(context.authenticationTimeout); + + const addonInfo = message.args as OGIAddonConfiguration; + connection.addonInfo = addonInfo; + if ( + config.securityCheck && + (!message.args.secret || message.args.secret !== config.secret) + ) { + closeProtocolError( + context, + 'Client attempted to authenticate with an invalid secret' + ); + context.resolveAuthentication(false); + return; + } + + if (server.getClient(addonInfo.id)) { + closeProtocolError( + context, + 'Client attempted to authenticate with an ID that is already in use' + ); + context.resolveAuthentication(false); + return; + } + + console.log('Client authenticated:', message.args.name); + server.addClient(addonInfo.id, connection); + context.resolveAuthentication(true); +}; + +const handleConfigure: ClientMessageHandler = (context, message) => { + if (!requireAuthenticated(context, 'config')) return; + + context.connection.configTemplate = message.args; +}; + +const handleDeferUpdate: ClientMessageHandler = (context, message) => { + if (!requireAuthenticated(context, 'defer-update')) return; + if (!message.args) return; + + if (!message.args.deferID) { + closeProtocolError( + context, + 'Client attempted to send defer-update without an ID' + ); + return; + } + + const deferredTask = context.server.getDeferredTasksManager().getTasks()[ + message.args.deferID + ]; + if (!deferredTask) { + closeProtocolError( + context, + 'Client attempted to send defer-update with an invalid ID' + ); + return; + } + + if (deferredTask.addonOwner !== context.connection.addonInfo!.id) return; + deferredTask.logs = message.args.logs; + deferredTask.progress = message.args.progress; + if (message.args.failed) { + deferredTask.failed = message.args.failed; + deferredTask.finished = true; + } +}; + +const handleInputAsked: ClientMessageHandler = (context, message) => { + if (!requireAuthenticated(context, 'input-asked')) return; + if (!message.args) return; + + if (!message.args.config || !message.args.name || !message.args.description) { + closeProtocolError( + context, + 'Client attempted to send input-asked without a configuration' + ); + return; + } + + if (!requireMessageId(context, 'input-asked', message.id)) return; + + const configurationAsked = message.args.config as + | ConfigurationFile + | undefined; + const name = message.args.name as string; + const description = message.args.description as string; + if (!configurationAsked || !name || !description) { + closeProtocolError( + context, + 'Client attempted to send input-asked without a configuration' + ); + return; + } + + context.server.emit( + 'input-asked', + name, + description, + configurationAsked, + (reply: Record) => { + context.connection.events.response(message.id!, reply); + } + ); +}; + +const handleTaskUpdate: ClientMessageHandler = (context, message) => { + if (!requireAuthenticated(context, 'task-update')) return; + if (!message.args.id) { + closeProtocolError( + context, + 'Client attempted to send task-update without an ID' + ); + return; + } + + const taskUpdate = message.args as ClientSentEventTypes['task-update']; + let task = context.server.getDeferredTasksManager().getTasks()[ + message.args.id + ]; + + if (!task) { + task = new DeferrableTask(async () => { + return null; + }, context.connection.addonInfo!.id); + task.id = taskUpdate.id; + context.server.getDeferredTasksManager().addTask(task); + } + + task.progress = taskUpdate.progress; + task.logs = taskUpdate.logs; + task.finished = taskUpdate.finished; + task.failed = taskUpdate.failed; + + if (taskUpdate.failed) { + task.finished = true; + return; + } + + if (taskUpdate.finished && !taskUpdate.failed) { + context.server.getDeferredTasksManager().removeTask(message.args.id); + } +}; + +const handleGetAppDetails: ClientMessageHandler = async (context, message) => { + if (!requireAuthenticated(context, 'get-app-details')) return; + if (!requireMessageId(context, 'get-app-details', message.id)) return; + + const { appID, storefront }: ClientSentEventTypes['get-app-details'] = + message.args; + const clientsWithStorefront = getClientsSupporting( + context, + storefront, + 'game-details' + ); + + let appDetails: StoreData | undefined; + for (const client of clientsWithStorefront) { + const response = await client.events.gameDetails({ appID, storefront }); + if (response.args) { + appDetails = response.args; + break; + } + } + + if (!appDetails) { + console.error('No app details found for client'); + context.connection.events.response(message.id, undefined); + return; + } + + context.connection.events.response(message.id, appDetails); + console.log('Sent app details to client'); +}; + +const handleSearchAppName: ClientMessageHandler = async (context, message) => { + if (!requireAuthenticated(context, 'search-app-name')) return; + if (!requireMessageId(context, 'search-app-name', message.id)) return; + + const { query, storefront }: ClientSentEventTypes['search-app-name'] = + message.args; + const clientsWithStorefront = getClientsSupporting( + context, + storefront, + 'library-search' + ); + const searchResult: StoreData[] = []; + + for (const client of clientsWithStorefront) { + const response = await client.events.librarySearch(query); + if (response.args) { + searchResult.push(...response.args); + } + } + + context.connection.events.response(message.id, searchResult); +}; + +const handleFlag: ClientMessageHandler = (context, message) => { + if (!requireAuthenticated(context, 'flag')) return; + + if (message.args.flag === 'events-available') { + console.log( + 'Setting events-available to', + message.args.value, + 'for addon', + context.connection.addonInfo!.id + ); + context.connection.eventsAvailable = message.args.value as OGIAddonEvent[]; + } +}; + +export const createClientMessageHandlers = (): ClientMessageHandlers => ({ + notification: handleNotification, + authenticate: handleAuthenticate, + configure: handleConfigure, + 'defer-update': handleDeferUpdate, + 'input-asked': handleInputAsked, + 'task-update': handleTaskUpdate, + 'get-app-details': handleGetAppDetails, + 'search-app-name': handleSearchAppName, + flag: handleFlag, +}); diff --git a/packages/addon-server/lib/handlers/helpers.ts b/packages/addon-server/lib/handlers/helpers.ts new file mode 100644 index 00000000..6644df84 --- /dev/null +++ b/packages/addon-server/lib/handlers/helpers.ts @@ -0,0 +1,48 @@ +import type { OGIAddonEvent } from 'ogi-addon'; +import type { HandlerContext } from './types'; +import { supportsStorefront } from '../lib'; + +export const closeProtocolError = ( + { connection }: HandlerContext, + message: string +): void => { + console.error(message); + connection.ws.close(1008, message); +}; + +export const requireAuthenticated = ( + context: HandlerContext, + event: string +): boolean => { + if (context.connection.addonInfo) return true; + + closeProtocolError( + context, + `Client attempted to send ${event} before authentication` + ); + return false; +}; + +export const requireMessageId = ( + context: HandlerContext, + event: string, + id: string | undefined +): id is string => { + if (id) return true; + + closeProtocolError(context, `Client attempted to send ${event} without an ID`); + return false; +}; + +export const getClientsSupporting = ( + { server }: HandlerContext, + storefront: string, + event: OGIAddonEvent +) => { + return Array.from(server.getConnections().values()).filter( + (client) => + supportsStorefront(client.addonInfo?.storefronts, storefront) && + client.eventsAvailable.includes(event) + ); +}; + diff --git a/packages/addon-server/lib/handlers/types.ts b/packages/addon-server/lib/handlers/types.ts new file mode 100644 index 00000000..3ba51e98 --- /dev/null +++ b/packages/addon-server/lib/handlers/types.ts @@ -0,0 +1,21 @@ +import type { OGIAddonClientSentEvent, WebsocketMessageClient } from 'ogi-addon'; +import type { AddonConnection } from '../addon-connection'; +import type { AddonConfig, AddonServer } from '../addon'; + +export type HandlerContext = { + connection: AddonConnection; + config: AddonConfig; + server: AddonServer; + authenticationTimeout: Timer; + resolveAuthentication: (authenticated: boolean) => void; +}; + +export type ClientMessageHandler = ( + context: HandlerContext, + message: WebsocketMessageClient +) => Promise | void; + +export type ClientMessageHandlers = Partial< + Record +>; + diff --git a/packages/addon-server/lib/index.ts b/packages/addon-server/lib/index.ts new file mode 100644 index 00000000..29e0c7cf --- /dev/null +++ b/packages/addon-server/lib/index.ts @@ -0,0 +1,4 @@ +export * from './addon'; +export * from './addon-connection'; +export * from './deffered'; +export * from './lib'; diff --git a/packages/addon-server/lib/lib.ts b/packages/addon-server/lib/lib.ts new file mode 100644 index 00000000..c0e0eb40 --- /dev/null +++ b/packages/addon-server/lib/lib.ts @@ -0,0 +1,7 @@ +export function supportsStorefront( + storefronts: readonly string[] | undefined, + storefront: string +): boolean { + if (!storefronts) return false; + return storefronts.includes('*') || storefronts.includes(storefront); +} diff --git a/packages/addon-server/package.json b/packages/addon-server/package.json new file mode 100644 index 00000000..af7774b7 --- /dev/null +++ b/packages/addon-server/package.json @@ -0,0 +1,39 @@ +{ + "name": "@ogi-sdk/addon-server", + "type": "module", + "version": "1.0.0", + "module": "./build/index.mjs", + "main": "./build/index.cjs", + "exports": { + ".": { + "import": { + "default": "./build/index.mjs", + "types": "./build/index.d.mts" + }, + "require": { + "default": "./build/index.cjs", + "types": "./build/index.d.cts" + } + } + }, + "scripts": { + "generate:event-proxy": "bun scripts/generate-event-proxy.ts", + "prebuild": "bun run generate:event-proxy", + "build": "tsdown --config tsdown.config.js" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/websocket": "^1.0.10", + "@types/ws": "^8.18.1", + "tsdown": "^0.21.10" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "elysia": "^1.4.28", + "ogi-addon": "workspace:*", + "websocket": "^1.0.35", + "ws": "^8.20.0" + } +} diff --git a/packages/addon-server/scripts/generate-event-proxy.ts b/packages/addon-server/scripts/generate-event-proxy.ts new file mode 100644 index 00000000..e97baa15 --- /dev/null +++ b/packages/addon-server/scripts/generate-event-proxy.ts @@ -0,0 +1,167 @@ +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const packageRoot = join(scriptDir, '..'); +const repoRoot = join(packageRoot, '..', '..'); +const addonMainPath = join(repoRoot, 'packages', 'ogi-addon', 'src', 'main.ts'); +const outputPath = join(packageRoot, 'lib', '_generated', 'event-proxy.ts'); + +const source = await Bun.file(addonMainPath).text(); + +const serverEventUnion = source.match( + /export type OGIAddonServerSentEvent =([\s\S]*?);/ +); + +if (!serverEventUnion) { + throw new Error('Could not find OGIAddonServerSentEvent in ogi-addon'); +} + +const events = [...serverEventUnion[1]!.matchAll(/'([^']+)'/g)].map( + (match) => match[1]! +); + +const camelCaseEvent = (event: string): string => { + return event.replace(/-([a-z])/g, (_, letter: string) => + letter.toUpperCase() + ); +}; + +const listenerEvents = new Set( + [ + ...source.matchAll(/^\s*'([^']+)': \(/gm), + ...source.matchAll(/^\s*(\w+): \(/gm), + ] + .map((match) => match[1]) + .filter(Boolean) +); + +const argsTypeForEvent = (event: string): string => { + if (event === 'authenticate') return '[config?: unknown]'; + if (event === 'configure') return '[config: ConfigurationFile]'; + if (event === 'config-update') return '[config: ConfigurationFile]'; + if (event === 'response') + return '[id: string, response?: unknown, statusError?: string]'; + if (event === 'task-run') return '[task: TaskRunArgs]'; + if (listenerEvents.has(event)) return `ListenerEventArgs<'${event}'>`; + + throw new Error(`No argument mapping for server event "${event}"`); +}; + +const packerForEvent = (event: string): string => { + if (event === 'catalog') return '() => ({ args: undefined })'; + if (event === 'request-dl') + return '([appID, info]) => ({ args: { appID, info } })'; + if (event === 'response') { + return '([id, response, statusError]) => ({ id, args: response, statusError })'; + } + + const paramName = + event === 'library-search' + ? 'query' + : event === 'game-details' + ? 'details' + : event === 'check-for-updates' || event === 'launch-app' + ? 'data' + : event === 'task-run' + ? 'task' + : event === 'response' + ? 'response' + : event === 'authenticate' + ? 'config' + : event === 'configure' || event === 'config-update' + ? 'config' + : event === 'search' + ? 'query' + : 'data'; + + return `([${paramName}]) => ({ args: ${paramName} })`; +}; + +const generated = `// This file is generated by scripts/generate-event-proxy.ts. Do not edit by hand. +import type { + EventListenerTypes, + LibraryInfo, + OGIAddonServerSentEvent, + WebsocketMessageClient, + WebsocketMessageServer, +} from 'ogi-addon'; +import type { ConfigurationFile } from 'ogi-addon/config'; + +type ArgsBeforeEventResponse = T extends [ + ...infer Args, + unknown, +] + ? Args + : T; + +type ListenerEventArgs = + ArgsBeforeEventResponse>; + +export type TaskRunArgs = { + manifest?: Record; + downloadPath?: string; + name?: string; + taskName?: string; + libraryInfo?: LibraryInfo; + deferID?: string; +}; + +export type ServerEventArgs = { +${events.map((event) => ` '${event}': ${argsTypeForEvent(event)};`).join('\n')} +}; + +type SendEventProxyMethod = ( + ...args: ServerEventArgs[Event] +) => Promise; + +type CamelCaseEvent = + Event extends \`\${infer Head}-\${infer Tail}\` + ? \`\${Head}\${Capitalize>}\` + : Event; + +type SendEventProxyMethods = { + [Event in OGIAddonServerSentEvent as CamelCaseEvent]: SendEventProxyMethod; +}; + +export type SendEventProxy = SendEventProxyMethods & { + noResponse: SendEventProxyMethods; +}; + +type EventMessagePackers = { + [Event in OGIAddonServerSentEvent]: ( + args: ServerEventArgs[Event] + ) => Omit; +}; + +const eventMessagePackers: EventMessagePackers = { +${events.map((event) => ` '${event}': ${packerForEvent(event)},`).join('\n')} +}; + +const toCamelCaseEvent = (event: string): string => { + return event.replace(/-([a-z])/g, (_, letter: string) => + letter.toUpperCase() + ); +}; + +export const eventAliases = Object.keys(eventMessagePackers).reduce( + (aliases, event) => { + aliases[toCamelCaseEvent(event)] = event as OGIAddonServerSentEvent; + return aliases; + }, + {} as Record +); + +export const buildEventMessage = ( + event: Event, + args: ServerEventArgs[Event] +): WebsocketMessageServer => { + return { + event, + ...eventMessagePackers[event](args), + }; +}; +`; + +await Bun.write(outputPath, generated); +console.log(`Generated ${outputPath}`); diff --git a/packages/addon-server/tsconfig.json b/packages/addon-server/tsconfig.json new file mode 100644 index 00000000..c59d0ac9 --- /dev/null +++ b/packages/addon-server/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "baseUrl": ".", + "paths": { + "@/*": ["lib/*"] + }, + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/packages/addon-server/tsdown.config.js b/packages/addon-server/tsdown.config.js new file mode 100644 index 00000000..fca7a751 --- /dev/null +++ b/packages/addon-server/tsdown.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: ['lib/**'], + splitting: false, + sourcemap: true, + clean: true, + format: ['cjs', 'esm'], + dts: true, + outDir: 'build', + target: false, +}); diff --git a/packages/executor/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc b/packages/executor/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc new file mode 120000 index 00000000..6100270f --- /dev/null +++ b/packages/executor/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc @@ -0,0 +1 @@ +../../CLAUDE.md \ No newline at end of file diff --git a/packages/executor/.gitignore b/packages/executor/.gitignore new file mode 100644 index 00000000..3e8f6c35 --- /dev/null +++ b/packages/executor/.gitignore @@ -0,0 +1,35 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store +build/ \ No newline at end of file diff --git a/packages/executor/CLAUDE.md b/packages/executor/CLAUDE.md new file mode 100644 index 00000000..ebda9951 --- /dev/null +++ b/packages/executor/CLAUDE.md @@ -0,0 +1,111 @@ +--- +description: Use Bun instead of Node.js, npm, pnpm, or vite. +globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" +alwaysApply: false +--- + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; +import { createRoot } from "react-dom/client"; + +// import .css files directly and it works +import './index.css'; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. diff --git a/packages/executor/README.md b/packages/executor/README.md new file mode 100644 index 00000000..b9f0e2fb --- /dev/null +++ b/packages/executor/README.md @@ -0,0 +1,15 @@ +# addon-executor + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.13. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/packages/executor/lib/addon-setup.ts b/packages/executor/lib/addon-setup.ts new file mode 100644 index 00000000..dc7e637d --- /dev/null +++ b/packages/executor/lib/addon-setup.ts @@ -0,0 +1,217 @@ +import { Addon } from '@/addon'; +import { spawn } from 'child_process'; +import { join } from 'path'; +import { access } from 'fs/promises'; +import parseArgsStringToArgv from 'string-argv'; +import { createWriteStream, rmSync, unlink } from 'fs'; +import { Git } from '@/git'; + +export class AddonSetup { + public git: Git; + constructor(private readonly addon: Addon) { + this.git = new Git(this.addon); + } + + private runScript(script: string) { + const startCommand = Addon.intoExecutor(script); + const [command, ...args] = parseArgsStringToArgv(startCommand); + + console.log(`[${this.addon.config.name}] Running script: ${startCommand}`); + // get the installation log path + const installationLogPath = join( + this.addon.config.path, + 'installation.log' + ); + // create a write stream to the installation log + const installationLogStream = createWriteStream(installationLogPath); + // write at the beginning the command + + installationLogStream.write(`--------------------------------`); + installationLogStream.write( + `[${this.addon.config.name}] Running script: ${startCommand}` + ); + installationLogStream.write(`--------------------------------`); + + const child = spawn(command!, args, { + cwd: this.addon.config.path, + stdio: ['ignore', 'pipe', 'pipe'], + }); + child.stdout?.on('data', (data) => { + console.log(`[${this.addon.config.name}] ${data.toString()}`); + installationLogStream.write(data.toString()); + }); + child.stderr?.on('data', (data) => { + console.error(`[${this.addon.config.name}] ${data.toString()}`); + installationLogStream.write(data.toString()); + }); + child.on('error', (error) => { + console.error(`[${this.addon.config.name}] ${error}`); + installationLogStream.write(error.message); + }); + + child.on('exit', (code, signal) => { + console.log( + `[${this.addon.config.name}] Exited with code ${code} and signal ${signal}` + ); + installationLogStream.write( + `[${this.addon.config.name}] Exited with code ${code} and signal ${signal}` + ); + installationLogStream.end(); + if (code !== 0) { + throw new Error( + `[${this.addon.config.name}] Exited with code ${code} and signal ${signal}` + ); + } + }); + + return child; + } + + private spawnScriptCapture( + script: string, + scriptName: string + ): Promise { + const startCommand = Addon.intoExecutor(script); + const [command, ...args] = parseArgsStringToArgv(startCommand); + const name = this.addon.config.name; + + return new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + const child = spawn(command!, args, { + cwd: this.addon.config.path, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + child.stdout?.on('data', (data: Buffer) => { + const t = data.toString(); + console.log(`[${name}@${scriptName}] ${t}`); + stdout += t; + }); + child.stderr?.on('data', (data: Buffer) => { + const t = data.toString(); + console.error(`[${name}@${scriptName}] ${t}`); + stderr += t; + }); + + child.on('close', (code) => { + if (code !== 0) { + reject( + new Error( + `Addon ${name} exited with error: ${code}\n${stderr}` + ) + ); + return; + } + resolve(stdout); + }); + child.on('error', reject); + }); + } + + /** + * Runs optional pre/setup/post scripts and returns combined stdout (for `installation.log`). + */ + public async collectSetupLog(): Promise { + if (!this.addon.config.scripts) { + const cfg = this.addon.loadAddonConfig(this.addon.config.path); + this.addon.config.scripts = cfg.scripts; + } + const scripts = this.addon.config.scripts; + const addonName = this.addon.config.name; + let setupLogs = ''; + + if (scripts.preSetup) { + setupLogs += `\nRunning pre-setup script for ${addonName}...\n> ${scripts.preSetup}\n`; + setupLogs += await this.spawnScriptCapture(scripts.preSetup, 'pre-setup'); + } + if (scripts.setup) { + setupLogs += `\nRunning setup script for ${addonName}...\n> ${scripts.setup}\n`; + setupLogs += await this.spawnScriptCapture(scripts.setup, 'setup'); + } + if (scripts.postSetup) { + setupLogs += `\nRunning post-setup script for ${addonName}...\n> ${scripts.postSetup}\n`; + setupLogs += await this.spawnScriptCapture( + scripts.postSetup, + 'post-setup' + ); + } + + return setupLogs; + } + + public setup(): Promise { + if (!this.addon.config.scripts?.setup) { + throw new Error('Setup script not found'); + } + const process = this.runScript(this.addon.config.scripts.setup); + + return new Promise((resolve, reject) => { + process.on('exit', (code, signal) => { + if (code !== 0) { + reject( + new Error( + `[${this.addon.config.name}] Exited with code ${code} and signal ${signal}` + ) + ); + } + resolve(); + }); + }); + } + + public preSetup(): Promise { + if (!this.addon.config.scripts?.preSetup) { + throw new Error('Pre-setup script not found'); + } + const process = this.runScript(this.addon.config.scripts.preSetup); + return new Promise((resolve, reject) => { + process.on('exit', (code, signal) => { + if (code !== 0) { + reject( + new Error( + `[${this.addon.config.name}] Exited with code ${code} and signal ${signal}` + ) + ); + } + resolve(); + }); + }); + } + + public postSetup(): Promise { + if (!this.addon.config.scripts?.postSetup) { + throw new Error('Post-setup script not found'); + } + const process = this.runScript(this.addon.config.scripts.postSetup); + return new Promise((resolve, reject) => { + process.on('exit', (code, signal) => { + if (code !== 0) { + reject( + new Error( + `[${this.addon.config.name}] Exited with code ${code} and signal ${signal}` + ) + ); + } + resolve(); + }); + }); + } + + public async runSetup(): Promise { + // delete the installation log + rmSync(join(this.addon.config.path, 'installation.log')); + await this.preSetup(); + await this.setup(); + await this.postSetup(); + } + + public async isInstalled(): Promise { + try { + await access(join(this.addon.config.path, 'installation.log')); + return true; + } catch { + return false; + } + } +} diff --git a/packages/executor/lib/addon.ts b/packages/executor/lib/addon.ts new file mode 100644 index 00000000..cadbea7f --- /dev/null +++ b/packages/executor/lib/addon.ts @@ -0,0 +1,132 @@ +import { ChildProcess, spawn } from 'child_process'; +import { readFileSync } from 'fs'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import parseArgsStringToArgv from 'string-argv'; +import z from 'zod'; +import { AddonSetup } from '@/addon-setup'; + +export type AddonConfig = { + port: number; + secret: string; + path: string; + name: string; + scripts?: z.infer['scripts']; +}; + +export const AddonFileConfigurationSchema = z.object({ + author: z.string(), + scripts: z.object({ + setup: z.string().optional(), + run: z.string(), + preSetup: z.string().optional(), + postSetup: z.string().optional(), + }), +}); + +export type AddonFileConfiguration = z.infer< + typeof AddonFileConfigurationSchema +>; + +export class Addon { + public config: AddonConfig; + private process: ChildProcess | null = null; + private abort = new AbortController(); + public setup: AddonSetup; + + constructor(config: AddonConfig) { + this.config = config; + this.setup = new AddonSetup(this); + } + + public static getBunPath(): string { + if (process.platform === 'win32') { + return join(process.env.USERPROFILE || '', '.bun', 'bin', 'bun.exe'); + } else { + return join(process.env.HOME || '', '.bun', 'bin', 'bun'); + } + } + + public loadAddonConfig( + path: string + ): z.infer { + const addonConfig = readFileSync(join(path, 'addon.json'), 'utf-8'); + return AddonFileConfigurationSchema.parse(addonConfig); + } + + public static intoExecutor(fullCommand: string): string { + // turn any 'bun' from the line command into the full path to bun + fullCommand = fullCommand.replace( + /^(\.?[\\/]?bun(?:.exe)?)\b/, + `"${this.getBunPath()}"` + ); + return fullCommand; + } + + public start(): void { + if (!this.config.scripts?.run) { + // load the addon config + const addonConfig = this.loadAddonConfig(this.config.path); + this.config.scripts = addonConfig.scripts; + } + + // start the addon from the path with the given config + const startCommand = Addon.intoExecutor(this.config.scripts.run); + // split into the command and the arguments + const [command, ...args] = parseArgsStringToArgv(startCommand); + const child = spawn( + command!, + [ + ...args, + '--addonPort=' + this.config.port.toString(), + '--addonSecret=' + this.config.secret, + ], + { + cwd: this.config.path, + stdio: ['ignore', 'pipe', 'pipe'], + signal: this.abort.signal, + } + ); + + // register the stdout and stderr to the console + + child.stdout?.on('data', (data) => { + console.log(`[${this.config.name}] ${data.toString()}`); + }); + child.stderr?.on('data', (data) => { + console.error(`[${this.config.name}] ${data.toString()}`); + }); + + child.on('error', (error) => { + console.error(`[${this.config.name}] ${error}`); + }); + + child.on('exit', (code, signal) => { + console.log( + `[${this.config.name}] Exited with code ${code} and signal ${signal}` + ); + if (code !== 0) { + console.error( + `[${this.config.name}] Exited with code ${code} and signal ${signal}` + ); + } + }); + + this.process = child; + } + + public getChildProcess(): ChildProcess | null { + return this.process; + } + + public stop(): void { + this.abort.abort(); + this.process?.kill(); + this.process = null; + } + + public restart(): void { + this.stop(); + this.start(); + } +} diff --git a/packages/executor/lib/git.ts b/packages/executor/lib/git.ts new file mode 100644 index 00000000..bd65b74b --- /dev/null +++ b/packages/executor/lib/git.ts @@ -0,0 +1,134 @@ +import { Addon } from './addon'; +import { spawn } from 'child_process'; +import { dirname } from 'path'; + +function pipeGitStreams(child: ReturnType): void { + child.stdout?.on('data', (data) => { + console.log(data.toString()); + }); + child.stderr?.on('data', (data) => { + console.error(data.toString()); + }); +} + +function runGitProcess( + cwd: string, + args: string[], + operation: string +): Promise { + const child = spawn('git', args.filter(Boolean), { + cwd, + stdio: 'pipe', + }); + pipeGitStreams(child); + + return new Promise((resolve, reject) => { + child.on('error', reject); + child.on('close', (code) => { + if (code !== 0) { + reject( + new Error( + `Git ${operation} failed with code ${code}: git ${args.join(' ')}` + ) + ); + return; + } + resolve(); + }); + }); +} + +export class Git { + constructor(private readonly addon: Addon) {} + + private execGit(args: string[], operation: string): Promise { + return runGitProcess(this.addon.config.path, args, operation); + } + + /** + * Clone `url` into {@link Addon.config.path} (`git clone ... `). + * Parent of the addon path must exist; the clone target must not already exist as a repo root. + */ + public clone( + url: string, + options: { branch?: string; depth?: number; extraArgs?: string[] } = {} + ): Promise { + const target = this.addon.config.path; + const args = ['clone']; + if (options.depth != null) { + args.push('--depth', String(options.depth)); + } + if (options.branch != null) { + args.push('-b', options.branch); + } + if (options.extraArgs?.length) { + args.push(...options.extraArgs); + } + args.push(url, target); + return runGitProcess(dirname(target), args, 'clone'); + } + + /** `git fetch` with optional extra arguments (e.g. `['origin', 'main']`). */ + public fetch(extraArgs: string[] = []): Promise { + return this.execGit(['fetch', ...extraArgs], 'fetch'); + } + + /** + * Fetch a specific ref from a remote without updating HEAD. + * Example: `fetchRef('origin', 'feature/x')` + */ + public fetchRef(remote: string, ref: string): Promise { + return this.fetch([remote, ref]); + } + + public pull(options: { force?: boolean } = {}): Promise { + const args = ['pull']; + if (options.force) { + args.push('--force'); + } + return this.execGit(args, 'pull'); + } + + /** + * Switch to an existing branch (`git switch `). + */ + public switchBranch(branch: string): Promise { + return this.execGit(['switch', branch], `switch to branch ${branch}`); + } + + /** + * Create and switch to a new branch from the current HEAD (`git switch -c `). + */ + public createBranch(branch: string, startPoint?: string): Promise { + const args = ['switch', '-c', branch]; + if (startPoint) { + args.push(startPoint); + } + return this.execGit(args, `create branch ${branch}`); + } + + /** + * Pin the working tree to an exact commit (detached HEAD). + * Fetches first when `fetchFirst` is true so the hash exists locally. + */ + public async checkoutCommit( + hash: string, + options: { fetchFirst?: boolean } = {} + ): Promise { + if (options.fetchFirst) { + await this.fetch(); + } + return this.execGit( + ['switch', '--detach', hash], + `checkout commit ${hash}` + ); + } + + /** + * Move the current branch to `ref` and match index/worktree (`git reset --hard `). + * Use for pinning a branch tip to a specific commit after fetch. + */ + public resetHard(ref: string): Promise { + return this.execGit(['reset', '--hard', ref], `reset --hard ${ref}`); + } +} diff --git a/packages/executor/lib/index.ts b/packages/executor/lib/index.ts new file mode 100644 index 00000000..5c31211d --- /dev/null +++ b/packages/executor/lib/index.ts @@ -0,0 +1,4 @@ +export { Addon, AddonFileConfigurationSchema } from './addon.ts'; +export type { AddonConfig, AddonFileConfiguration } from './addon.ts'; +export { AddonSetup } from './addon-setup.ts'; +export { Git } from './git.ts'; diff --git a/packages/executor/package.json b/packages/executor/package.json new file mode 100644 index 00000000..d5acc228 --- /dev/null +++ b/packages/executor/package.json @@ -0,0 +1,29 @@ +{ + "name": "@ogi-sdk/executor", + "version": "1.0.0", + "module": "./build/index.mjs", + "main": "./build/index.cjs", + "exports": { + ".": { + "import": { + "default": "./build/index.mjs", + "types": "./build/index.d.mts" + } + } + }, + "type": "module", + "scripts": { + "build": "tsdown --config tsdown.config.js" + }, + "devDependencies": { + "@types/bun": "latest", + "tsdown": "^0.21.10" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "string-argv": "^0.3.2", + "zod": "^4.4.2" + } +} diff --git a/packages/executor/tsconfig.json b/packages/executor/tsconfig.json new file mode 100644 index 00000000..0109293a --- /dev/null +++ b/packages/executor/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + "types": ["bun"], + "baseUrl": ".", + "paths": { + "@/*": ["lib/*"] + }, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": ["lib/**/*"] +} diff --git a/packages/executor/tsdown.config.js b/packages/executor/tsdown.config.js new file mode 100644 index 00000000..1a4c9d78 --- /dev/null +++ b/packages/executor/tsdown.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: ['lib/index.ts'], + splitting: false, + sourcemap: true, + clean: true, + format: ['cjs', 'esm'], + dts: true, + outDir: 'build', + target: false, +}); diff --git a/packages/ogi-addon/package.json b/packages/ogi-addon/package.json index 9ad7a55e..43cad3e5 100644 --- a/packages/ogi-addon/package.json +++ b/packages/ogi-addon/package.json @@ -44,7 +44,7 @@ }, "scripts": { "auto-build": "tsc -w", - "build": "tsdown --config tsdown.config.js", + "build": "tsdown --config tsdown.config.js && bun run --filter '../../packages/addon-server' generate:event-proxy", "typecheck": "tsc --noEmit", "release": "bun run build && npm publish", "format": "prettier --write \"src/**/*.{ts,svelte,json}\"", diff --git a/packages/ogi-addon/src/main.ts b/packages/ogi-addon/src/main.ts index 2b57fe74..03f7c847 100644 --- a/packages/ogi-addon/src/main.ts +++ b/packages/ogi-addon/src/main.ts @@ -816,7 +816,7 @@ export const ZodLibraryInfo = z.object({ .optional(), }); export type LibraryInfo = z.infer; -interface Notification { +export interface Notification { type: 'warning' | 'error' | 'info' | 'success'; message: string; id: string; @@ -827,16 +827,26 @@ class OGIAddonWSListener { public addon: OGIAddon; constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) { - if ( - process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret' - ) { + const secret = process.argv + .find((arg) => arg.startsWith('--addonSecret=')) + ?.split('=')[1]; + if (!secret) { throw new Error( 'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.' ); } + + // get the port from the arguments + let port = process.argv + .find((arg) => arg.startsWith('--addonPort=')) + ?.split('=')[1]; + if (!port) { + port = defaultPort.toString(); + } + this.addon = ogiAddon; this.eventEmitter = eventEmitter; - this.socket = new ws('ws://localhost:' + defaultPort); + this.socket = new ws('ws://localhost:' + port); this.socket.on('open', () => { console.log('Connected to OGI Addon Server'); console.log('OGI Addon Server Version:', VERSION);