From a474f04e8edda718f3b0407cd13eaaeeec2dd895 Mon Sep 17 00:00:00 2001 From: Demir Yerli Date: Sat, 4 Apr 2026 17:34:37 +0300 Subject: [PATCH 01/25] Refactor app structure and update unit tests --- packages/quadrant-node/index.d.ts | 41 + packages/quadrant-node/index.js | 80 + packages/quadrant-node/index.test.js | 95 + packages/quadrant-node/package.json | 7 + src-tauri/Cargo.lock | 153 +- src-tauri/Cargo.toml | 5 +- .../crates/quadrant-core/src/account/id.rs | 66 +- .../crates/quadrant-core/src/account/mod.rs | 7 + .../src/account/quadrant_settings_sync.rs | 6 +- .../src/account/quadrant_share.rs | 6 +- src-tauri/crates/quadrant-host/Cargo.toml | 31 + src-tauri/crates/quadrant-host/src/lib.rs | 2128 +++++++++++++++++ src-tauri/crates/quadrant-napi/Cargo.toml | 18 + src-tauri/crates/quadrant-napi/build.rs | 3 + src-tauri/crates/quadrant-napi/index.js | 26 + src-tauri/crates/quadrant-napi/package.json | 10 + src-tauri/crates/quadrant-napi/src/lib.rs | 213 ++ src-tauri/src/account/id.rs | 1125 +-------- src-tauri/src/account/mod.rs | 21 +- .../src/account/quadrant_settings_sync.rs | 30 +- src-tauri/src/account/quadrant_share.rs | 63 +- src-tauri/src/account/quadrant_sync.rs | 204 +- src-tauri/src/config/mod.rs | 14 +- src-tauri/src/lib.rs | 72 +- src-tauri/src/mc_mod/curseforge.rs | 24 +- src-tauri/src/mc_mod/mod.rs | 148 +- src-tauri/src/mc_mod/modrinth.rs | 21 +- src-tauri/src/modpacks/general.rs | 49 +- src-tauri/src/modpacks/manage_modpack.rs | 107 +- src-tauri/src/other/rss.rs | 8 +- src-tauri/src/other/telemetry.rs | 32 +- 31 files changed, 3163 insertions(+), 1650 deletions(-) create mode 100644 packages/quadrant-node/index.d.ts create mode 100644 packages/quadrant-node/index.js create mode 100644 packages/quadrant-node/index.test.js create mode 100644 packages/quadrant-node/package.json create mode 100644 src-tauri/crates/quadrant-host/Cargo.toml create mode 100644 src-tauri/crates/quadrant-host/src/lib.rs create mode 100644 src-tauri/crates/quadrant-napi/Cargo.toml create mode 100644 src-tauri/crates/quadrant-napi/build.rs create mode 100644 src-tauri/crates/quadrant-napi/index.js create mode 100644 src-tauri/crates/quadrant-napi/package.json create mode 100644 src-tauri/crates/quadrant-napi/src/lib.rs diff --git a/packages/quadrant-node/index.d.ts b/packages/quadrant-node/index.d.ts new file mode 100644 index 00000000..bf31c2b3 --- /dev/null +++ b/packages/quadrant-node/index.d.ts @@ -0,0 +1,41 @@ +export interface QuadrantClientOptions { + dataDir: string; + mcFolder?: string | null; + apiBaseUrl?: string | null; + oauthClientId: string; + oauthClientSecret: string; + quadrantApiKey: string; + configStoreName?: string | null; + updateStoreName?: string | null; + keyringServiceName?: string | null; + appVersion?: string | null; + osName?: string | null; + userAgent?: string | null; +} + +export interface QuadrantEventEnvelope { + event: string; + payload: unknown; +} + +export interface QuadrantClient { + startBackgroundWorkers(): Promise; + stopBackgroundWorkers(): Promise; + shutdown(): Promise; + initConfig(): Promise; + getMinecraftFolder(): Promise; + invoke(command: string, payload?: unknown): Promise; + getModpacks(hideFree?: boolean): Promise; + getAccountInfo(): Promise; + getNews(): Promise; + installMod(args: unknown): Promise; + syncModpack(modpack: unknown, overwrite?: boolean): Promise; + on(eventName: string, listener: (payload: unknown) => void): () => void; + off(eventName: string, listener: (payload: unknown) => void): void; + once(eventName: string, listener: (payload: unknown) => void): void; +} + +export function createQuadrantClient( + options: QuadrantClientOptions, + nativeModule?: any, +): QuadrantClient; diff --git a/packages/quadrant-node/index.js b/packages/quadrant-node/index.js new file mode 100644 index 00000000..67eb12d9 --- /dev/null +++ b/packages/quadrant-node/index.js @@ -0,0 +1,80 @@ +import { EventEmitter } from "node:events"; +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); + +function loadNativeModule() { + if (process.env.QUADRANT_NAPI_MODULE) { + return require(process.env.QUADRANT_NAPI_MODULE); + } + + return require("../../src-tauri/crates/quadrant-napi/index.js"); +} + +function nativeMethod(target, ...names) { + for (const name of names) { + if (typeof target[name] === "function") { + return target[name].bind(target); + } + } + throw new Error(`Native method not found. Tried: ${names.join(", ")}`); +} + +function mapOptions(options) { + return { + data_dir: options.dataDir, + mc_folder: options.mcFolder ?? null, + api_base_url: options.apiBaseUrl ?? null, + oauth_client_id: options.oauthClientId, + oauth_client_secret: options.oauthClientSecret, + quadrant_api_key: options.quadrantApiKey, + config_store_name: options.configStoreName ?? null, + update_store_name: options.updateStoreName ?? null, + keyring_service_name: options.keyringServiceName ?? null, + app_version: options.appVersion ?? null, + os_name: options.osName ?? null, + user_agent: options.userAgent ?? null, + }; +} + +export function createQuadrantClient(options, nativeModule = loadNativeModule()) { + const host = new nativeModule.QuadrantHostAddon(mapOptions(options)); + const emitter = new EventEmitter(); + + nativeMethod(host, "onEvent", "on_event")((rawEvent) => { + const event = typeof rawEvent === "string" ? JSON.parse(rawEvent) : rawEvent; + emitter.emit("event", event); + emitter.emit(event.event, event.payload); + }); + + return { + startBackgroundWorkers: () => + nativeMethod(host, "startBackgroundWorkers", "start_background_workers")(), + stopBackgroundWorkers: () => + nativeMethod(host, "stopBackgroundWorkers", "stop_background_workers")(), + shutdown: () => nativeMethod(host, "shutdown")(), + initConfig: () => nativeMethod(host, "initConfig", "init_config")(), + getMinecraftFolder: () => + nativeMethod(host, "getMinecraftFolder", "get_minecraft_folder")(), + invoke: (command, payload = null) => + nativeMethod(host, "invoke")(command, payload), + getModpacks: (hideFree = false) => + nativeMethod(host, "getModpacks", "get_modpacks")(hideFree), + getAccountInfo: () => + nativeMethod(host, "getAccountInfo", "get_account_info")(), + getNews: () => nativeMethod(host, "getNews", "get_news")(), + installMod: (args) => nativeMethod(host, "installMod", "install_mod")(args), + syncModpack: (modpack, overwrite = true) => + nativeMethod(host, "syncModpack", "sync_modpack")(modpack, overwrite), + on: (eventName, listener) => { + emitter.on(eventName, listener); + return () => emitter.off(eventName, listener); + }, + off: (eventName, listener) => { + emitter.off(eventName, listener); + }, + once: (eventName, listener) => { + emitter.once(eventName, listener); + }, + }; +} diff --git a/packages/quadrant-node/index.test.js b/packages/quadrant-node/index.test.js new file mode 100644 index 00000000..d0983489 --- /dev/null +++ b/packages/quadrant-node/index.test.js @@ -0,0 +1,95 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { createQuadrantClient } from "./index.js"; + +test("createQuadrantClient maps events and invoke-style methods", async () => { + const seen = []; + const fakeNative = { + QuadrantHostAddon: class { + constructor() { + fakeNative.instance = this; + } + + on_event(callback) { + this.callback = callback; + } + + invoke(command, payload) { + return Promise.resolve({ command, payload }); + } + + get_modpacks(hideFree) { + return Promise.resolve([{ name: "demo", hideFree }]); + } + + get_account_info() { + return Promise.resolve({ login: "demo" }); + } + + get_news() { + return Promise.resolve([{ title: "hello" }]); + } + + install_mod() { + return Promise.resolve(); + } + + sync_modpack() { + return Promise.resolve(); + } + + start_background_workers() { + return Promise.resolve(); + } + + stop_background_workers() { + return Promise.resolve(); + } + + shutdown() { + return Promise.resolve(); + } + + init_config() { + return Promise.resolve(); + } + + get_minecraft_folder() { + return Promise.resolve("C:/demo/.minecraft"); + } + }, + }; + + const client = createQuadrantClient( + { + dataDir: "C:/quadrant", + oauthClientId: "client", + oauthClientSecret: "secret", + quadrantApiKey: "api-key", + }, + fakeNative, + ); + + const dispose = client.on("refreshNotifications", (payload) => { + seen.push(payload); + }); + + fakeNative.instance.callback?.( + JSON.stringify({ + event: "refreshNotifications", + payload: [{ notification_id: "n1" }], + }), + ); + + const invokeResult = await client.invoke("get_modpacks", { hideFree: true }); + const modpacks = await client.getModpacks(true); + + assert.deepEqual(invokeResult, { + command: "get_modpacks", + payload: { hideFree: true }, + }); + assert.equal(modpacks[0].name, "demo"); + assert.deepEqual(seen, [[{ notification_id: "n1" }]]); + dispose(); +}); diff --git a/packages/quadrant-node/package.json b/packages/quadrant-node/package.json new file mode 100644 index 00000000..c30a5bea --- /dev/null +++ b/packages/quadrant-node/package.json @@ -0,0 +1,7 @@ +{ + "name": "@quadrant/quadrant-node", + "version": "26.4.0", + "type": "module", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1eb024f1..a8ab2da7 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -827,6 +827,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.18.1" @@ -1050,6 +1059,22 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "darling" version = "0.20.11" @@ -1216,7 +1241,7 @@ version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", @@ -1425,6 +1450,21 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "dunce" version = "1.0.5" @@ -3014,7 +3054,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] @@ -3049,6 +3089,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libredox" version = "0.1.14" @@ -3269,6 +3319,66 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "napi" +version = "3.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb7848c221fb7bb789e02f01875287ebb1e078b92a6566a34de01ef8806e7c2b" +dependencies = [ + "bitflags 2.11.0", + "ctor 0.8.0", + "futures", + "napi-build", + "napi-sys", + "nohash-hasher", + "rustc-hash", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "napi-build" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" + +[[package]] +name = "napi-derive" +version = "3.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60867ff9a6f76e82350e0c3420cb0736f5866091b61d7d8a024baa54b0ec17dd" +dependencies = [ + "convert_case 0.11.0", + "ctor 0.8.0", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "napi-derive-backend" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0864cf6a82e2cfb69067374b64c9253d7e910e5b34db833ed7495dda56ccb18" +dependencies = [ + "convert_case 0.11.0", + "proc-macro2", + "quote", + "semver", + "syn 2.0.117", +] + +[[package]] +name = "napi-sys" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b" +dependencies = [ + "libloading 0.9.0", +] + [[package]] name = "ndk" version = "0.9.0" @@ -3329,6 +3439,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "8.0.0" @@ -4339,6 +4455,36 @@ dependencies = [ "zip 8.4.0", ] +[[package]] +name = "quadrant-host" +version = "26.4.0-stable" +dependencies = [ + "anyhow", + "futures", + "keyring", + "log", + "quadrant-core", + "serde", + "serde_json", + "tempfile", + "tokio", + "tokio-tungstenite", + "url", + "uuid", +] + +[[package]] +name = "quadrant-napi" +version = "26.4.0-stable" +dependencies = [ + "napi", + "napi-build", + "napi-derive", + "quadrant-host", + "serde_json", + "tokio", +] + [[package]] name = "quadrant_next" version = "26.4.0-stable" @@ -4355,6 +4501,7 @@ dependencies = [ "once_cell", "open", "quadrant-core", + "quadrant-host", "reqwest 0.13.2", "reqwest-middleware", "rss", @@ -6198,7 +6345,7 @@ dependencies = [ "anyhow", "brotli", "cargo_metadata", - "ctor", + "ctor 0.2.9", "dunce", "glob", "html5ever 0.29.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 35bc1eba..7e268e36 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Demir Yerli "] edition = "2024" [workspace] -members = ["crates/quadrant-core"] +members = ["crates/quadrant-core", "crates/quadrant-host", "crates/quadrant-napi"] [workspace.package] version = "26.4.0-stable" @@ -23,7 +23,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] [features] default = ["proprietary"] telemetry = [] -curseforge = ["quadrant-core/curseforge"] +curseforge = ["quadrant-core/curseforge", "quadrant-host/curseforge"] # Quadrant ID/Sync/Share quadrant_id = [] updater = [] @@ -38,6 +38,7 @@ tauri-build = { version = "2.5.6", features = [] } tauri = { version = "2.10.3", features = ["tray-icon", "config-json5"] } quadrant-core = { path = "crates/quadrant-core" } +quadrant-host = { path = "crates/quadrant-host" } tauri-plugin-opener = "2.5.3" serde = { version = "1.0.228", features = ["derive"] } diff --git a/src-tauri/crates/quadrant-core/src/account/id.rs b/src-tauri/crates/quadrant-core/src/account/id.rs index e167d5f5..52daeb63 100644 --- a/src-tauri/crates/quadrant-core/src/account/id.rs +++ b/src-tauri/crates/quadrant-core/src/account/id.rs @@ -1,13 +1,13 @@ //! Account identity, login, refresh, and notification APIs. -use std::{collections::HashMap, env}; +use std::collections::HashMap; use reqwest::StatusCode; use serde::{Deserialize, Deserializer, Serialize}; use crate::{ Result, - account::{QNT_BASE_URL, get_account_token, get_refresh_token, set_secret}, + account::{backend_base_url, get_account_token, get_refresh_token, set_secret}, ports::SecretStore, }; @@ -19,10 +19,6 @@ const EMPTY_NOTIFICATION_HISTORY_BODY: &str = "Notification history request retu const EMPTY_ACCOUNT_INFO_BODY: &str = "Account info request returned an empty body"; const EMPTY_OAUTH_BODY: &str = "OAuth2 token endpoint returned an empty body"; -fn backend_base_url() -> String { - env::var("QUADRANT_API_BASE_URL").unwrap_or_else(|_| QNT_BASE_URL.to_string()) -} - /// Account profile returned by the Quadrant backend. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccountInfo { @@ -345,6 +341,30 @@ pub async fn get_notification_history_page( read: Option, limit: Option, include_modpack_sync: bool, +) -> Result { + get_notification_history_page_with_refresh( + secret_store, + user_agent, + cursor, + read, + limit, + include_modpack_sync, + env!("QUADRANT_OAUTH2_CLIENT_ID"), + env!("QUADRANT_OAUTH2_CLIENT_SECRET"), + ) + .await +} + +/// Fetches a single page of notification history and refreshes the token on demand. +pub async fn get_notification_history_page_with_refresh( + secret_store: &impl SecretStore, + user_agent: &str, + cursor: Option<&NotificationCursor>, + read: Option, + limit: Option, + include_modpack_sync: bool, + client_id: &str, + client_secret: &str, ) -> Result { let token = get_account_token(secret_store)?; let query = notification_history_query(cursor, read, limit, include_modpack_sync); @@ -359,13 +379,7 @@ pub async fn get_notification_history_page( if response.status() == StatusCode::UNAUTHORIZED { log::info!("Notification history request unauthorized, attempting token refresh"); - try_refresh_token( - secret_store, - env!("QUADRANT_OAUTH2_CLIENT_ID"), - env!("QUADRANT_OAUTH2_CLIENT_SECRET"), - user_agent, - ) - .await?; + try_refresh_token(secret_store, client_id, client_secret, user_agent).await?; let new_token = get_account_token(secret_store)?; let retry = reqwest::Client::new() .get(format!("{}/account/notifications/get", backend_base_url())) @@ -387,18 +401,42 @@ pub async fn get_notification_history_all_since( cursor: Option<&NotificationCursor>, read: Option, include_modpack_sync: bool, +) -> Result<(Vec, NotificationCursor)> { + get_notification_history_all_since_with_refresh( + secret_store, + user_agent, + cursor, + read, + include_modpack_sync, + env!("QUADRANT_OAUTH2_CLIENT_ID"), + env!("QUADRANT_OAUTH2_CLIENT_SECRET"), + ) + .await +} + +/// Fetches all notification history pages from the provided cursor onward. +pub async fn get_notification_history_all_since_with_refresh( + secret_store: &impl SecretStore, + user_agent: &str, + cursor: Option<&NotificationCursor>, + read: Option, + include_modpack_sync: bool, + client_id: &str, + client_secret: &str, ) -> Result<(Vec, NotificationCursor)> { let mut page_cursor = cursor.cloned().unwrap_or_default(); let mut notifications = Vec::new(); loop { - let page = get_notification_history_page( + let page = get_notification_history_page_with_refresh( secret_store, user_agent, Some(&page_cursor), read, Some(DEFAULT_NOTIFICATION_HISTORY_LIMIT), include_modpack_sync, + client_id, + client_secret, ) .await?; diff --git a/src-tauri/crates/quadrant-core/src/account/mod.rs b/src-tauri/crates/quadrant-core/src/account/mod.rs index 7337c6c8..72c4a60c 100644 --- a/src-tauri/crates/quadrant-core/src/account/mod.rs +++ b/src-tauri/crates/quadrant-core/src/account/mod.rs @@ -1,5 +1,7 @@ //! Account token helpers and Quadrant cloud entrypoints. +use std::env; + use crate::{Result, ports::SecretStore}; pub mod id; @@ -12,6 +14,11 @@ pub const QNT_BASE_URL: &str = "https://api.usequadrant.dev/api/v3"; /// Stable keyring service name used by the current app. pub const KEYRING_SERVICE: &str = "dev.mrquantumoff.mcmodpackmanager"; +/// Resolves the backend base URL, preferring an explicit runtime override. +pub fn backend_base_url() -> String { + env::var("QUADRANT_API_BASE_URL").unwrap_or_else(|_| QNT_BASE_URL.to_string()) +} + /// Persists a named secret in the host secret store. pub fn set_secret(secret_store: &impl SecretStore, key: &str, value: &str) -> Result<()> { log::info!("Setting secret: {key}"); diff --git a/src-tauri/crates/quadrant-core/src/account/quadrant_settings_sync.rs b/src-tauri/crates/quadrant-core/src/account/quadrant_settings_sync.rs index 16f6a697..7fd30d26 100644 --- a/src-tauri/crates/quadrant-core/src/account/quadrant_settings_sync.rs +++ b/src-tauri/crates/quadrant-core/src/account/quadrant_settings_sync.rs @@ -8,7 +8,7 @@ use serde_json::{Value, json}; use crate::{ Result, - account::{QNT_BASE_URL, get_account_token}, + account::{backend_base_url, get_account_token}, ports::{SecretStore, SettingsStore}, }; @@ -40,7 +40,7 @@ pub async fn get_quadrant_settings( log::debug!("Pulling settings from cloud"); let token = get_account_token(secret_store)?; let json = reqwest::Client::new() - .get(format!("{}/quadrant/settings_sync/get", QNT_BASE_URL)) + .get(format!("{}/quadrant/settings_sync/get", backend_base_url())) .header("User-Agent", user_agent) .bearer_auth(token) .send() @@ -104,7 +104,7 @@ pub async fn submit_quadrant_settings( } let response = reqwest::Client::new() - .post(format!("{}/quadrant/settings_sync/submit", QNT_BASE_URL)) + .post(format!("{}/quadrant/settings_sync/submit", backend_base_url())) .header("User-Agent", user_agent) .bearer_auth(token) .json(&json!({ diff --git a/src-tauri/crates/quadrant-core/src/account/quadrant_share.rs b/src-tauri/crates/quadrant-core/src/account/quadrant_share.rs index 6395a8f6..6e09bb6b 100644 --- a/src-tauri/crates/quadrant-core/src/account/quadrant_share.rs +++ b/src-tauri/crates/quadrant-core/src/account/quadrant_share.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{ Result, - account::QNT_BASE_URL, + account::backend_base_url, models::InstalledModpack, ports::{SecretStore, SettingsStore}, }; @@ -52,7 +52,7 @@ pub async fn share_modpack_raw( } let token = secret_store.get_secret("accountToken")?; - let mut url = format!("{}/quadrant/share/submit", QNT_BASE_URL); + let mut url = format!("{}/quadrant/share/submit", backend_base_url()); if token.is_some() { url = format!("{}/id", url); } @@ -86,7 +86,7 @@ pub async fn get_quadrant_share_modpack( ) -> Result { log::info!("Fetching shared modpack with code: {code}"); let response = reqwest::Client::new() - .get(format!("{}/quadrant/share/get", QNT_BASE_URL)) + .get(format!("{}/quadrant/share/get", backend_base_url())) .query(&[("code", code)]) .header("User-Agent", user_agent) .header("Authorization", api_key) diff --git a/src-tauri/crates/quadrant-host/Cargo.toml b/src-tauri/crates/quadrant-host/Cargo.toml new file mode 100644 index 00000000..f968c0eb --- /dev/null +++ b/src-tauri/crates/quadrant-host/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "quadrant-host" +version.workspace = true +edition = "2024" + +[features] +default = [] +curseforge = ["quadrant-core/curseforge"] + +[dependencies] +anyhow = "1.0.102" +futures = "0.3.32" +keyring = { version = "3.6.3", features = [ + "apple-native", + "windows-native", + "sync-secret-service", +] } +log = "0.4.29" +quadrant-core = { path = "../quadrant-core" } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +tokio = { version = "1.50.0", features = ["full"] } +tokio-tungstenite = { version = "0.29.0", features = [ + "rustls-tls-webpki-roots", + "connect", +] } +url = "2.5.8" +uuid = { version = "1.23.0", features = ["v7"] } + +[dev-dependencies] +tempfile = "3.27.0" diff --git a/src-tauri/crates/quadrant-host/src/lib.rs b/src-tauri/crates/quadrant-host/src/lib.rs new file mode 100644 index 00000000..f640c5a5 --- /dev/null +++ b/src-tauri/crates/quadrant-host/src/lib.rs @@ -0,0 +1,2128 @@ +use std::{ + collections::HashMap, + fs, + path::{PathBuf}, + sync::{Arc, Mutex}, + time::Duration, +}; + +use anyhow::anyhow; +use futures::StreamExt; +use keyring::{Entry, Error as KeyringError}; +use quadrant_core::{ + Error, Result, + account::{ + KEYRING_SERVICE, clear_account_token, + id::{ + AccountInfo, Notification, NotificationCursor, NotificationWsFrame, + get_account_info_with_refresh, get_notification_history_all_since_with_refresh, + oauth2_login, read_notification, + }, + quadrant_settings_sync, + quadrant_share::{QuadrantShareSubmissionResponse, get_quadrant_share_modpack, share_modpack_raw}, + quadrant_sync::{SyncedModpack, answer_invite, delete_synced_modpack, get_synced_modpacks, invite_member, kick_member}, + set_secret, + }, + config::{ensure_default_app_config, get_mc_folder}, + events::BackendEvent, + mc_mod::{ + GetModArgs, GlobalSearchModsArgs, IdentifiedMod, MinecraftVersion, Mod, ModType, + UniversalModFile, check_mod_updates, get_user_agent, get_versions, identify_modpack, + install_mod, install_remote_file, search_mods, + }, + models::{Article, InstalledMod, InstalledModpack, LocalModpack, ModLoader, ModSource}, + modpacks::{ + apply_modpack, create_modpack, delete_mod, delete_modpack, export_modpack_to, + get_modpacks, install_modpack, register_mod, set_modpack_sync_date, update_modpack, + }, + ports::{EventSink, SecretStore, SettingsStore}, + telemetry::{AppInfo, get_telemetry_info, remove_telemetry, send_telemetry}, +}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use serde_json::{Value, json}; +use tokio::{ + sync::{Mutex as AsyncMutex, broadcast}, + task::JoinHandle, +}; +use tokio_tungstenite::{ + connect_async, + tungstenite::{Message, client::IntoClientRequest}, +}; +use url::Url; +use uuid::Uuid; + +pub use quadrant_core::mc_mod::{get_mod_url, get_user_url}; +pub use quadrant_core::mc_mod::modrinth::{get_mod_deps_modrinth, get_mod_modrinth, get_mod_owners_modrinth}; +#[cfg(feature = "curseforge")] +pub use quadrant_core::mc_mod::curseforge::{get_mod_curseforge, get_mod_deps_curseforge, get_mod_owners_curseforge}; + +const NOTIFICATION_CURSOR_CREATED_AT_KEY: &str = "notificationCursorCreatedAt"; +const NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY: &str = "notificationCursorNotificationId"; +const SETTINGS_SYNC_INTERVAL_SECS: u64 = 120; +const WS_REPLAY_LIMIT: usize = 500; +const REFRESH_SYNCED_MODPACKS_EVENT: &str = "refreshSyncedModpacks"; + +#[derive(Debug, Clone)] +pub struct QuadrantHostOptions { + pub data_dir: PathBuf, + pub mc_folder: Option, + pub api_base_url: Option, + pub oauth_client_id: String, + pub oauth_client_secret: String, + pub quadrant_api_key: String, + pub config_store_name: String, + pub update_store_name: String, + pub keyring_service_name: String, + pub app_version: String, + pub os_name: String, + pub user_agent: String, +} + +impl QuadrantHostOptions { + pub fn new( + data_dir: PathBuf, + oauth_client_id: impl Into, + oauth_client_secret: impl Into, + quadrant_api_key: impl Into, + ) -> Self { + Self { + data_dir, + mc_folder: None, + api_base_url: None, + oauth_client_id: oauth_client_id.into(), + oauth_client_secret: oauth_client_secret.into(), + quadrant_api_key: quadrant_api_key.into(), + config_store_name: "config.json".to_string(), + update_store_name: "updateConfig.json".to_string(), + keyring_service_name: KEYRING_SERVICE.to_string(), + app_version: env!("CARGO_PKG_VERSION").to_string(), + os_name: std::env::consts::OS.to_uppercase(), + user_agent: get_user_agent(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct HostEventEnvelope { + pub event: String, + pub payload: Value, +} + +#[derive(Clone)] +pub struct QuadrantHost { + inner: Arc, +} + +struct QuadrantHostInner { + options: QuadrantHostOptions, + config_store: JsonFileStore, + _update_store: JsonFileStore, + secret_store: KeyringSecretStore, + event_sink: HostEventBridge, + runtime_state: AsyncMutex, + worker_handles: AsyncMutex, +} + +#[derive(Default)] +struct WorkerHandles { + notification: Option>, + settings_sync: Option>, +} + +#[derive(Default)] +struct HostRuntimeState { + updated_modpacks: Vec, + notification_connection_id: String, + notification_state: NotificationRuntimeState, +} + +#[derive(Clone)] +struct JsonFileStore { + path: PathBuf, + values: Arc>>, +} + +impl JsonFileStore { + fn new(path: PathBuf) -> Result { + let values = if path.exists() { + let raw = fs::read_to_string(&path)?; + let parsed = serde_json::from_str::(&raw).unwrap_or_else(|_| json!({})); + parsed + .as_object() + .cloned() + .unwrap_or_default() + .into_iter() + .collect::>() + } else { + HashMap::new() + }; + Ok(Self { + path, + values: Arc::new(Mutex::new(values)), + }) + } + + fn delete_key(&self, key: &str) -> Result<()> { + let mut values = self.values.lock().map_err(|_| anyhow!("settings store is busy"))?; + values.remove(key); + self.persist(&values) + } + + fn persist(&self, values: &HashMap) -> Result<()> { + if let Some(parent) = self.path.parent() { + fs::create_dir_all(parent)?; + } + fs::write( + &self.path, + serde_json::to_vec_pretty(&Value::Object(values.clone().into_iter().collect()))?, + )?; + Ok(()) + } +} + +impl SettingsStore for JsonFileStore { + fn get_value(&self, key: &str) -> Result> { + let values = self.values.lock().map_err(|_| anyhow!("settings store is busy"))?; + Ok(values.get(key).cloned()) + } + + fn set_value(&self, key: &str, value: Value) -> Result<()> { + let mut values = self.values.lock().map_err(|_| anyhow!("settings store is busy"))?; + values.insert(key.to_string(), value); + self.persist(&values) + } + + fn entries(&self) -> Result> { + let values = self.values.lock().map_err(|_| anyhow!("settings store is busy"))?; + Ok(values + .iter() + .map(|(key, value)| (key.clone(), value.clone())) + .collect()) + } +} + +#[derive(Clone)] +struct KeyringSecretStore { + service_name: String, +} + +impl SecretStore for KeyringSecretStore { + fn get_secret(&self, key: &str) -> Result> { + let entry = Entry::new(&self.service_name, key)?; + map_keyring_secret_result(entry.get_password()) + } + + fn set_secret(&self, key: &str, value: &str) -> Result<()> { + let entry = Entry::new(&self.service_name, key)?; + entry.set_password(value)?; + Ok(()) + } + + fn delete_secret(&self, key: &str) -> Result<()> { + let entry = Entry::new(&self.service_name, key)?; + match entry.delete_credential() { + Ok(()) | Err(KeyringError::NoEntry) => Ok(()), + Err(error) => Err(error.into()), + } + } +} + +fn map_keyring_secret_result( + result: std::result::Result, +) -> Result> { + match result { + Ok(value) => Ok(Some(value)), + Err(KeyringError::NoEntry) => Ok(None), + Err(error) => Err(error.into()), + } +} + +#[derive(Clone)] +struct HostEventBridge { + sender: broadcast::Sender, +} + +impl HostEventBridge { + fn new() -> Self { + let (sender, _) = broadcast::channel(256); + Self { sender } + } + + fn subscribe(&self) -> broadcast::Receiver { + self.sender.subscribe() + } + + fn emit(&self, event: impl Into, payload: T) -> Result<()> { + let _ = self.sender.send(HostEventEnvelope { + event: event.into(), + payload: serde_json::to_value(payload)?, + }); + Ok(()) + } + + fn emit_value(&self, event: impl Into, payload: Value) { + let _ = self.sender.send(HostEventEnvelope { + event: event.into(), + payload, + }); + } +} + +impl EventSink for HostEventBridge { + fn publish(&self, event: BackendEvent) -> Result<()> { + match event { + BackendEvent::ModDownloadProgress(payload) => self.emit("modDownloadProgress", payload), + BackendEvent::ModInstallProgress(payload) => self.emit("modInstallProgress", payload), + BackendEvent::ModpackDownloadProgress(payload) => { + self.emit("modpackDownloadProgress", payload) + } + BackendEvent::QuadrantExportProgress(payload) => { + self.emit("quadrantExportProgress", payload) + } + BackendEvent::QuadrantShareSubmission(payload) => { + self.emit_value("quadrantShareSubmission", payload); + Ok(()) + } + BackendEvent::RefreshNotifications(payload) => { + self.emit_value("refreshNotifications", payload); + Ok(()) + } + BackendEvent::RecheckAccountToken => self.emit("recheckAccountToken", Value::Null), + } + } +} + +impl QuadrantHost { + pub fn new(options: QuadrantHostOptions) -> Result { + fs::create_dir_all(&options.data_dir)?; + if let Some(api_base_url) = &options.api_base_url { + unsafe { + std::env::set_var("QUADRANT_API_BASE_URL", api_base_url); + } + } + + let config_store = JsonFileStore::new(options.data_dir.join(&options.config_store_name))?; + let update_store = JsonFileStore::new(options.data_dir.join(&options.update_store_name))?; + ensure_default_app_config(&config_store)?; + + if let Some(mc_folder) = &options.mc_folder { + config_store.set_string("mcFolder", mc_folder.to_string_lossy().to_string())?; + } + + Ok(Self { + inner: Arc::new(QuadrantHostInner { + secret_store: KeyringSecretStore { + service_name: options.keyring_service_name.clone(), + }, + event_sink: HostEventBridge::new(), + runtime_state: AsyncMutex::new(HostRuntimeState { + notification_connection_id: Uuid::now_v7().to_string(), + ..HostRuntimeState::default() + }), + worker_handles: AsyncMutex::new(WorkerHandles::default()), + config_store, + _update_store: update_store, + options, + }), + }) + } + + pub fn options(&self) -> &QuadrantHostOptions { + &self.inner.options + } + + pub fn subscribe_events(&self) -> broadcast::Receiver { + self.inner.event_sink.subscribe() + } + + pub async fn start_background_workers(&self) -> Result<()> { + let mut worker_handles = self.inner.worker_handles.lock().await; + + if worker_handles.notification.is_none() { + let host = self.clone(); + worker_handles.notification = Some(tokio::spawn(async move { + host.notification_worker_loop().await; + })); + } + + if worker_handles.settings_sync.is_none() { + let host = self.clone(); + worker_handles.settings_sync = Some(tokio::spawn(async move { + host.settings_sync_loop().await; + })); + } + + Ok(()) + } + + pub async fn stop_background_workers(&self) -> Result<()> { + let mut worker_handles = self.inner.worker_handles.lock().await; + + if let Some(handle) = worker_handles.notification.take() { + handle.abort(); + let _ = handle.await; + } + + if let Some(handle) = worker_handles.settings_sync.take() { + handle.abort(); + let _ = handle.await; + } + + Ok(()) + } + + pub async fn shutdown(&self) -> Result<()> { + self.stop_background_workers().await + } + + pub fn init_config(&self) -> Result<()> { + ensure_default_app_config(&self.inner.config_store)?; + self.ensure_override_mc_folder() + } + + pub fn get_minecraft_folder(&self) -> Result { + self.inner + .config_store + .get_string("mcFolder")? + .map(PathBuf::from) + .or_else(|| get_mc_folder().ok().flatten()) + .ok_or_else(|| anyhow!("mcFolder is not configured")) + } + + pub fn get_modpacks_folder(&self) -> Result { + Ok(self.get_minecraft_folder()?.join("modpacks")) + } + + pub async fn get_modpacks(&self, hide_free: bool) -> Result> { + get_modpacks(&self.get_minecraft_folder()?, hide_free) + } + + pub fn frontend_apply_modpack(&self, name: String) -> Result<()> { + apply_modpack(&self.get_minecraft_folder()?, &name) + } + + pub async fn delete_mod(&self, modpack_name: String, mod_id: String) -> Result<()> { + let modpack = delete_mod( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &modpack_name, + &mod_id, + )?; + self.maybe_auto_sync_updated_modpack(Some(modpack)).await + } + + pub async fn update_modpack( + &self, + modpack_source: String, + name: Option, + version: Option, + mod_loader: Option, + ) -> Result<()> { + let modpack = update_modpack( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &modpack_source, + name, + version, + mod_loader, + )?; + self.maybe_auto_sync_updated_modpack(Some(modpack)).await + } + + pub async fn create_modpack( + &self, + name: String, + version: String, + mod_loader: ModLoader, + ) -> Result<()> { + create_modpack( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &name, + &version, + mod_loader, + ) + } + + pub async fn delete_modpack(&self, name: String) -> Result<()> { + delete_modpack(&self.get_minecraft_folder()?, &self.get_modpacks(false).await?, &name) + } + + pub async fn register_mod(&self, mod_: InstalledMod, modpack: String) -> Result<()> { + register_mod( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + mod_, + &modpack, + ) + } + + pub async fn install_modpack(&self, mod_config: InstalledModpack) -> Result<()> { + install_modpack( + &self.get_minecraft_folder()?, + mod_config, + &self.inner.config_store, + &self.inner.event_sink, + ) + .await + } + + pub async fn export_modpack_to(&self, modpack: String, destination: PathBuf) -> Result<()> { + export_modpack_to( + &self.get_minecraft_folder()?, + &modpack, + &destination, + &self.inner.event_sink, + ) + } + + pub fn set_modpack_sync_date( + &self, + time: u64, + modpack: String, + modpack_id: Option, + ) -> Result<()> { + set_modpack_sync_date( + &self.get_minecraft_folder()?, + time, + &modpack, + modpack_id.as_deref(), + ) + } + + pub async fn get_versions(&self) -> Result> { + get_versions().await + } + + pub async fn search_mods(&self, args: GlobalSearchModsArgs) -> Result> { + search_mods(args, &self.inner.config_store).await + } + + pub async fn check_mod_updates( + &self, + mod_to_update: Mod, + minecraft_version: String, + mod_loader: ModLoader, + modpack_name: String, + ) -> Result> { + let modpack = self + .get_modpacks(false) + .await? + .into_iter() + .find(|modpack| modpack.name == modpack_name) + .ok_or_else(|| anyhow!("Modpack not found"))?; + let show_unupgradeable_mods = self + .inner + .config_store + .get_bool("showUnupgradeableMods")? + .unwrap_or(false); + + check_mod_updates( + mod_to_update, + minecraft_version, + mod_loader, + modpack, + show_unupgradeable_mods, + ) + .await + } + + pub async fn install_mod( + &self, + id: String, + minecraft_version: String, + mod_loader: ModLoader, + source: ModSource, + modpack: Option, + mod_type: ModType, + file_id: Option, + ) -> Result<()> { + let updated_modpack = install_mod( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &self.inner.config_store, + &self.inner.event_sink, + id, + minecraft_version, + mod_loader, + source, + modpack, + mod_type, + file_id, + ) + .await?; + self.maybe_auto_sync_updated_modpack(updated_modpack).await + } + + pub async fn install_remote_file( + &self, + file: UniversalModFile, + mod_type: ModType, + modpack: Option, + source: ModSource, + id: String, + ) -> Result<()> { + let updated_modpack = install_remote_file( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &self.inner.event_sink, + file, + mod_type, + modpack, + source, + id, + ) + .await?; + self.maybe_auto_sync_updated_modpack(updated_modpack).await + } + + pub async fn identify_modpack(&self, modpack: String) -> Result> { + let curseforge_enabled = self.inner.config_store.get_bool("curseforge")?.unwrap_or(false); + let modrinth_enabled = self.inner.config_store.get_bool("modrinth")?.unwrap_or(false); + identify_modpack( + &self.get_minecraft_folder()?, + modpack, + curseforge_enabled, + modrinth_enabled, + ) + .await + } + + pub async fn get_mod_modrinth(&self, args: GetModArgs) -> Result { + get_mod_modrinth(args).await + } + + pub async fn get_mod_owners_modrinth(&self, id: String) -> Result> { + get_mod_owners_modrinth(id).await + } + + pub async fn get_mod_deps_modrinth(&self, id: String) -> Result> { + get_mod_deps_modrinth(id).await + } + + #[cfg(feature = "curseforge")] + pub async fn get_mod_curseforge(&self, args: GetModArgs) -> Result { + get_mod_curseforge(args).await + } + + #[cfg(feature = "curseforge")] + pub async fn get_mod_owners_curseforge(&self, id: String) -> Result> { + get_mod_owners_curseforge(id).await + } + + #[cfg(feature = "curseforge")] + pub async fn get_mod_deps_curseforge(&self, id: String) -> Result> { + get_mod_deps_curseforge(id).await + } + + pub fn set_secret(&self, key: String, value: String) -> Result<()> { + set_secret(&self.inner.secret_store, &key, &value) + } + + pub fn clear_account_token(&self) -> Result<()> { + clear_account_token(&self.inner.secret_store) + } + + pub fn logout(&self) -> Result<()> { + clear_account_token(&self.inner.secret_store) + } + + pub fn oauth2_client_id(&self) -> String { + self.inner.options.oauth_client_id.clone() + } + + pub async fn get_account_info(&self) -> Result { + get_account_info_with_refresh( + &self.inner.secret_store, + &self.inner.options.user_agent, + &self.inner.options.oauth_client_id, + &self.inner.options.oauth_client_secret, + ) + .await + } + + pub async fn oauth2_login(&self, code: String, redirect_uri: String) -> Result<()> { + oauth2_login( + &self.inner.secret_store, + &self.inner.options.user_agent, + &self.inner.options.oauth_client_id, + &self.inner.options.oauth_client_secret, + code, + redirect_uri, + ) + .await?; + self.inner + .event_sink + .emit("recheckAccountToken", Value::Null) + } + + pub async fn read_notification(&self, notification_id: String) -> Result<()> { + read_notification( + &self.inner.secret_store, + &self.inner.options.user_agent, + notification_id.clone(), + ) + .await?; + + if let Some(notifications) = self.mark_notification_read_and_snapshot(¬ification_id).await + { + self.inner + .event_sink + .emit("refreshNotifications", notifications)?; + } + + Ok(()) + } + + pub async fn get_synced_modpacks( + &self, + show_owners: bool, + modpack_id: Option, + ) -> Result> { + get_synced_modpacks( + &self.inner.secret_store, + &self.inner.options.user_agent, + show_owners, + modpack_id, + ) + .await + } + + pub async fn kick_member(&self, modpack_id: String, username: String) -> Result<()> { + kick_member( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack_id, + username, + ) + .await + } + + pub async fn invite_member( + &self, + modpack_id: String, + username: String, + admin: bool, + ) -> Result<()> { + invite_member( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack_id, + username, + admin, + ) + .await + } + + pub async fn delete_synced_modpack(&self, modpack_id: String) -> Result<()> { + delete_synced_modpack( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack_id, + ) + .await + } + + pub async fn sync_modpack(&self, modpack: LocalModpack, overwrite: bool) -> Result<()> { + let connection_id = { + let runtime = self.inner.runtime_state.lock().await; + runtime.notification_connection_id.clone() + }; + + let timestamp = quadrant_core::account::quadrant_sync::sync_modpack( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack.clone(), + overwrite, + Some(connection_id.as_str()), + ) + .await?; + + let persisted_modpack_id = self + .resolve_submitted_modpack_id(&modpack, timestamp) + .await? + .or_else(|| modpack.modpack_id.clone()); + self.persist_sync_metadata(&modpack.name, timestamp as u64, persisted_modpack_id.as_deref()) + } + + pub async fn answer_invite( + &self, + modpack_id: String, + notification_id: String, + answer: bool, + ) -> Result<()> { + answer_invite( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack_id, + answer, + ) + .await?; + self.read_notification(notification_id).await + } + + pub async fn share_modpack( + &self, + modpack_name: String, + ) -> Result { + let modpack = self + .get_modpacks(false) + .await? + .into_iter() + .find(|modpack| modpack.name == modpack_name) + .ok_or_else(|| anyhow!("Modpack not found"))?; + self.share_modpack_raw(modpack.into()).await + } + + pub async fn share_modpack_raw( + &self, + mod_config: InstalledModpack, + ) -> Result { + let response = share_modpack_raw( + &self.inner.config_store, + &self.inner.secret_store, + &self.inner.options.user_agent, + mod_config, + &self.inner.options.quadrant_api_key, + ) + .await?; + self.inner + .event_sink + .emit("quadrantShareSubmission", &response)?; + self.send_telemetry().await?; + Ok(response) + } + + pub async fn get_quadrant_share_modpack(&self, code: String) -> Result { + get_quadrant_share_modpack( + &self.inner.options.user_agent, + &self.inner.options.quadrant_api_key, + code, + ) + .await + } + + pub async fn get_quadrant_settings(&self) -> Result<()> { + quadrant_settings_sync::get_quadrant_settings( + &self.inner.config_store, + &self.inner.secret_store, + &self.inner.options.user_agent, + ) + .await + } + + pub async fn submit_quadrant_settings(&self) -> Result<()> { + quadrant_settings_sync::submit_quadrant_settings( + &self.inner.config_store, + &self.inner.secret_store, + &self.inner.options.user_agent, + ) + .await + } + + pub async fn get_news(&self) -> Result> { + quadrant_core::rss::get_news().await + } + + pub async fn get_telemetry_info(&self) -> Result { + get_telemetry_info( + &self.inner.config_store, + self.inner.options.app_version.clone(), + self.inner.options.os_name.clone(), + ) + .await + } + + pub async fn send_telemetry(&self) -> Result<()> { + send_telemetry( + &self.inner.config_store, + &self.inner.options.user_agent, + self.inner.options.app_version.clone(), + self.inner.options.os_name.clone(), + &self.inner.options.quadrant_api_key, + ) + .await + } + + pub async fn remove_telemetry(&self) -> Result<()> { + remove_telemetry( + &self.inner.config_store, + &self.inner.options.user_agent, + &self.inner.options.quadrant_api_key, + ) + .await + } + + pub fn supported_commands() -> &'static [&'static str] { + &[ + "get_modpacks", + "frontend_apply_modpack", + "delete_mod", + "update_modpack", + "create_modpack", + "delete_modpack", + "register_mod", + "install_modpack", + "export_modpack_to", + "set_modpack_sync_date", + "get_news", + "get_minecraft_folder", + "get_modpacks_folder", + "init_config", + "search_mods", + "get_versions", + "get_user_url", + "install_mod", + "install_remote_file", + "identify_modpack", + "check_mod_updates", + "get_mod_modrinth", + "get_mod_owners_modrinth", + "get_mod_deps_modrinth", + "set_secret", + "clear_account_token", + "get_account_info", + "oauth2_login", + "oauth2_client_id", + "read_notification", + "get_synced_modpacks", + "kick_member", + "invite_member", + "delete_synced_modpack", + "sync_modpack", + "answer_invite", + "share_modpack", + "share_modpack_raw", + "get_quadrant_share_modpack", + "get_quadrant_settings", + "submit_quadrant_settings", + "send_telemetry", + "remove_telemetry", + #[cfg(feature = "curseforge")] + "get_mod_curseforge", + #[cfg(feature = "curseforge")] + "get_mod_owners_curseforge", + #[cfg(feature = "curseforge")] + "get_mod_deps_curseforge", + ] + } + + #[allow(clippy::too_many_lines)] + pub async fn invoke(&self, command: &str, payload: Value) -> Result { + match command { + "get_modpacks" => { + let args: HideFreeArgs = from_value(payload)?; + to_value(self.get_modpacks(args.hide_free).await?) + } + "frontend_apply_modpack" => { + let args: NameArgs = from_value(payload)?; + self.frontend_apply_modpack(args.name)?; + Ok(Value::Null) + } + "delete_mod" => { + let args: DeleteModArgs = from_value(payload)?; + self.delete_mod(args.modpack_name, args.mod_id).await?; + Ok(Value::Null) + } + "update_modpack" => { + let args: UpdateModpackArgs = from_value(payload)?; + self.update_modpack(args.modpack_source, args.name, args.version, args.mod_loader) + .await?; + Ok(Value::Null) + } + "create_modpack" => { + let args: CreateModpackArgs = from_value(payload)?; + self.create_modpack(args.name, args.version, args.mod_loader).await?; + Ok(Value::Null) + } + "delete_modpack" => { + let args: NameArgs = from_value(payload)?; + self.delete_modpack(args.name).await?; + Ok(Value::Null) + } + "register_mod" => { + let args: RegisterModArgs = from_value(payload)?; + self.register_mod(args.mod_, args.modpack).await?; + Ok(Value::Null) + } + "install_modpack" => { + let args: InstallModpackArgs = from_value(payload)?; + self.install_modpack(args.mod_config).await?; + Ok(Value::Null) + } + "export_modpack_to" => { + let args: ExportModpackArgs = from_value(payload)?; + self.export_modpack_to(args.modpack, PathBuf::from(args.destination)) + .await?; + Ok(Value::Null) + } + "set_modpack_sync_date" => { + let args: SetModpackSyncDateArgs = from_value(payload)?; + self.set_modpack_sync_date(args.time, args.modpack, args.modpack_id)?; + Ok(Value::Null) + } + "get_news" => to_value(self.get_news().await?), + "get_minecraft_folder" => to_value(self.get_minecraft_folder()?), + "get_modpacks_folder" => to_value(self.get_modpacks_folder()?), + "init_config" => { + self.init_config()?; + Ok(Value::Null) + } + "search_mods" => { + let args: SearchModsCompatArgs = from_value(payload)?; + to_value(self.search_mods(args.args).await?) + } + "get_versions" => to_value(self.get_versions().await?), + "get_user_url" => { + let args: GetUserUrlArgs = from_value(payload)?; + to_value(get_user_url(args.username, args.source)) + } + "install_mod" => { + let args: InstallModArgs = from_value(payload)?; + self.install_mod( + args.id, + args.minecraft_version, + args.mod_loader, + args.source, + args.modpack, + args.mod_type, + args.file_id, + ) + .await?; + Ok(Value::Null) + } + "install_remote_file" => { + let args: InstallRemoteFileArgs = from_value(payload)?; + self.install_remote_file(args.file, args.mod_type, args.modpack, args.source, args.id) + .await?; + Ok(Value::Null) + } + "identify_modpack" => { + let args: ModpackOnlyArgs = from_value(payload)?; + to_value(self.identify_modpack(args.modpack).await?) + } + "check_mod_updates" => { + let args: CheckModUpdatesArgs = from_value(payload)?; + to_value( + self.check_mod_updates( + args.mod_to_update, + args.minecraft_version, + args.mod_loader, + args.modpack_name, + ) + .await?, + ) + } + "get_mod_modrinth" => { + let args: GetModArgsWrapper = from_value(payload)?; + to_value(self.get_mod_modrinth(args.args).await?) + } + "get_mod_owners_modrinth" => { + let args: IdArgs = from_value(payload)?; + to_value(self.get_mod_owners_modrinth(args.id).await?) + } + "get_mod_deps_modrinth" => { + let args: IdArgs = from_value(payload)?; + to_value(self.get_mod_deps_modrinth(args.id).await?) + } + #[cfg(feature = "curseforge")] + "get_mod_curseforge" => { + let args: GetModArgsWrapper = from_value(payload)?; + to_value(self.get_mod_curseforge(args.args).await?) + } + #[cfg(feature = "curseforge")] + "get_mod_owners_curseforge" => { + let args: IdArgs = from_value(payload)?; + to_value(self.get_mod_owners_curseforge(args.id).await?) + } + #[cfg(feature = "curseforge")] + "get_mod_deps_curseforge" => { + let args: IdArgs = from_value(payload)?; + to_value(self.get_mod_deps_curseforge(args.id).await?) + } + "set_secret" => { + let args: SetSecretArgs = from_value(payload)?; + self.set_secret(args.key, args.value)?; + Ok(Value::Null) + } + "clear_account_token" => { + self.clear_account_token()?; + Ok(Value::Null) + } + "get_account_info" => to_value(self.get_account_info().await?), + "oauth2_login" => { + let args: OAuthLoginArgs = from_value(payload)?; + self.oauth2_login(args.code, args.redirect_uri).await?; + Ok(Value::Null) + } + "oauth2_client_id" => to_value(self.oauth2_client_id()), + "read_notification" => { + let args: NotificationIdArgs = from_value(payload)?; + self.read_notification(args.notification_id).await?; + Ok(Value::Null) + } + "get_synced_modpacks" => { + let args: GetSyncedModpacksArgs = from_value(payload)?; + to_value(self.get_synced_modpacks(args.show_owners, args.modpack_id).await?) + } + "kick_member" => { + let args: KickMemberArgs = from_value(payload)?; + self.kick_member(args.modpack_id, args.username).await?; + Ok(Value::Null) + } + "invite_member" => { + let args: InviteMemberArgs = from_value(payload)?; + self.invite_member(args.modpack_id, args.username, args.admin).await?; + Ok(Value::Null) + } + "delete_synced_modpack" => { + let args: ModpackIdArgs = from_value(payload)?; + self.delete_synced_modpack(args.modpack_id).await?; + Ok(Value::Null) + } + "sync_modpack" => { + let args: SyncModpackArgs = from_value(payload)?; + self.sync_modpack(args.modpack, args.overwrite).await?; + Ok(Value::Null) + } + "answer_invite" => { + let args: AnswerInviteArgs = from_value(payload)?; + self.answer_invite(args.modpack_id, args.notification_id, args.answer) + .await?; + Ok(Value::Null) + } + "share_modpack" => { + let args: ModpackNameArgs = from_value(payload)?; + to_value(self.share_modpack(args.modpack_name).await?) + } + "share_modpack_raw" => { + let args: InstallModpackArgs = from_value(payload)?; + to_value(self.share_modpack_raw(args.mod_config).await?) + } + "get_quadrant_share_modpack" => { + let args: CodeArgs = from_value(payload)?; + to_value(self.get_quadrant_share_modpack(args.code).await?) + } + "get_quadrant_settings" => { + self.get_quadrant_settings().await?; + Ok(Value::Null) + } + "submit_quadrant_settings" => { + self.submit_quadrant_settings().await?; + Ok(Value::Null) + } + "send_telemetry" => { + self.send_telemetry().await?; + Ok(Value::Null) + } + "remove_telemetry" => { + self.remove_telemetry().await?; + Ok(Value::Null) + } + _ => Err(anyhow!("Unsupported command: {command}")), + } + } + + fn ensure_override_mc_folder(&self) -> Result<()> { + if let Some(mc_folder) = &self.inner.options.mc_folder { + self.inner + .config_store + .set_string("mcFolder", mc_folder.to_string_lossy().to_string())?; + } + Ok(()) + } + + async fn maybe_auto_sync_updated_modpack( + &self, + updated_modpack: Option, + ) -> Result<()> { + let Some(modpack) = updated_modpack else { + return Ok(()); + }; + + let auto_sync = self + .inner + .config_store + .get_bool("autoQuadrantSync")? + .unwrap_or(false); + + if modpack.last_synced != 0 && auto_sync { + self.sync_modpack(modpack, true).await?; + } + + Ok(()) + } + + async fn resolve_submitted_modpack_id( + &self, + modpack: &LocalModpack, + timestamp: i64, + ) -> Result> { + let synced_modpacks = self.get_synced_modpacks(false, None).await?; + + let mut matching = synced_modpacks.into_iter().filter(|synced_modpack| { + synced_modpack.name == modpack.name + && synced_modpack.minecraft_version == modpack.version + && synced_modpack.mod_loader == modpack.mod_loader + && synced_modpack.last_synced == timestamp + }); + + let first = matching.next().map(|modpack| modpack.modpack_id); + if matching.next().is_some() { + return Ok(None); + } + + Ok(first) + } + + fn persist_sync_metadata( + &self, + modpack_name: &str, + last_synced: u64, + modpack_id: Option<&str>, + ) -> Result<()> { + let modpack_folder = self.get_modpacks_folder()?.join(modpack_name); + if !modpack_folder.exists() { + return Ok(()); + } + + set_modpack_sync_date( + &self.get_minecraft_folder()?, + last_synced, + modpack_name, + modpack_id, + ) + } + + async fn notification_worker_loop(&self) { + loop { + if let Err(error) = self.bootstrap_notifications().await { + log::error!("Notification bootstrap failed: {error}"); + } + + if let Err(error) = self.run_notification_socket().await { + log::warn!("Notification socket cycle ended: {error}"); + } + + if let Err(error) = self.bootstrap_notifications().await { + log::warn!("Notification catch-up failed after socket cycle: {error}"); + } + + let delay = { + let mut state = self.inner.runtime_state.lock().await; + state.notification_state.reconnect_attempt = + state.notification_state.reconnect_attempt.saturating_add(1); + reconnect_delay(state.notification_state.reconnect_attempt) + }; + tokio::time::sleep(delay).await; + } + } + + async fn settings_sync_loop(&self) { + let mut interval = tokio::time::interval(Duration::from_secs(SETTINGS_SYNC_INTERVAL_SECS)); + loop { + interval.tick().await; + if let Err(error) = self.sync_remote_settings().await { + log::warn!("Settings sync worker failed: {error}"); + } + } + } + + async fn bootstrap_notifications(&self) -> Result<()> { + let cursor = self.load_notification_cursor()?; + let (notifications, next_cursor) = get_notification_history_all_since_with_refresh( + &self.inner.secret_store, + &self.inner.options.user_agent, + Some(&cursor), + None, + true, + &self.inner.options.oauth_client_id, + &self.inner.options.oauth_client_secret, + ) + .await?; + + if notifications.is_empty() && next_cursor == cursor { + return Ok(()); + } + + let merge_outcome = { + let mut state = self.inner.runtime_state.lock().await; + let runtime = &mut state.notification_state; + let merge_outcome = merge_notifications_and_collect_updates(runtime, notifications); + if runtime.cursor != next_cursor { + runtime.cursor = next_cursor.clone(); + } + merge_outcome + }; + + self.persist_notification_cursor(&next_cursor)?; + + if let Some(notifications) = merge_outcome.notifications_for_ui { + self.inner + .event_sink + .emit("refreshNotifications", notifications)?; + } + + for notification in merge_outcome.modpack_sync_notifications { + if let Err(error) = self.handle_modpack_sync_notification(¬ification).await { + log::warn!("Failed to process bootstrapped modpack sync notification: {error}"); + } else { + self.mark_modpack_sync_processed(¬ification).await; + } + } + + self.set_reconnect_attempt(0).await; + Ok(()) + } + + async fn run_notification_socket(&self) -> Result<()> { + let token = quadrant_core::account::get_account_token(&self.inner.secret_store)?; + let (cursor, connection_id) = { + let state = self.inner.runtime_state.lock().await; + ( + state.notification_state.cursor.clone(), + state.notification_connection_id.clone(), + ) + }; + + let ws_url = self.build_notification_ws_url(&cursor, &connection_id)?; + let mut request = ws_url.as_str().into_client_request()?; + request + .headers_mut() + .insert("Authorization", format!("Bearer {token}").parse()?); + request + .headers_mut() + .insert("User-Agent", self.inner.options.user_agent.parse()?); + + let (mut socket, _) = connect_async(request).await?; + self.set_reconnect_attempt(0).await; + + while let Some(frame) = socket.next().await { + match frame? { + Message::Text(payload) => self.handle_notification_ws_frame(&payload).await?, + Message::Close(frame) => { + let reason = frame + .map(|frame| frame.reason.to_string()) + .unwrap_or_else(|| "closed".to_string()); + return Err(anyhow!("notification websocket closed: {reason}")); + } + Message::Ping(_) | Message::Pong(_) | Message::Binary(_) | Message::Frame(_) => {} + } + } + + Err(anyhow!("notification websocket stream ended")) + } + + async fn handle_notification_ws_frame(&self, payload: &str) -> Result<()> { + match serde_json::from_str::(payload)? { + NotificationWsFrame::Connected { server_time } => { + log::info!("Notification websocket connected at {server_time}"); + } + NotificationWsFrame::Notification { + delivery: _, + notification, + } => { + let notifications_to_emit = { + let mut state = self.inner.runtime_state.lock().await; + let outcome = state.notification_state.upsert(notification.clone()); + outcome + .changed + .then(|| state.notification_state.notifications_for_ui()) + }; + + if let Some(notifications) = notifications_to_emit { + self.inner + .event_sink + .emit("refreshNotifications", notifications)?; + } + + if is_modpack_sync_notification(¬ification) { + if let Err(error) = self.handle_modpack_sync_notification(¬ification).await { + log::warn!("Failed to process modpack sync notification: {error}"); + } else { + self.mark_modpack_sync_processed(¬ification).await; + } + } + } + NotificationWsFrame::ReplayComplete { truncated, .. } => { + if truncated { + self.bootstrap_notifications().await?; + } + } + NotificationWsFrame::Error { error } => { + if error.to_lowercase().contains("lagged") { + self.bootstrap_notifications().await?; + } + return Err(anyhow!("notification websocket error frame: {error}")); + } + } + + Ok(()) + } + + fn build_notification_ws_url( + &self, + cursor: &NotificationCursor, + connection_id: &str, + ) -> Result { + let mut url = Url::parse( + self.inner + .options + .api_base_url + .as_deref() + .unwrap_or(quadrant_core::account::QNT_BASE_URL), + )?; + match url.scheme() { + "https" => url.set_scheme("wss").map_err(|_| anyhow!("invalid ws scheme"))?, + "http" => url.set_scheme("ws").map_err(|_| anyhow!("invalid ws scheme"))?, + "wss" | "ws" => {} + _ => return Err(anyhow!("unsupported backend scheme")), + } + url.set_path("/api/v3/account/notifications/ws"); + { + let mut query = url.query_pairs_mut(); + if let Some(created_at) = cursor.created_at.as_deref() { + query.append_pair("since", created_at); + } + query.append_pair("replay_limit", &WS_REPLAY_LIMIT.to_string()); + query.append_pair("modpack_sync", "true"); + query.append_pair("connection_id", connection_id); + } + Ok(url) + } + + async fn handle_modpack_sync_notification(&self, notification: &Notification) -> Result<()> { + let synced_modpack = self.resolve_synced_modpack_from_notification(notification).await?; + let auto_quadrant_sync = self + .inner + .config_store + .get_bool("autoQuadrantSync")? + .unwrap_or(false); + + if auto_quadrant_sync { + let local_modpack = self.resolve_local_modpack_for_sync(&synced_modpack).await?; + let local_modpack = self + .maybe_backfill_local_modpack_id(local_modpack, &synced_modpack) + .await?; + + if let Some(local_modpack) = local_modpack { + self.maybe_apply_remote_modpack_update(&local_modpack, &synced_modpack) + .await?; + } + } + + self.inner + .event_sink + .emit(REFRESH_SYNCED_MODPACKS_EVENT, synced_modpack.modpack_id)?; + Ok(()) + } + + async fn resolve_synced_modpack_from_notification( + &self, + notification: &Notification, + ) -> Result { + if let Some(payload) = parse_notification_message(notification) { + return Ok(payload.modpack); + } + + let resource_id = notification + .resource_id + .as_deref() + .ok_or_else(|| anyhow!("modpack sync notification missing resource_id"))?; + let mut synced_modpacks = self + .get_synced_modpacks(false, Some(resource_id.to_string())) + .await?; + synced_modpacks + .drain(..) + .next() + .ok_or_else(|| anyhow!("modpack sync fallback fetch returned no modpack")) + } + + async fn resolve_local_modpack_for_sync( + &self, + synced_modpack: &SyncedModpack, + ) -> Result> { + let modpacks = get_modpacks(&self.get_minecraft_folder()?, true)?; + + if let Some(local_modpack) = modpacks + .iter() + .find(|modpack| modpack.modpack_id.as_deref() == Some(synced_modpack.modpack_id.as_str())) + .cloned() + { + return Ok(Some(local_modpack)); + } + + Ok(modpacks + .into_iter() + .find(|modpack| modpack.last_synced != 0 && modpack.name == synced_modpack.name)) + } + + async fn maybe_backfill_local_modpack_id( + &self, + local_modpack: Option, + synced_modpack: &SyncedModpack, + ) -> Result> { + let Some(mut local_modpack) = local_modpack else { + return Ok(None); + }; + + if local_modpack.modpack_id.as_deref() == Some(synced_modpack.modpack_id.as_str()) { + return Ok(Some(local_modpack)); + } + + let local_sync_time = u64::try_from(local_modpack.last_synced / 1000).unwrap_or_default(); + self.persist_sync_metadata( + &local_modpack.name, + local_sync_time, + Some(synced_modpack.modpack_id.as_str()), + )?; + local_modpack.modpack_id = Some(synced_modpack.modpack_id.clone()); + Ok(Some(local_modpack)) + } + + async fn maybe_apply_remote_modpack_update( + &self, + local_modpack: &LocalModpack, + synced_modpack: &SyncedModpack, + ) -> Result<()> { + let local_sync_time = local_modpack.last_synced / 1000; + if synced_modpack.last_synced <= local_sync_time { + return Ok(()); + } + + if !self.begin_modpack_update(&local_modpack.name).await { + return Ok(()); + } + + let install_result = self + .install_modpack(InstalledModpack { + mod_loader: synced_modpack.mod_loader, + name: synced_modpack.name.clone(), + version: synced_modpack.minecraft_version.clone(), + mods: serde_json::from_str(&synced_modpack.mods)?, + }) + .await; + + if let Err(error) = install_result { + self.finish_modpack_update(&local_modpack.name).await; + return Err(error); + } + + self.persist_sync_metadata( + &local_modpack.name, + synced_modpack.last_synced as u64, + Some(synced_modpack.modpack_id.as_str()), + )?; + self.finish_modpack_update(&local_modpack.name).await; + Ok(()) + } + + async fn begin_modpack_update(&self, modpack_name: &str) -> bool { + let mut state = self.inner.runtime_state.lock().await; + if state.updated_modpacks.iter().any(|name| name == modpack_name) { + return false; + } + state.updated_modpacks.push(modpack_name.to_string()); + true + } + + async fn finish_modpack_update(&self, modpack_name: &str) { + let mut state = self.inner.runtime_state.lock().await; + state.updated_modpacks.retain(|name| name != modpack_name); + } + + async fn sync_remote_settings(&self) -> Result<()> { + if !self + .inner + .config_store + .get_bool("syncSettings")? + .unwrap_or(false) + { + return Ok(()); + } + + if let Err(error) = self.get_quadrant_settings().await { + if error.to_string() == "Current settings are newer" { + self.submit_quadrant_settings().await?; + } else { + return Err(error); + } + } + + Ok(()) + } + + async fn mark_notification_read_and_snapshot( + &self, + notification_id: &str, + ) -> Option> { + let mut state = self.inner.runtime_state.lock().await; + state + .notification_state + .mark_read(notification_id) + .then(|| state.notification_state.notifications_for_ui()) + } + + async fn set_reconnect_attempt(&self, attempt: u32) { + let mut state = self.inner.runtime_state.lock().await; + state.notification_state.reconnect_attempt = attempt; + } + + async fn mark_modpack_sync_processed(&self, notification: &Notification) { + let mut state = self.inner.runtime_state.lock().await; + state + .notification_state + .mark_modpack_sync_processed(notification); + } + + fn load_notification_cursor(&self) -> Result { + Ok(NotificationCursor { + created_at: self + .inner + .config_store + .get_string(NOTIFICATION_CURSOR_CREATED_AT_KEY)?, + notification_id: self + .inner + .config_store + .get_string(NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY)?, + }) + } + + fn persist_notification_cursor(&self, cursor: &NotificationCursor) -> Result<()> { + match &cursor.created_at { + Some(created_at) => self + .inner + .config_store + .set_string(NOTIFICATION_CURSOR_CREATED_AT_KEY, created_at.clone())?, + None => self + .inner + .config_store + .delete_key(NOTIFICATION_CURSOR_CREATED_AT_KEY)?, + } + match &cursor.notification_id { + Some(notification_id) => self + .inner + .config_store + .set_string(NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY, notification_id.clone())?, + None => self + .inner + .config_store + .delete_key(NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY)?, + } + Ok(()) + } +} + +#[derive(Default, Clone)] +struct NotificationRuntimeState { + by_key: HashMap, + key_by_notification_id: HashMap, + ordered_keys: Vec, + processed_modpack_sync_by_key: HashMap, + cursor: NotificationCursor, + reconnect_attempt: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct UpsertOutcome { + changed: bool, +} + +struct MergeNotificationsOutcome { + notifications_for_ui: Option>, + modpack_sync_notifications: Vec, +} + +#[derive(Debug, Deserialize)] +struct ModpackSyncPayload { + notification_type: String, + modpack: SyncedModpack, +} + +impl NotificationRuntimeState { + fn upsert(&mut self, notification: Notification) -> UpsertOutcome { + self.advance_cursor(¬ification); + let identity_key = notification_identity_key(¬ification); + match self.by_key.get(&identity_key) { + Some(existing) if existing == ¬ification => UpsertOutcome { changed: false }, + Some(_) => { + if let Some(previous) = self.by_key.insert(identity_key.clone(), notification.clone()) + { + self.key_by_notification_id + .remove(previous.notification_id.as_str()); + } + self.key_by_notification_id + .insert(notification.notification_id.clone(), identity_key); + self.sort_keys(); + UpsertOutcome { changed: true } + } + None => { + self.ordered_keys.push(identity_key.clone()); + self.by_key + .insert(identity_key.clone(), notification.clone()); + self.key_by_notification_id + .insert(notification.notification_id.clone(), identity_key); + self.sort_keys(); + UpsertOutcome { changed: true } + } + } + } + + fn mark_read(&mut self, notification_id: &str) -> bool { + let Some(identity_key) = self.key_by_notification_id.get(notification_id).cloned() else { + return false; + }; + let Some(notification) = self.by_key.get_mut(identity_key.as_str()) else { + return false; + }; + if notification.read { + return false; + } + notification.read = true; + true + } + + fn notifications_for_ui(&self) -> Vec { + let mut notifications = self + .ordered_keys + .iter() + .filter_map(|key| self.by_key.get(key)) + .cloned() + .collect::>(); + notifications.sort_by(|a, b| { + b.created_at_unix + .cmp(&a.created_at_unix) + .then_with(|| b.notification_id.cmp(&a.notification_id)) + }); + notifications + } + + fn advance_cursor(&mut self, notification: &Notification) { + let current_created_at = self.cursor.created_at.as_deref(); + let current_notification_id = self.cursor.notification_id.as_deref(); + let is_newer = match current_created_at { + None => true, + Some(created_at) => { + notification.created_at.as_str() > created_at + || (notification.created_at.as_str() == created_at + && current_notification_id + .map(|id| notification.notification_id.as_str() > id) + .unwrap_or(true)) + } + }; + + if is_newer { + self.cursor = NotificationCursor { + created_at: Some(notification.created_at.clone()), + notification_id: Some(notification.notification_id.clone()), + }; + } + } + + fn sort_keys(&mut self) { + self.ordered_keys.sort_by(|left, right| { + let left_notification = self.by_key.get(left).expect("missing notification"); + let right_notification = self.by_key.get(right).expect("missing notification"); + left_notification + .created_at_unix + .cmp(&right_notification.created_at_unix) + .then_with(|| { + left_notification + .notification_id + .cmp(&right_notification.notification_id) + }) + }); + } + + fn should_process_modpack_sync_notification(&self, notification: &Notification) -> bool { + if !is_modpack_sync_notification(notification) { + return false; + } + + let identity_key = notification_identity_key(notification); + self.processed_modpack_sync_by_key + .get(identity_key.as_str()) + .map(|notification_id| notification_id != ¬ification.notification_id) + .unwrap_or(true) + } + + fn mark_modpack_sync_processed(&mut self, notification: &Notification) { + if !is_modpack_sync_notification(notification) { + return; + } + + let identity_key = notification_identity_key(notification); + self.processed_modpack_sync_by_key + .insert(identity_key, notification.notification_id.clone()); + } +} + +fn merge_notifications_and_collect_updates( + runtime: &mut NotificationRuntimeState, + mut notifications: Vec, +) -> MergeNotificationsOutcome { + notifications.sort_by(|left, right| { + left.created_at_unix + .cmp(&right.created_at_unix) + .then_with(|| left.notification_id.cmp(&right.notification_id)) + }); + + let mut changed = false; + let mut latest_modpack_sync_by_key = HashMap::new(); + let mut modpack_sync_order = Vec::new(); + + for notification in notifications { + let should_queue_modpack_sync = + runtime.should_process_modpack_sync_notification(¬ification); + let outcome = runtime.upsert(notification.clone()); + changed |= outcome.changed; + + if should_queue_modpack_sync { + let identity_key = notification_identity_key(¬ification); + latest_modpack_sync_by_key.insert(identity_key.clone(), notification); + if let Some(index) = modpack_sync_order + .iter() + .position(|existing| existing == &identity_key) + { + modpack_sync_order.remove(index); + } + modpack_sync_order.push(identity_key); + } + } + + MergeNotificationsOutcome { + notifications_for_ui: changed.then(|| runtime.notifications_for_ui()), + modpack_sync_notifications: modpack_sync_order + .into_iter() + .filter_map(|identity_key| latest_modpack_sync_by_key.remove(&identity_key)) + .collect(), + } +} + +fn reconnect_delay(attempt: u32) -> Duration { + let secs = match attempt { + 0 | 1 => 1, + 2 => 2, + 3 => 5, + 4 => 10, + 5 => 30, + _ => 60, + }; + Duration::from_secs(secs) +} + +fn parse_notification_message(notification: &Notification) -> Option { + serde_json::from_str::(¬ification.message) + .ok() + .filter(|payload| payload.notification_type == "modpack_sync") +} + +fn is_modpack_sync_notification(notification: &Notification) -> bool { + notification.notification_type.as_deref() == Some("modpack_sync") +} + +fn notification_identity_key(notification: &Notification) -> String { + if is_modpack_sync_notification(notification) + && let Some(resource_id) = notification.resource_id.as_deref() + { + return format!("modpack_sync:{resource_id}"); + } + notification.notification_id.clone() +} + +fn from_value(value: Value) -> Result { + serde_json::from_value(value).map_err(Error::from) +} + +fn to_value(value: T) -> Result { + serde_json::to_value(value).map_err(Error::from) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct HideFreeArgs { + hide_free: bool, +} + +#[derive(Deserialize)] +struct NameArgs { + name: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct DeleteModArgs { + modpack_name: String, + mod_id: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct UpdateModpackArgs { + modpack_source: String, + name: Option, + version: Option, + mod_loader: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CreateModpackArgs { + name: String, + version: String, + mod_loader: ModLoader, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RegisterModArgs { + mod_: InstalledMod, + modpack: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct InstallModpackArgs { + mod_config: InstalledModpack, +} + +#[derive(Deserialize)] +struct ExportModpackArgs { + modpack: String, + destination: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct SetModpackSyncDateArgs { + time: u64, + modpack: String, + modpack_id: Option, +} + +#[derive(Deserialize)] +struct SearchModsCompatArgs { + args: GlobalSearchModsArgs, +} + +#[derive(Deserialize)] +struct GetUserUrlArgs { + username: String, + source: ModSource, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct InstallModArgs { + id: String, + minecraft_version: String, + mod_loader: ModLoader, + source: ModSource, + modpack: Option, + mod_type: ModType, + file_id: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct InstallRemoteFileArgs { + file: UniversalModFile, + mod_type: ModType, + modpack: Option, + source: ModSource, + id: String, +} + +#[derive(Deserialize)] +struct ModpackOnlyArgs { + modpack: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CheckModUpdatesArgs { + mod_to_update: Mod, + minecraft_version: String, + mod_loader: ModLoader, + modpack_name: String, +} + +#[derive(Deserialize)] +struct GetModArgsWrapper { + args: GetModArgs, +} + +#[derive(Deserialize)] +struct IdArgs { + id: String, +} + +#[derive(Deserialize)] +struct SetSecretArgs { + key: String, + value: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct OAuthLoginArgs { + code: String, + redirect_uri: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct NotificationIdArgs { + notification_id: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GetSyncedModpacksArgs { + show_owners: bool, + modpack_id: Option, +} + +#[derive(Deserialize)] +struct KickMemberArgs { + modpack_id: String, + username: String, +} + +#[derive(Deserialize)] +struct InviteMemberArgs { + modpack_id: String, + username: String, + admin: bool, +} + +#[derive(Deserialize)] +struct ModpackIdArgs { + modpack_id: String, +} + +#[derive(Deserialize)] +struct SyncModpackArgs { + modpack: LocalModpack, + overwrite: bool, +} + +#[derive(Deserialize)] +struct AnswerInviteArgs { + modpack_id: String, + notification_id: String, + answer: bool, +} + +#[derive(Deserialize)] +struct ModpackNameArgs { + modpack_name: String, +} + +#[derive(Deserialize)] +struct CodeArgs { + code: String, +} + +#[cfg(test)] +mod tests { + use super::{ + HostEventEnvelope, JsonFileStore, Notification, NotificationCursor, + NotificationRuntimeState, QuadrantHost, QuadrantHostOptions, + merge_notifications_and_collect_updates, + }; + use quadrant_core::{account::KEYRING_SERVICE, ports::SettingsStore}; + use serde_json::Value; + use std::path::PathBuf; + use tempfile::tempdir; + + fn notification(id: &str, unix: i64, read: bool) -> Notification { + Notification { + notification_id: id.to_string(), + user_id: "u1".to_string(), + notification_type: Some("invite_to_sync".to_string()), + resource_id: None, + message: "{\"simple_message\":\"hello\"}".to_string(), + created_at: format!("2026-03-20T10:{unix:02}:00Z"), + created_at_unix: unix, + read, + } + } + + #[test] + fn json_store_persists_updates() { + let temp_dir = tempdir().unwrap(); + let store = JsonFileStore::new(temp_dir.path().join("config.json")).unwrap(); + store.set_bool("modrinth", true).unwrap(); + let reloaded = JsonFileStore::new(temp_dir.path().join("config.json")).unwrap(); + assert_eq!(reloaded.get_bool("modrinth").unwrap(), Some(true)); + } + + #[test] + fn notification_merge_deduplicates_and_orders() { + let mut state = NotificationRuntimeState::default(); + assert!(merge_notifications_and_collect_updates( + &mut state, + vec![notification("n1", 1, false), notification("n2", 2, false)] + ) + .notifications_for_ui + .is_some()); + assert!( + merge_notifications_and_collect_updates(&mut state, vec![notification("n1", 1, false)]) + .notifications_for_ui + .is_none() + ); + + let notifications = state.notifications_for_ui(); + assert_eq!(notifications[0].notification_id, "n2"); + assert_eq!( + state.cursor, + NotificationCursor { + created_at: Some("2026-03-20T10:02:00Z".to_string()), + notification_id: Some("n2".to_string()), + } + ); + } + + #[test] + fn supported_commands_include_settings_sync_surface() { + let commands = QuadrantHost::supported_commands(); + assert!(commands.contains(&"get_quadrant_settings")); + assert!(commands.contains(&"submit_quadrant_settings")); + assert!(!commands.contains(&"open_modpacks_folder")); + assert!(!commands.contains(&"request_check_for_updates")); + } + + #[test] + fn host_options_default_to_compatible_keyring_name() { + let options = QuadrantHostOptions::new( + PathBuf::from("C:/quadrant"), + "client", + "secret", + "api-key", + ); + assert_eq!(options.keyring_service_name, KEYRING_SERVICE); + } + + #[tokio::test] + async fn event_subscription_receives_emitted_envelopes() { + let temp_dir = tempdir().unwrap(); + let host = QuadrantHost::new(QuadrantHostOptions::new( + temp_dir.path().to_path_buf(), + "client", + "secret", + "api-key", + )) + .unwrap(); + + let mut receiver = host.subscribe_events(); + host.inner + .event_sink + .emit("refreshSyncedModpacks", "modpack-1") + .unwrap(); + + let envelope: HostEventEnvelope = receiver.recv().await.unwrap(); + assert_eq!(envelope.event, "refreshSyncedModpacks"); + assert_eq!(envelope.payload, Value::String("modpack-1".to_string())); + } +} diff --git a/src-tauri/crates/quadrant-napi/Cargo.toml b/src-tauri/crates/quadrant-napi/Cargo.toml new file mode 100644 index 00000000..0f865c25 --- /dev/null +++ b/src-tauri/crates/quadrant-napi/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "quadrant-napi" +version.workspace = true +edition = "2024" +build = "build.rs" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +napi = { version = "3", default-features = false, features = ["napi8", "tokio_rt", "serde-json"] } +napi-derive = "3" +quadrant-host = { path = "../quadrant-host" } +serde_json = "1.0.149" +tokio = { version = "1.50.0", features = ["full"] } + +[build-dependencies] +napi-build = "2" diff --git a/src-tauri/crates/quadrant-napi/build.rs b/src-tauri/crates/quadrant-napi/build.rs new file mode 100644 index 00000000..0f1b0100 --- /dev/null +++ b/src-tauri/crates/quadrant-napi/build.rs @@ -0,0 +1,3 @@ +fn main() { + napi_build::setup(); +} diff --git a/src-tauri/crates/quadrant-napi/index.js b/src-tauri/crates/quadrant-napi/index.js new file mode 100644 index 00000000..9b086960 --- /dev/null +++ b/src-tauri/crates/quadrant-napi/index.js @@ -0,0 +1,26 @@ +const candidates = [ + process.env.QUADRANT_NAPI_BINDING, + "./index.node", + "./quadrant-napi.node", + "./quadrant_napi.node", +].filter(Boolean); + +let loaded; +let lastError; + +for (const candidate of candidates) { + try { + loaded = require(candidate); + break; + } catch (error) { + lastError = error; + } +} + +if (!loaded) { + throw new Error( + `Failed to load Quadrant N-API binding. Set QUADRANT_NAPI_BINDING to the built addon path. Last error: ${lastError}`, + ); +} + +module.exports = loaded; diff --git a/src-tauri/crates/quadrant-napi/package.json b/src-tauri/crates/quadrant-napi/package.json new file mode 100644 index 00000000..0df22d43 --- /dev/null +++ b/src-tauri/crates/quadrant-napi/package.json @@ -0,0 +1,10 @@ +{ + "name": "@quadrant/quadrant-napi", + "version": "26.4.0", + "main": "index.js", + "files": [ + "index.js", + "index.node", + "*.node" + ] +} diff --git a/src-tauri/crates/quadrant-napi/src/lib.rs b/src-tauri/crates/quadrant-napi/src/lib.rs new file mode 100644 index 00000000..c1163221 --- /dev/null +++ b/src-tauri/crates/quadrant-napi/src/lib.rs @@ -0,0 +1,213 @@ +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; + +use napi::{ + Result, + bindgen_prelude::Function, + threadsafe_function::ThreadsafeFunctionCallMode, +}; +use napi_derive::napi; +use quadrant_host::{QuadrantHost, QuadrantHostOptions}; +use serde_json::Value; + +#[napi(object)] +pub struct QuadrantHostInit { + pub data_dir: String, + pub mc_folder: Option, + pub api_base_url: Option, + pub oauth_client_id: String, + pub oauth_client_secret: String, + pub quadrant_api_key: String, + pub config_store_name: Option, + pub update_store_name: Option, + pub keyring_service_name: Option, + pub app_version: Option, + pub os_name: Option, + pub user_agent: Option, +} + +#[napi] +pub struct QuadrantHostAddon { + host: QuadrantHost, + event_forwarder: Arc>>>, +} + +#[napi] +impl QuadrantHostAddon { + #[napi(constructor)] + pub fn new(options: QuadrantHostInit) -> Result { + let mut host_options = QuadrantHostOptions::new( + PathBuf::from(options.data_dir), + options.oauth_client_id, + options.oauth_client_secret, + options.quadrant_api_key, + ); + host_options.mc_folder = options.mc_folder.map(PathBuf::from); + host_options.api_base_url = options.api_base_url; + if let Some(value) = options.config_store_name { + host_options.config_store_name = value; + } + if let Some(value) = options.update_store_name { + host_options.update_store_name = value; + } + if let Some(value) = options.keyring_service_name { + host_options.keyring_service_name = value; + } + if let Some(value) = options.app_version { + host_options.app_version = value; + } + if let Some(value) = options.os_name { + host_options.os_name = value; + } + if let Some(value) = options.user_agent { + host_options.user_agent = value; + } + + Ok(Self { + host: QuadrantHost::new(host_options) + .map_err(|error| napi::Error::from_reason(error.to_string()))?, + event_forwarder: Arc::new(Mutex::new(None)), + }) + } + + #[napi] + pub async fn start_background_workers(&self) -> Result<()> { + self.host + .start_background_workers() + .await + .map_err(to_napi_error) + } + + #[napi] + pub async fn stop_background_workers(&self) -> Result<()> { + self.host + .stop_background_workers() + .await + .map_err(to_napi_error) + } + + #[napi] + pub async fn shutdown(&self) -> Result<()> { + self.host.shutdown().await.map_err(to_napi_error)?; + self.clear_event_forwarder(); + Ok(()) + } + + #[napi] + pub fn on_event(&self, callback: Function<'_, String, ()>) -> Result<()> { + self.clear_event_forwarder(); + + let tsfn = callback.build_threadsafe_function().build()?; + let mut receiver = self.host.subscribe_events(); + let handle = tokio::spawn(async move { + loop { + match receiver.recv().await { + Ok(event) => { + if let Ok(payload) = serde_json::to_string(&event) { + let _ = tsfn.call(payload, ThreadsafeFunctionCallMode::NonBlocking); + } + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, + } + } + }); + + *self + .event_forwarder + .lock() + .map_err(|_| napi::Error::from_reason("event forwarder is busy"))? = Some(handle); + Ok(()) + } + + #[napi] + pub async fn invoke(&self, command: String, payload: Option) -> Result { + self.host + .invoke(&command, payload.unwrap_or(Value::Null)) + .await + .map_err(to_napi_error) + } + + #[napi] + pub async fn init_config(&self) -> Result<()> { + self.host.init_config().map_err(to_napi_error) + } + + #[napi] + pub async fn get_minecraft_folder(&self) -> Result { + Ok(self + .host + .get_minecraft_folder() + .map_err(to_napi_error)? + .to_string_lossy() + .to_string()) + } + + #[napi] + pub async fn get_modpacks(&self, hide_free: Option) -> Result { + self.host + .get_modpacks(hide_free.unwrap_or(false)) + .await + .and_then(|value| serde_json::to_value(value).map_err(Into::into)) + .map_err(to_napi_error) + } + + #[napi] + pub async fn get_account_info(&self) -> Result { + self.host + .get_account_info() + .await + .and_then(|value| serde_json::to_value(value).map_err(Into::into)) + .map_err(to_napi_error) + } + + #[napi] + pub async fn get_news(&self) -> Result { + self.host + .get_news() + .await + .and_then(|value| serde_json::to_value(value).map_err(Into::into)) + .map_err(to_napi_error) + } + + #[napi] + pub async fn install_mod(&self, args: Value) -> Result<()> { + self.host + .invoke("install_mod", args) + .await + .map_err(to_napi_error) + .map(|_| ()) + } + + #[napi] + pub async fn sync_modpack(&self, modpack: Value, overwrite: bool) -> Result<()> { + self.host + .invoke( + "sync_modpack", + serde_json::json!({ + "modpack": modpack, + "overwrite": overwrite, + }), + ) + .await + .map_err(to_napi_error) + .map(|_| ()) + } +} + +impl QuadrantHostAddon { + fn clear_event_forwarder(&self) { + if let Some(handle) = self + .event_forwarder + .lock() + .expect("event forwarder lock poisoned") + .take() + { + handle.abort(); + } + } +} + +fn to_napi_error(error: impl ToString) -> napi::Error { + napi::Error::from_reason(error.to_string()) +} diff --git a/src-tauri/src/account/id.rs b/src-tauri/src/account/id.rs index 3529339a..10017ce1 100644 --- a/src-tauri/src/account/id.rs +++ b/src-tauri/src/account/id.rs @@ -1,208 +1,16 @@ -use std::{collections::HashMap, time::Duration}; - -use anyhow::anyhow; -use futures::StreamExt; -use serde::Deserialize; -use tauri::{AppHandle, Emitter, Manager}; -use tauri_plugin_notification::NotificationExt; -use tauri_plugin_store::StoreExt; -use tokio::sync::Mutex; -use tokio_tungstenite::{ - connect_async, - tungstenite::{Message, client::IntoClientRequest}, -}; -use url::Url; - -use crate::{ - AppState, - account::{ - self, - quadrant_settings_sync::{get_quadrant_settings, submit_quadrant_settings}, - quadrant_sync::{SyncedModpack, get_synced_modpacks, persist_sync_metadata}, - }, - mc_mod::get_user_agent, - modpacks::general::{InstalledModpack, LocalModpack, install_modpack}, - tauri_adapter::{TauriSecretStore, mc_folder}, -}; +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; pub use quadrant_core::account::id::{ AccountInfo, Notification, NotificationCursor, NotificationWsFrame, OAuth2Response, }; -const NOTIFICATION_CURSOR_CREATED_AT_KEY: &str = "notificationCursorCreatedAt"; -const NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY: &str = "notificationCursorNotificationId"; -const SHOWN_NOTIFICATIONS_KEY: &str = "shownNotifications"; -const SETTINGS_SYNC_INTERVAL_SECS: u64 = 120; -const WS_REPLAY_LIMIT: usize = 500; -const NOTIFICATION_TITLE: &str = "Quadrant ID"; -const REFRESH_SYNCED_MODPACKS_EVENT: &str = "refreshSyncedModpacks"; - -#[derive(Default, Clone)] -pub struct NotificationRuntimeState { - pub by_key: HashMap, - pub key_by_notification_id: HashMap, - pub ordered_keys: Vec, - pub processed_modpack_sync_by_key: HashMap, - pub cursor: NotificationCursor, - pub reconnect_attempt: u32, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -struct UpsertOutcome { - changed: bool, - inserted: bool, -} - -struct MergeNotificationsOutcome { - notifications_for_ui: Option>, - modpack_sync_notifications: Vec, -} - -impl NotificationRuntimeState { - fn upsert(&mut self, notification: Notification) -> UpsertOutcome { - self.advance_cursor(¬ification); - let identity_key = notification_identity_key(¬ification); - match self.by_key.get(&identity_key) { - Some(existing) if existing == ¬ification => UpsertOutcome { - changed: false, - inserted: false, - }, - Some(_) => { - if let Some(previous) = self - .by_key - .insert(identity_key.clone(), notification.clone()) - { - self.key_by_notification_id - .remove(previous.notification_id.as_str()); - } - self.key_by_notification_id - .insert(notification.notification_id.clone(), identity_key); - self.sort_keys(); - UpsertOutcome { - changed: true, - inserted: false, - } - } - None => { - self.ordered_keys.push(identity_key.clone()); - self.by_key - .insert(identity_key.clone(), notification.clone()); - self.key_by_notification_id - .insert(notification.notification_id.clone(), identity_key); - self.sort_keys(); - UpsertOutcome { - changed: true, - inserted: true, - } - } - } - } - - fn mark_read(&mut self, notification_id: &str) -> bool { - let Some(identity_key) = self.key_by_notification_id.get(notification_id).cloned() else { - return false; - }; - let Some(notification) = self.by_key.get_mut(identity_key.as_str()) else { - return false; - }; - if notification.read { - return false; - } - notification.read = true; - true - } - - fn notifications_for_ui(&self) -> Vec { - let mut notifications = self - .ordered_keys - .iter() - .filter_map(|key| self.by_key.get(key)) - .cloned() - .collect::>(); - notifications.sort_by(|a, b| { - b.created_at_unix - .cmp(&a.created_at_unix) - .then_with(|| b.notification_id.cmp(&a.notification_id)) - }); - notifications - } - - fn advance_cursor(&mut self, notification: &Notification) { - let current_created_at = self.cursor.created_at.as_deref(); - let current_notification_id = self.cursor.notification_id.as_deref(); - let is_newer = match current_created_at { - None => true, - Some(created_at) => { - notification.created_at.as_str() > created_at - || (notification.created_at.as_str() == created_at - && current_notification_id - .map(|id| notification.notification_id.as_str() > id) - .unwrap_or(true)) - } - }; - - if is_newer { - self.cursor = NotificationCursor { - created_at: Some(notification.created_at.clone()), - notification_id: Some(notification.notification_id.clone()), - }; - } - } - - fn sort_keys(&mut self) { - self.ordered_keys.sort_by(|left, right| { - let left_notification = self.by_key.get(left).expect("missing notification"); - let right_notification = self.by_key.get(right).expect("missing notification"); - left_notification - .created_at_unix - .cmp(&right_notification.created_at_unix) - .then_with(|| { - left_notification - .notification_id - .cmp(&right_notification.notification_id) - }) - }); - } - - fn should_process_modpack_sync_notification(&self, notification: &Notification) -> bool { - if !is_modpack_sync_notification(notification) { - return false; - } - - let identity_key = notification_identity_key(notification); - self.processed_modpack_sync_by_key - .get(identity_key.as_str()) - .map(|notification_id| notification_id != ¬ification.notification_id) - .unwrap_or(true) - } - - fn mark_modpack_sync_processed(&mut self, notification: &Notification) { - if !is_modpack_sync_notification(notification) { - return; - } - - let identity_key = notification_identity_key(notification); - self.processed_modpack_sync_by_key - .insert(identity_key, notification.notification_id.clone()); - } -} - -#[derive(Debug, Deserialize)] -struct ModpackSyncPayload { - notification_type: String, - modpack: SyncedModpack, -} - #[tauri::command] -pub async fn get_account_info() -> Result { - quadrant_core::account::id::get_account_info_with_refresh( - &TauriSecretStore, - &get_user_agent(), - env!("QUADRANT_OAUTH2_CLIENT_ID"), - env!("QUADRANT_OAUTH2_CLIENT_SECRET"), - ) - .await - .map_err(tauri::Error::from) +pub async fn get_account_info(app: AppHandle) -> Result { + app.state::() + .get_account_info() + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -211,23 +19,15 @@ pub async fn oauth2_login( redirect_uri: String, app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::account::id::oauth2_login( - &TauriSecretStore, - &get_user_agent(), - env!("QUADRANT_OAUTH2_CLIENT_ID"), - env!("QUADRANT_OAUTH2_CLIENT_SECRET"), - code, - redirect_uri, - ) - .await - .map_err(tauri::Error::from)?; - app.emit("recheckAccountToken", "")?; - Ok(()) + app.state::() + .oauth2_login(code, redirect_uri) + .await + .map_err(tauri::Error::from) } #[tauri::command] -pub fn oauth2_client_id() -> String { - env!("QUADRANT_OAUTH2_CLIENT_ID").to_string() +pub fn oauth2_client_id(app: AppHandle) -> String { + app.state::().oauth2_client_id() } #[tauri::command] @@ -235,899 +35,8 @@ pub async fn read_notification( notification_id: String, app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::account::id::read_notification( - &TauriSecretStore, - &get_user_agent(), - notification_id.clone(), - ) - .await - .map_err(tauri::Error::from)?; - - if let Some(notifications) = mark_notification_read_and_snapshot(&app, ¬ification_id).await { - emit_notification_refresh(&app, notifications)?; - } - - Ok(()) -} - -pub fn start_notification_worker(app: AppHandle) { - tokio::task::spawn(async move { - loop { - if let Err(error) = bootstrap_notifications(&app).await { - log::error!("Notification bootstrap failed: {error}"); - } - - let stream_result = run_notification_socket(app.clone()).await; - if let Err(error) = stream_result { - log::warn!("Notification socket cycle ended: {error}"); - } - - if let Err(error) = bootstrap_notifications(&app).await { - log::warn!("Notification catch-up failed after socket cycle: {error}"); - } - - let delay = { - let state = app.state::>(); - let mut state = state.lock().await; - state.notification_state.reconnect_attempt = - state.notification_state.reconnect_attempt.saturating_add(1); - reconnect_delay(state.notification_state.reconnect_attempt) - }; - tokio::time::sleep(delay).await; - } - }); -} - -pub fn start_settings_sync_worker(app: AppHandle) { - tokio::task::spawn(async move { - let mut interval = tokio::time::interval(Duration::from_secs(SETTINGS_SYNC_INTERVAL_SECS)); - loop { - interval.tick().await; - if let Err(error) = sync_remote_settings(app.clone()).await { - log::warn!("Settings sync worker failed: {error}"); - } - } - }); -} - -async fn bootstrap_notifications(app: &AppHandle) -> Result<(), anyhow::Error> { - let cursor = load_notification_cursor(app)?; - let (notifications, next_cursor) = - quadrant_core::account::id::get_notification_history_all_since( - &TauriSecretStore, - &get_user_agent(), - Some(&cursor), - None, - true, - ) - .await?; - - if notifications.is_empty() && next_cursor == cursor { - return Ok(()); - } - - let merge_outcome = { - let state = app.state::>(); - let mut state = state.lock().await; - let runtime = &mut state.notification_state; - let merge_outcome = merge_notifications_and_collect_updates(runtime, notifications); - if runtime.cursor != next_cursor { - runtime.cursor = next_cursor.clone(); - } - merge_outcome - }; - - persist_notification_cursor(app, &next_cursor)?; - - if let Some(notifications) = merge_outcome.notifications_for_ui { - emit_notification_refresh(app, notifications)?; - } - - for notification in merge_outcome.modpack_sync_notifications { - match handle_modpack_sync_notification(app, ¬ification).await { - Ok(()) => { - mark_modpack_sync_processed(app, ¬ification).await; - } - Err(error) => { - log::warn!("Failed to process bootstrapped modpack sync notification: {error}"); - } - } - } - - set_reconnect_attempt(app, 0).await; - - Ok(()) -} - -fn merge_notifications_and_collect_updates( - runtime: &mut NotificationRuntimeState, - mut notifications: Vec, -) -> MergeNotificationsOutcome { - notifications.sort_by(|left, right| { - left.created_at_unix - .cmp(&right.created_at_unix) - .then_with(|| left.notification_id.cmp(&right.notification_id)) - }); - - let mut changed = false; - let mut latest_modpack_sync_by_key = HashMap::new(); - let mut modpack_sync_order = Vec::new(); - - for notification in notifications { - let should_queue_modpack_sync = runtime.should_process_modpack_sync_notification(¬ification); - let outcome = runtime.upsert(notification.clone()); - changed |= outcome.changed; - - if should_queue_modpack_sync { - let identity_key = notification_identity_key(¬ification); - latest_modpack_sync_by_key.insert(identity_key.clone(), notification); - if let Some(index) = modpack_sync_order - .iter() - .position(|existing| existing == &identity_key) - { - modpack_sync_order.remove(index); - } - modpack_sync_order.push(identity_key); - } - } - - MergeNotificationsOutcome { - notifications_for_ui: changed.then(|| runtime.notifications_for_ui()), - modpack_sync_notifications: modpack_sync_order - .into_iter() - .filter_map(|identity_key| latest_modpack_sync_by_key.remove(&identity_key)) - .collect(), - } -} - -async fn run_notification_socket(app: AppHandle) -> Result<(), anyhow::Error> { - let token = account::get_account_token()?; - let (cursor, connection_id) = { - let state = app.state::>(); - let state = state.lock().await; - ( - state.notification_state.cursor.clone(), - state.notification_connection_id.clone(), - ) - }; - - let ws_url = build_notification_ws_url(&cursor, &connection_id)?; - let mut request = ws_url.as_str().into_client_request()?; - request - .headers_mut() - .insert("Authorization", format!("Bearer {token}").parse()?); - request - .headers_mut() - .insert("User-Agent", get_user_agent().parse()?); - - let (mut socket, _) = connect_async(request).await?; - set_reconnect_attempt(&app, 0).await; - - while let Some(frame) = socket.next().await { - match frame? { - Message::Text(payload) => { - let payload = payload.to_string(); - handle_notification_ws_frame(&app, &payload).await?; - } - Message::Close(frame) => { - let reason = frame - .map(|f| f.reason.to_string()) - .unwrap_or_else(|| "closed".to_string()); - return Err(anyhow!("notification websocket closed: {reason}")); - } - Message::Ping(_) | Message::Pong(_) | Message::Binary(_) | Message::Frame(_) => {} - } - } - - Err(anyhow!("notification websocket stream ended")) -} - -async fn handle_notification_ws_frame(app: &AppHandle, payload: &str) -> Result<(), anyhow::Error> { - let frame = serde_json::from_str::(payload)?; - match frame { - NotificationWsFrame::Connected { server_time } => { - log::info!("Notification websocket connected at {server_time}"); - } - NotificationWsFrame::Notification { - delivery, - notification, - } => { - let should_notify = delivery == "live" && !notification.read; - let notification_id = notification.notification_id.clone(); - let simple_message = notification_simple_message(¬ification); - let is_modpack_sync = is_modpack_sync_notification(¬ification); - let notification_for_processing = notification.clone(); - - let notifications_to_emit = { - let state = app.state::>(); - let mut state = state.lock().await; - let outcome = state.notification_state.upsert(notification); - outcome.changed.then(|| { - ( - state.notification_state.notifications_for_ui(), - outcome.inserted, - ) - }) - }; - - if let Some((notifications, inserted)) = notifications_to_emit { - emit_notification_refresh(app, notifications)?; - if should_notify && inserted { - maybe_show_native_notification( - app, - ¬ification_for_processing, - ¬ification_id, - simple_message.as_deref(), - ) - .await?; - } - } - - if is_modpack_sync { - match handle_modpack_sync_notification(app, ¬ification_for_processing).await { - Ok(()) => { - mark_modpack_sync_processed(app, ¬ification_for_processing).await; - } - Err(error) => { - log::warn!("Failed to process modpack sync notification: {error}"); - } - } - } - } - NotificationWsFrame::ReplayComplete { truncated, .. } => { - if truncated { - bootstrap_notifications(app).await?; - } - } - NotificationWsFrame::Error { error } => { - if error.to_lowercase().contains("lagged") { - bootstrap_notifications(app).await?; - } - return Err(anyhow!("notification websocket error frame: {error}")); - } - } - - Ok(()) -} - -fn build_notification_ws_url( - cursor: &NotificationCursor, - connection_id: &str, -) -> Result { - let base = std::env::var("QUADRANT_API_BASE_URL") - .unwrap_or_else(|_| quadrant_core::account::QNT_BASE_URL.to_string()); - let mut url = Url::parse(&base)?; - match url.scheme() { - "https" => url - .set_scheme("wss") - .map_err(|_| anyhow!("invalid ws scheme"))?, - "http" => url - .set_scheme("ws") - .map_err(|_| anyhow!("invalid ws scheme"))?, - "wss" | "ws" => {} - _ => return Err(anyhow!("unsupported backend scheme")), - } - url.set_path("/api/v3/account/notifications/ws"); - { - let mut query = url.query_pairs_mut(); - if let Some(created_at) = cursor.created_at.as_deref() { - query.append_pair("since", created_at); - } - query.append_pair("replay_limit", &WS_REPLAY_LIMIT.to_string()); - query.append_pair("modpack_sync", "true"); - query.append_pair("connection_id", connection_id); - } - Ok(url) -} - -fn reconnect_delay(attempt: u32) -> Duration { - let secs = match attempt { - 0 => 1, - 1 => 1, - 2 => 2, - 3 => 5, - 4 => 10, - 5 => 30, - _ => 60, - }; - Duration::from_secs(secs) -} - -fn emit_notification_refresh( - app: &AppHandle, - notifications: Vec, -) -> Result<(), anyhow::Error> { - app.emit("refreshNotifications", notifications)?; - Ok(()) -} - -fn emit_synced_modpacks_refresh(app: &AppHandle, modpack_id: &str) -> Result<(), anyhow::Error> { - app.emit(REFRESH_SYNCED_MODPACKS_EVENT, modpack_id)?; - Ok(()) -} - -async fn mark_notification_read_and_snapshot( - app: &AppHandle, - notification_id: &str, -) -> Option> { - let state = app.state::>(); - let mut state = state.lock().await; - state - .notification_state - .mark_read(notification_id) - .then(|| state.notification_state.notifications_for_ui()) -} - -async fn set_reconnect_attempt(app: &AppHandle, attempt: u32) { - let state = app.state::>(); - let mut state = state.lock().await; - state.notification_state.reconnect_attempt = attempt; -} - -async fn mark_modpack_sync_processed(app: &AppHandle, notification: &Notification) { - let state = app.state::>(); - let mut state = state.lock().await; - state - .notification_state - .mark_modpack_sync_processed(notification); -} - -fn load_notification_cursor(app: &AppHandle) -> Result { - let store = app.store("config.json")?; - Ok(NotificationCursor { - created_at: store - .get(NOTIFICATION_CURSOR_CREATED_AT_KEY) - .and_then(|value| value.as_str().map(ToOwned::to_owned)), - notification_id: store - .get(NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY) - .and_then(|value| value.as_str().map(ToOwned::to_owned)), - }) -} - -fn persist_notification_cursor( - app: &AppHandle, - cursor: &NotificationCursor, -) -> Result<(), anyhow::Error> { - let store = app.store("config.json")?; - match &cursor.created_at { - Some(created_at) => { - store.set( - NOTIFICATION_CURSOR_CREATED_AT_KEY, - serde_json::Value::String(created_at.clone()), - ); - } - None => { - store.delete(NOTIFICATION_CURSOR_CREATED_AT_KEY); - } - }; - match &cursor.notification_id { - Some(notification_id) => { - store.set( - NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY, - serde_json::Value::String(notification_id.clone()), - ); - } - None => { - store.delete(NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY); - } - }; - store.save()?; - Ok(()) -} - -async fn maybe_show_native_notification( - app: &AppHandle, - notification: &Notification, - notification_id: &str, - body: Option<&str>, -) -> Result<(), anyhow::Error> { - let body = match body { - Some(body) if !body.is_empty() => body, - _ => return Ok(()), - }; - - if is_modpack_sync_notification(notification) - && !should_show_modpack_sync_notification(app, notification).await - { - return Ok(()); - } - - let store = app.store("config.json")?; - let mut shown_notifications = store - .get(SHOWN_NOTIFICATIONS_KEY) - .and_then(|value| serde_json::from_value::>(value).ok()) - .unwrap_or_default(); - if shown_notifications.iter().any(|id| id == notification_id) { - return Ok(()); - } - - app.notification() - .builder() - .title(NOTIFICATION_TITLE) - .body(body) - .show()?; - - shown_notifications.push(notification_id.to_string()); - store.set( - SHOWN_NOTIFICATIONS_KEY, - serde_json::to_value(shown_notifications)?, - ); - store.save()?; - - Ok(()) -} - -fn notification_simple_message(notification: &Notification) -> Option { - serde_json::from_str::(¬ification.message) - .ok() - .and_then(|message| { - message - .get("simple_message") - .and_then(|value| value.as_str().map(ToOwned::to_owned)) - }) -} - -fn notification_updated_by(notification: &Notification) -> Option { - serde_json::from_str::(¬ification.message) - .ok() - .and_then(|message| { - message - .get("updated_by") - .and_then(|value| value.as_str().map(ToOwned::to_owned)) - }) -} - -fn parse_notification_message(notification: &Notification) -> Option { - serde_json::from_str::(¬ification.message) - .ok() - .filter(|payload| payload.notification_type == "modpack_sync") -} - -fn is_modpack_sync_notification(notification: &Notification) -> bool { - notification.notification_type.as_deref() == Some("modpack_sync") -} - -fn notification_identity_key(notification: &Notification) -> String { - if is_modpack_sync_notification(notification) - && let Some(resource_id) = notification.resource_id.as_deref() - { - return format!("modpack_sync:{resource_id}"); - } - - notification.notification_id.clone() -} - -async fn should_show_modpack_sync_notification( - app: &AppHandle, - notification: &Notification, -) -> bool { - let Ok(config) = app.store("config.json") else { - return true; - }; - if !config - .get("showModpackUpdateNotifications") - .and_then(|value| value.as_bool()) - .unwrap_or(true) - { - return false; - } - - let Some(updated_by) = notification_updated_by(notification) else { - return true; - }; - - let updated_by = normalize_identity(updated_by.as_str()); - if updated_by.is_empty() { - return true; - } - - match quadrant_core::account::id::get_account_info_with_refresh( - &TauriSecretStore, - &get_user_agent(), - env!("QUADRANT_OAUTH2_CLIENT_ID"), - env!("QUADRANT_OAUTH2_CLIENT_SECRET"), - ) - .await - { - Ok(account_info) => { - let identities = [account_info.name, account_info.login]; - !identities - .iter() - .map(|identity| normalize_identity(identity.as_str())) - .any(|identity| !identity.is_empty() && identity == updated_by) - } - Err(error) => { - log::warn!("Failed to resolve current account for notification filtering: {error}"); - true - } - } -} - -fn normalize_identity(identity: &str) -> String { - identity.trim().to_lowercase() -} - -async fn handle_modpack_sync_notification( - app: &AppHandle, - notification: &Notification, -) -> Result<(), anyhow::Error> { - let synced_modpack = resolve_synced_modpack_from_notification(notification).await?; - - let config = app.store("config.json")?; - let auto_quadrant_sync = config - .get("autoQuadrantSync") - .and_then(|value| value.as_bool()) - .unwrap_or(false); - - if auto_quadrant_sync { - let local_modpack = resolve_local_modpack_for_sync(app, &synced_modpack).await?; - let local_modpack = - maybe_backfill_local_modpack_id(app, local_modpack, &synced_modpack).await?; - - if let Some(local_modpack) = local_modpack { - maybe_apply_remote_modpack_update(app, &local_modpack, &synced_modpack).await?; - } - } - - emit_synced_modpacks_refresh(app, &synced_modpack.modpack_id)?; - - Ok(()) -} - -async fn resolve_synced_modpack_from_notification( - notification: &Notification, -) -> Result { - if let Some(payload) = parse_notification_message(notification) { - return Ok(payload.modpack); - } - - let resource_id = notification - .resource_id - .as_deref() - .ok_or_else(|| anyhow!("modpack sync notification missing resource_id"))?; - let mut synced_modpacks = get_synced_modpacks(false, Some(resource_id.to_string())).await?; - synced_modpacks - .drain(..) - .next() - .ok_or_else(|| anyhow!("modpack sync fallback fetch returned no modpack")) -} - -async fn resolve_local_modpack_for_sync( - app: &AppHandle, - synced_modpack: &SyncedModpack, -) -> Result, anyhow::Error> { - let modpacks = quadrant_core::modpacks::get_modpacks(&mc_folder(app)?, true)?; - - if let Some(local_modpack) = modpacks - .iter() - .find(|modpack| modpack.modpack_id.as_deref() == Some(synced_modpack.modpack_id.as_str())) - .cloned() - { - return Ok(Some(local_modpack)); - } - - Ok(modpacks - .into_iter() - .find(|modpack| modpack.last_synced != 0 && modpack.name == synced_modpack.name)) -} - -async fn maybe_backfill_local_modpack_id( - app: &AppHandle, - local_modpack: Option, - synced_modpack: &SyncedModpack, -) -> Result, anyhow::Error> { - let Some(mut local_modpack) = local_modpack else { - return Ok(None); - }; - - if local_modpack.modpack_id.as_deref() == Some(synced_modpack.modpack_id.as_str()) { - return Ok(Some(local_modpack)); - } - - let local_sync_time = u64::try_from(local_modpack.last_synced / 1000).unwrap_or_default(); - persist_sync_metadata( - app, - &local_modpack.name, - local_sync_time, - Some(synced_modpack.modpack_id.as_str()), - )?; - local_modpack.modpack_id = Some(synced_modpack.modpack_id.clone()); - - Ok(Some(local_modpack)) -} - -async fn maybe_apply_remote_modpack_update( - app: &AppHandle, - local_modpack: &LocalModpack, - synced_modpack: &SyncedModpack, -) -> Result<(), anyhow::Error> { - let local_sync_time = local_modpack.last_synced / 1000; - if synced_modpack.last_synced <= local_sync_time { - return Ok(()); - } - - if !begin_modpack_update(app, &local_modpack.name).await { - return Ok(()); - } - - let install_result = install_modpack( - InstalledModpack { - mod_loader: synced_modpack.mod_loader, - name: synced_modpack.name.clone(), - version: synced_modpack.minecraft_version.clone(), - mods: serde_json::from_str(&synced_modpack.mods)?, - }, - app.clone(), - ) - .await; - - if let Err(error) = install_result { - finish_modpack_update(app, &local_modpack.name).await; - return Err(error.into()); - } - - persist_sync_metadata( - app, - &local_modpack.name, - synced_modpack.last_synced as u64, - Some(synced_modpack.modpack_id.as_str()), - )?; - finish_modpack_update(app, &local_modpack.name).await; - - Ok(()) -} - -async fn begin_modpack_update(app: &AppHandle, modpack_name: &str) -> bool { - let state = app.state::>(); - let mut state = state.lock().await; - if state - .updated_modpacks - .iter() - .any(|name| name == modpack_name) - { - return false; - } - - state.updated_modpacks.push(modpack_name.to_string()); - true -} - -async fn finish_modpack_update(app: &AppHandle, modpack_name: &str) { - let state = app.state::>(); - let mut state = state.lock().await; - state.updated_modpacks.retain(|name| name != modpack_name); -} - -async fn sync_remote_settings(app: AppHandle) -> Result<(), anyhow::Error> { - let config = app.store("config.json")?; - let auto_settings_sync = config - .get("syncSettings") - .and_then(|value| value.as_bool()) - .unwrap_or(false); - if !auto_settings_sync { - return Ok(()); - } - - let res = get_quadrant_settings(app.clone()).await; - if let Err(error) = res - && error.to_string() == "Current settings are newer" - { - submit_quadrant_settings(app).await?; - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::{ - Notification, NotificationCursor, NotificationRuntimeState, build_notification_ws_url, - merge_notifications_and_collect_updates, - }; - - fn notification(id: &str, unix: i64, read: bool) -> Notification { - Notification { - notification_id: id.to_string(), - user_id: "u1".to_string(), - notification_type: Some("invite_to_sync".to_string()), - resource_id: None, - message: "{\"simple_message\":\"hello\"}".to_string(), - created_at: format!("2026-03-20T10:{unix:02}:00Z"), - created_at_unix: unix, - read, - } - } - - #[test] - fn merge_notifications_deduplicates_and_orders() { - let mut state = NotificationRuntimeState::default(); - assert!(merge_notifications_and_collect_updates( - &mut state, - vec![notification("n1", 1, false), notification("n2", 2, false)] - ) - .notifications_for_ui - .is_some()); - assert!( - merge_notifications_and_collect_updates(&mut state, vec![notification("n1", 1, false)]) - .notifications_for_ui - .is_none() - ); - - let notifications = state.notifications_for_ui(); - assert_eq!(notifications[0].notification_id, "n2"); - assert_eq!(notifications[1].notification_id, "n1"); - assert_eq!( - state.cursor, - NotificationCursor { - created_at: Some("2026-03-20T10:02:00Z".to_string()), - notification_id: Some("n2".to_string()), - } - ); - } - - #[test] - fn mark_notification_read_local_updates_cached_notification() { - let mut state = NotificationRuntimeState::default(); - let _ = merge_notifications_and_collect_updates(&mut state, vec![notification("n1", 1, false)]); - assert!(state.mark_read("n1")); - assert!(state.by_key.get("n1").unwrap().read); - assert!(!state.mark_read("n1")); - } - - #[test] - fn modpack_sync_notifications_replace_previous_resource_entry() { - let mut state = NotificationRuntimeState::default(); - let first = Notification { - notification_id: "n1".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"hello\"}" - .to_string(), - created_at: "2026-03-20T10:01:00Z".to_string(), - created_at_unix: 1, - read: false, - }; - let second = Notification { - notification_id: "n2".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"updated\"}" - .to_string(), - created_at: "2026-03-20T10:02:00Z".to_string(), - created_at_unix: 2, - read: false, - }; - - assert!( - merge_notifications_and_collect_updates(&mut state, vec![first]) - .notifications_for_ui - .is_some() - ); - assert!( - merge_notifications_and_collect_updates(&mut state, vec![second]) - .notifications_for_ui - .is_some() - ); - - let notifications = state.notifications_for_ui(); - assert_eq!(notifications.len(), 1); - assert_eq!(notifications[0].notification_id, "n2"); - assert_eq!( - state.key_by_notification_id.get("n2").map(String::as_str), - Some("modpack_sync:modpack-1") - ); - } - - #[test] - fn notification_websocket_url_includes_modpack_sync_and_connection_id() { - let cursor = NotificationCursor { - created_at: Some("2026-03-20T10:02:00Z".to_string()), - notification_id: Some("n2".to_string()), - }; - - let url = build_notification_ws_url(&cursor, "client-connection-123").unwrap(); - let query = url.query().unwrap_or_default(); - - assert!(query.contains("since=2026-03-20T10%3A02%3A00Z")); - assert!(query.contains("replay_limit=500")); - assert!(query.contains("modpack_sync=true")); - assert!(query.contains("connection_id=client-connection-123")); - } - - #[test] - fn bootstrap_merge_collects_latest_changed_modpack_sync_per_resource() { - let mut state = NotificationRuntimeState::default(); - let invite = notification("n0", 0, false); - let sync_first = Notification { - notification_id: "n1".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"hello\"}" - .to_string(), - created_at: "2026-03-20T10:01:00Z".to_string(), - created_at_unix: 1, - read: false, - }; - let sync_second = Notification { - notification_id: "n2".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"updated\"}" - .to_string(), - created_at: "2026-03-20T10:02:00Z".to_string(), - created_at_unix: 2, - read: false, - }; - let sync_other = Notification { - notification_id: "n3".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-2".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"other\"}" - .to_string(), - created_at: "2026-03-20T10:03:00Z".to_string(), - created_at_unix: 3, - read: false, - }; - - let outcome = merge_notifications_and_collect_updates( - &mut state, - vec![sync_second.clone(), invite, sync_first, sync_other.clone()], - ); - - assert!(outcome.notifications_for_ui.is_some()); - assert_eq!(outcome.modpack_sync_notifications.len(), 2); - assert_eq!(outcome.modpack_sync_notifications[0].notification_id, "n2"); - assert_eq!(outcome.modpack_sync_notifications[1].notification_id, "n3"); - } - - #[test] - fn bootstrap_merge_retries_unprocessed_modpack_sync_even_when_unchanged() { - let mut state = NotificationRuntimeState::default(); - let sync_notification = Notification { - notification_id: "n1".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"updated\"}" - .to_string(), - created_at: "2026-03-20T10:02:00Z".to_string(), - created_at_unix: 2, - read: false, - }; - - let _ = merge_notifications_and_collect_updates(&mut state, vec![sync_notification.clone()]); - let outcome = merge_notifications_and_collect_updates(&mut state, vec![sync_notification.clone()]); - - assert!(outcome.notifications_for_ui.is_none()); - assert_eq!(outcome.modpack_sync_notifications.len(), 1); - assert_eq!(outcome.modpack_sync_notifications[0].notification_id, "n1"); - } - - #[test] - fn bootstrap_merge_skips_processed_modpack_sync_when_unchanged() { - let mut state = NotificationRuntimeState::default(); - let sync_notification = Notification { - notification_id: "n1".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"updated\"}" - .to_string(), - created_at: "2026-03-20T10:02:00Z".to_string(), - created_at_unix: 2, - read: false, - }; - - let _ = merge_notifications_and_collect_updates(&mut state, vec![sync_notification.clone()]); - state.mark_modpack_sync_processed(&sync_notification); - let outcome = merge_notifications_and_collect_updates(&mut state, vec![sync_notification]); - - assert!(outcome.notifications_for_ui.is_none()); - assert!(outcome.modpack_sync_notifications.is_empty()); - } + app.state::() + .read_notification(notification_id) + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/account/mod.rs b/src-tauri/src/account/mod.rs index b46bf6bd..65b5338a 100644 --- a/src-tauri/src/account/mod.rs +++ b/src-tauri/src/account/mod.rs @@ -1,24 +1,29 @@ +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; + pub mod id; pub mod quadrant_settings_sync; pub mod quadrant_share; pub mod quadrant_sync; -use crate::tauri_adapter::TauriSecretStore; - #[tauri::command] -pub fn set_secret(key: String, value: String) -> Result<(), tauri::Error> { - quadrant_core::account::set_secret(&TauriSecretStore, &key, &value).map_err(tauri::Error::from) +pub fn set_secret(key: String, value: String, app: AppHandle) -> Result<(), tauri::Error> { + app.state::() + .set_secret(key, value) + .map_err(tauri::Error::from) } pub fn get_account_token() -> Result { - quadrant_core::account::get_account_token(&TauriSecretStore) + Err(anyhow::anyhow!("account token access moved to quadrant-host")) } pub fn get_refresh_token() -> Result { - quadrant_core::account::get_refresh_token(&TauriSecretStore) + Err(anyhow::anyhow!("refresh token access moved to quadrant-host")) } #[tauri::command] -pub fn clear_account_token() -> Result<(), tauri::Error> { - quadrant_core::account::clear_account_token(&TauriSecretStore).map_err(tauri::Error::from) +pub fn clear_account_token(app: AppHandle) -> Result<(), tauri::Error> { + app.state::() + .clear_account_token() + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/account/quadrant_settings_sync.rs b/src-tauri/src/account/quadrant_settings_sync.rs index 465554b2..95f0e45e 100644 --- a/src-tauri/src/account/quadrant_settings_sync.rs +++ b/src-tauri/src/account/quadrant_settings_sync.rs @@ -1,28 +1,18 @@ -use tauri::AppHandle; - -use crate::{ - mc_mod::get_user_agent, - tauri_adapter::{TauriSecretStore, TauriSettingsStore}, -}; +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; #[tauri::command] pub async fn get_quadrant_settings(app: AppHandle) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_settings_sync::get_quadrant_settings( - &TauriSettingsStore::new(app, "config.json"), - &TauriSecretStore, - &get_user_agent(), - ) - .await - .map_err(tauri::Error::from) + app.state::() + .get_quadrant_settings() + .await + .map_err(tauri::Error::from) } #[tauri::command] pub async fn submit_quadrant_settings(app: AppHandle) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_settings_sync::submit_quadrant_settings( - &TauriSettingsStore::new(app, "config.json"), - &TauriSecretStore, - &get_user_agent(), - ) - .await - .map_err(tauri::Error::from) + app.state::() + .submit_quadrant_settings() + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/account/quadrant_share.rs b/src-tauri/src/account/quadrant_share.rs index 152d64a8..245456ca 100644 --- a/src-tauri/src/account/quadrant_share.rs +++ b/src-tauri/src/account/quadrant_share.rs @@ -1,56 +1,47 @@ -use tauri::{AppHandle, Emitter}; +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; use tauri_plugin_clipboard_manager::ClipboardExt; pub use quadrant_core::account::quadrant_share::{ QuadrantShareResponse, QuadrantShareSubmission, QuadrantShareSubmissionResponse, }; -use crate::{ - mc_mod::get_user_agent, - modpacks::general::{InstalledModpack, get_modpacks}, - tauri_adapter::{TauriSecretStore, TauriSettingsStore}, -}; - #[tauri::command] pub async fn share_modpack(modpack_name: String, app: AppHandle) -> Result<(), tauri::Error> { - let modpacks = get_modpacks(false, app.clone()).await; - let modpack = modpacks - .iter() - .find(|modpack| modpack.name == modpack_name) - .ok_or_else(|| tauri::Error::from(anyhow::anyhow!("Modpack not found")))?; - share_modpack_raw(InstalledModpack::from(modpack.to_owned()), app).await + let response = app + .state::() + .share_modpack(modpack_name) + .await + .map_err(tauri::Error::from)?; + app.clipboard() + .write_text(response.code.to_string()) + .map_err(|error| tauri::Error::from(anyhow::Error::from(error)))?; + Ok(()) } #[tauri::command] pub async fn share_modpack_raw( - mod_config: InstalledModpack, + mod_config: crate::modpacks::general::InstalledModpack, app: AppHandle, ) -> Result<(), tauri::Error> { - let res = quadrant_core::account::quadrant_share::share_modpack_raw( - &TauriSettingsStore::new(app.clone(), "config.json"), - &TauriSecretStore, - &get_user_agent(), - mod_config, - env!("QUADRANT_API_KEY"), - ) - .await - .map_err(tauri::Error::from)?; - - crate::other::telemetry::send_telemetry(app.clone()).await; - app.emit("quadrantShareSubmission", &res)?; + let response = app + .state::() + .share_modpack_raw(mod_config) + .await + .map_err(tauri::Error::from)?; app.clipboard() - .write_text(res.code.to_string()) - .map_err(|e| tauri::Error::from(anyhow::Error::from(e)))?; + .write_text(response.code.to_string()) + .map_err(|error| tauri::Error::from(anyhow::Error::from(error)))?; Ok(()) } #[tauri::command] -pub async fn get_quadrant_share_modpack(code: String) -> Result { - quadrant_core::account::quadrant_share::get_quadrant_share_modpack( - &get_user_agent(), - env!("QUADRANT_API_KEY"), - code, - ) - .await - .map_err(tauri::Error::from) +pub async fn get_quadrant_share_modpack( + code: String, + app: AppHandle, +) -> Result { + app.state::() + .get_quadrant_share_modpack(code) + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/account/quadrant_sync.rs b/src-tauri/src/account/quadrant_sync.rs index ed3f42a6..ac23ee5a 100644 --- a/src-tauri/src/account/quadrant_sync.rs +++ b/src-tauri/src/account/quadrant_sync.rs @@ -1,40 +1,30 @@ +use quadrant_host::QuadrantHost; use tauri::{AppHandle, Manager}; -use tokio::sync::Mutex; pub use quadrant_core::account::quadrant_sync::{ModpackOwner, SyncedModpack}; -use crate::{ - AppState, - mc_mod::get_user_agent, - modpacks::general::LocalModpack, - tauri_adapter::{TauriSecretStore, mc_folder}, -}; - #[tauri::command] pub async fn get_synced_modpacks( show_owners: bool, modpack_id: Option, + app: AppHandle, ) -> Result, tauri::Error> { - quadrant_core::account::quadrant_sync::get_synced_modpacks( - &TauriSecretStore, - &get_user_agent(), - show_owners, - modpack_id, - ) - .await - .map_err(tauri::Error::from) + app.state::() + .get_synced_modpacks(show_owners, modpack_id) + .await + .map_err(tauri::Error::from) } #[tauri::command] -pub async fn kick_member(modpack_id: String, username: String) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_sync::kick_member( - &TauriSecretStore, - &get_user_agent(), - modpack_id, - username, - ) - .await - .map_err(tauri::Error::from) +pub async fn kick_member( + modpack_id: String, + username: String, + app: AppHandle, +) -> Result<(), tauri::Error> { + app.state::() + .kick_member(modpack_id, username) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -42,27 +32,23 @@ pub async fn invite_member( modpack_id: String, username: String, admin: bool, + app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_sync::invite_member( - &TauriSecretStore, - &get_user_agent(), - modpack_id, - username, - admin, - ) - .await - .map_err(tauri::Error::from) + app.state::() + .invite_member(modpack_id, username, admin) + .await + .map_err(tauri::Error::from) } #[tauri::command] -pub async fn delete_synced_modpack(modpack_id: String) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_sync::delete_synced_modpack( - &TauriSecretStore, - &get_user_agent(), - modpack_id, - ) - .await - .map_err(tauri::Error::from) +pub async fn delete_synced_modpack( + modpack_id: String, + app: AppHandle, +) -> Result<(), tauri::Error> { + app.state::() + .delete_synced_modpack(modpack_id) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -71,27 +57,10 @@ pub async fn sync_modpack( overwrite: bool, app: AppHandle, ) -> Result<(), tauri::Error> { - let connection_id = { - let state = app.state::>(); - let state = state.lock().await; - state.notification_connection_id.clone() - }; - - let timestamp = quadrant_core::account::quadrant_sync::sync_modpack( - &TauriSecretStore, - &get_user_agent(), - modpack.clone(), - overwrite, - Some(connection_id.as_str()), - ) - .await - .map_err(tauri::Error::from)?; - - let persisted_modpack_id = - choose_persisted_modpack_id(&modpack, resolve_submitted_modpack_id(&modpack, timestamp).await?); - persist_sync_metadata(&app, &modpack.name, timestamp as u64, persisted_modpack_id.as_deref()) - .map_err(tauri::Error::from)?; - Ok(()) + app.state::() + .sync_modpack(modpack, overwrite) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -101,109 +70,8 @@ pub async fn answer_invite( answer: bool, app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_sync::answer_invite( - &TauriSecretStore, - &get_user_agent(), - modpack_id, - answer, - ) - .await - .map_err(tauri::Error::from)?; - super::id::read_notification(notification_id, app).await -} - -async fn resolve_submitted_modpack_id( - modpack: &LocalModpack, - timestamp: i64, -) -> Result, tauri::Error> { - let synced_modpacks = quadrant_core::account::quadrant_sync::get_synced_modpacks( - &TauriSecretStore, - &get_user_agent(), - false, - None, - ) - .await - .map_err(tauri::Error::from)?; - - let mut matching = synced_modpacks.into_iter().filter(|synced_modpack| { - synced_modpack.name == modpack.name - && synced_modpack.minecraft_version == modpack.version - && synced_modpack.mod_loader == modpack.mod_loader - && synced_modpack.last_synced == timestamp - }); - - let first = matching.next().map(|modpack| modpack.modpack_id); - if matching.next().is_some() { - return Ok(None); - } - - Ok(first) -} - -fn choose_persisted_modpack_id( - modpack: &LocalModpack, - resolved_modpack_id: Option, -) -> Option { - resolved_modpack_id.or_else(|| modpack.modpack_id.clone()) -} - -pub fn persist_sync_metadata( - app: &AppHandle, - modpack_name: &str, - last_synced: u64, - modpack_id: Option<&str>, -) -> Result<(), anyhow::Error> { - let modpack_folder = mc_folder(app)?.join("modpacks").join(modpack_name); - if !modpack_folder.exists() { - return Ok(()); - } - - quadrant_core::modpacks::set_modpack_sync_date( - &mc_folder(app)?, - last_synced, - modpack_name, - modpack_id, - ) -} - -#[cfg(test)] -mod tests { - use super::choose_persisted_modpack_id; - use crate::modpacks::general::LocalModpack; - use quadrant_core::models::{InstalledMod, ModLoader, ModSource}; - - fn local_modpack(modpack_id: Option<&str>) -> LocalModpack { - LocalModpack { - name: "Better Create".to_string(), - version: "1.20.1".to_string(), - mod_loader: ModLoader::Fabric, - mods: vec![InstalledMod { - id: "abc".to_string(), - source: ModSource::Modrinth, - download_url: "https://example.com/mod.jar".to_string(), - }], - unknown_mods: false, - is_applied: false, - last_synced: 0, - modpack_id: modpack_id.map(ToOwned::to_owned), - } - } - - #[test] - fn choose_persisted_modpack_id_keeps_existing_id_when_lookup_is_empty() { - let modpack = local_modpack(Some("existing-modpack-id")); - - let chosen = choose_persisted_modpack_id(&modpack, None); - - assert_eq!(chosen.as_deref(), Some("existing-modpack-id")); - } - - #[test] - fn choose_persisted_modpack_id_prefers_resolved_id() { - let modpack = local_modpack(Some("existing-modpack-id")); - - let chosen = choose_persisted_modpack_id(&modpack, Some("resolved-modpack-id".to_string())); - - assert_eq!(chosen.as_deref(), Some("resolved-modpack-id")); - } + app.state::() + .answer_invite(modpack_id, notification_id, answer) + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/config/mod.rs b/src-tauri/src/config/mod.rs index 6494d986..53173ca2 100644 --- a/src-tauri/src/config/mod.rs +++ b/src-tauri/src/config/mod.rs @@ -1,17 +1,19 @@ use tauri::AppHandle; +use quadrant_host::QuadrantHost; +use tauri::Manager; pub use quadrant_core::config::{get_config_dir, get_mc_folder}; -use crate::tauri_adapter::TauriSettingsStore; - #[tauri::command] -pub fn get_minecraft_folder() -> Result { - let path = get_mc_folder().map_err(tauri::Error::from)?.unwrap(); +pub fn get_minecraft_folder(app: AppHandle) -> Result { + let path = app + .state::() + .get_minecraft_folder() + .map_err(tauri::Error::from)?; Ok(path.to_string_lossy().to_string()) } #[tauri::command] pub fn init_config(app: AppHandle) -> Result<(), tauri::Error> { - let store = TauriSettingsStore::new(app, "config.json"); - quadrant_core::config::ensure_default_app_config(&store).map_err(tauri::Error::from) + app.state::().init_config().map_err(tauri::Error::from) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index fa83159c..48e6be13 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ -use config::init_config; +use quadrant_host::{QuadrantHost, QuadrantHostOptions}; +use std::path::PathBuf; use tauri::{ Emitter, Manager, menu::{Menu, MenuItem}, @@ -14,8 +15,6 @@ use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_store::StoreExt; #[cfg(feature = "updater")] use tauri_plugin_updater::UpdaterExt; -#[cfg(feature = "quadrant_id")] -use uuid::Uuid; #[allow(dead_code)] // This is used in the Quadrant ID feature pub(crate) const QNT_BASE_URL: &str = "https://api.usequadrant.dev/api/v3"; @@ -35,10 +34,25 @@ pub struct AppState { pub is_update_enabled: bool, pub update: Option, pub update_bytes: Vec, - #[cfg(feature = "quadrant_id")] - pub notification_connection_id: String, - #[cfg(feature = "quadrant_id")] - pub notification_state: account::id::NotificationRuntimeState, +} + +fn build_quadrant_host(app: &tauri::AppHandle) -> Result { + let data_dir = app + .path() + .app_data_dir() + .ok() + .or_else(|| quadrant_core::config::get_config_dir().ok().flatten()) + .unwrap_or_else(|| PathBuf::from(".")); + let mut options = QuadrantHostOptions::new( + data_dir, + env!("QUADRANT_OAUTH2_CLIENT_ID"), + env!("QUADRANT_OAUTH2_CLIENT_SECRET"), + env!("QUADRANT_API_KEY"), + ); + options.api_base_url = std::env::var("QUADRANT_API_BASE_URL").ok(); + options.app_version = app.package_info().version.to_string(); + options.os_name = tauri_plugin_os::platform().to_string().to_uppercase(); + QuadrantHost::new(options) } #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -48,14 +62,10 @@ pub async fn run() { log::info!("Initializing Tauri..."); let mut builder = tauri::Builder::default().manage(Mutex::new(AppState { - is_update_enabled: false, updated_modpacks: vec![], + is_update_enabled: false, update: None, update_bytes: vec![], - #[cfg(feature = "quadrant_id")] - notification_connection_id: Uuid::now_v7().to_string(), - #[cfg(feature = "quadrant_id")] - notification_state: account::id::NotificationRuntimeState::default(), })); #[cfg(desktop)] @@ -105,7 +115,23 @@ pub async fn run() { .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_clipboard_manager::init()) .setup(|app| { - init_config(app.handle().clone())?; + let host = build_quadrant_host(&app.handle().clone())?; + let mut host_events = host.subscribe_events(); + let app_handle = app.handle().clone(); + tauri::async_runtime::spawn(async move { + loop { + match host_events.recv().await { + Ok(event) => { + let _ = app_handle.emit(&event.event, event.payload.clone()); + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => { + log::warn!("Dropped {skipped} host events while forwarding to Tauri"); + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, + } + } + }); + app.manage(host.clone()); log::info!("Initializing app...\nInitializing config..."); let mut autoupdate = true; @@ -207,18 +233,21 @@ pub async fn run() { } #[cfg(feature = "telemetry")] { - let handle = app.handle().clone(); log::info!("Initializing telemetry..."); + let host = host.clone(); tauri::async_runtime::spawn(async move { - other::telemetry::send_telemetry(handle.clone().to_owned()).await; + let _ = host.send_telemetry().await; }); } #[cfg(feature = "quadrant_id")] { log::info!("Starting Quadrant notification and sync workers..."); - let app_handle = app.handle().clone(); - account::id::start_notification_worker(app_handle.clone()); - account::id::start_settings_sync_worker(app_handle); + let worker_host = host.clone(); + tauri::async_runtime::spawn(async move { + if let Err(error) = worker_host.start_background_workers().await { + log::error!("Failed to start Quadrant host workers: {error}"); + } + }); } log::info!("Initializing tray..."); let tray = app.tray_by_id("main"); @@ -266,8 +295,9 @@ pub async fn run() { } log::info!("Updating Minecraft versions..."); + let version_host = host.clone(); let _task = tokio::task::spawn(async move { - let _res = mc_mod::get_versions().await; + let _res = version_host.get_versions().await; match _res { Ok(_) => {} Err(e) => { @@ -347,6 +377,10 @@ pub async fn run() { account::quadrant_sync::answer_invite, #[cfg(feature = "quadrant_id")] account::quadrant_share::get_quadrant_share_modpack, + #[cfg(feature = "quadrant_id")] + account::quadrant_settings_sync::get_quadrant_settings, + #[cfg(feature = "quadrant_id")] + account::quadrant_settings_sync::submit_quadrant_settings, ]); builder diff --git a/src-tauri/src/mc_mod/curseforge.rs b/src-tauri/src/mc_mod/curseforge.rs index 78abb3dd..527b82d4 100644 --- a/src-tauri/src/mc_mod/curseforge.rs +++ b/src-tauri/src/mc_mod/curseforge.rs @@ -1,24 +1,36 @@ +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; + use crate::mc_mod::{GetModArgs, Mod}; pub use quadrant_core::mc_mod::curseforge::ModFile; #[tauri::command] -pub async fn get_mod_curseforge(args: GetModArgs) -> Result { - quadrant_core::mc_mod::curseforge::get_mod_curseforge(args) +pub async fn get_mod_curseforge(args: GetModArgs, app: AppHandle) -> Result { + app.state::() + .get_mod_curseforge(args) .await .map_err(tauri::Error::from) } #[tauri::command] -pub async fn get_mod_owners_curseforge(id: String) -> Result, tauri::Error> { - quadrant_core::mc_mod::curseforge::get_mod_owners_curseforge(id) +pub async fn get_mod_owners_curseforge( + id: String, + app: AppHandle, +) -> Result, tauri::Error> { + app.state::() + .get_mod_owners_curseforge(id) .await .map_err(tauri::Error::from) } #[tauri::command] -pub async fn get_mod_deps_curseforge(id: String) -> Result, tauri::Error> { - quadrant_core::mc_mod::curseforge::get_mod_deps_curseforge(id) +pub async fn get_mod_deps_curseforge( + id: String, + app: AppHandle, +) -> Result, tauri::Error> { + app.state::() + .get_mod_deps_curseforge(id) .await .map_err(tauri::Error::from) } diff --git a/src-tauri/src/mc_mod/mod.rs b/src-tauri/src/mc_mod/mod.rs index 4c1411fd..c89b1e38 100644 --- a/src-tauri/src/mc_mod/mod.rs +++ b/src-tauri/src/mc_mod/mod.rs @@ -1,6 +1,5 @@ -use anyhow::anyhow; -use tauri::AppHandle; -use tauri_plugin_store::StoreExt; +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; pub use quadrant_core::mc_mod::{ GetModArgs, GlobalSearchModsArgs, IdentifiedMod, MinecraftVersion, Mod, ModType, @@ -8,18 +7,14 @@ pub use quadrant_core::mc_mod::{ }; pub use quadrant_core::models::{InstalledMod, ModSource}; -use crate::{ - modpacks::general::get_modpacks, - tauri_adapter::{TauriEventSink, TauriSettingsStore, mc_folder}, -}; - #[cfg(feature = "curseforge")] pub mod curseforge; pub mod modrinth; #[tauri::command] -pub async fn get_versions() -> Result, tauri::Error> { - quadrant_core::mc_mod::get_versions() +pub async fn get_versions(app: AppHandle) -> Result, tauri::Error> { + app.state::() + .get_versions() .await .map_err(tauri::Error::from) } @@ -32,26 +27,10 @@ pub async fn check_mod_updates( modpack_name: String, app: AppHandle, ) -> Result, tauri::Error> { - let modpacks = get_modpacks(false, app.clone()).await; - let modpack = modpacks - .into_iter() - .find(|modpack| modpack.name == modpack_name) - .ok_or_else(|| tauri::Error::from(anyhow!("Modpack not found")))?; - let config = app.store("config.json").map_err(anyhow::Error::from)?; - let show_unupgradeable_mods = config - .get("showUnupgradeableMods") - .and_then(|value| value.as_bool()) - .unwrap_or(false); - - quadrant_core::mc_mod::check_mod_updates( - mod_to_update, - minecraft_version, - mod_loader, - modpack, - show_unupgradeable_mods, - ) - .await - .map_err(tauri::Error::from) + app.state::() + .check_mod_updates(mod_to_update, minecraft_version, mod_loader, modpack_name) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -59,7 +38,8 @@ pub async fn search_mods( args: GlobalSearchModsArgs, app: AppHandle, ) -> Result, tauri::Error> { - quadrant_core::mc_mod::search_mods(args, &TauriSettingsStore::new(app, "config.json")) + app.state::() + .search_mods(args) .await .map_err(tauri::Error::from) } @@ -72,28 +52,21 @@ pub async fn install_mod( source: ModSource, modpack: Option, mod_type: ModType, - #[allow(unused_variables)] file_id: Option, + file_id: Option, app: AppHandle, ) -> Result<(), tauri::Error> { - let existing_modpacks = get_modpacks(false, app.clone()).await; - let updated_modpack = quadrant_core::mc_mod::install_mod( - &mc_folder(&app).map_err(tauri::Error::from)?, - &existing_modpacks, - &TauriSettingsStore::new(app.clone(), "config.json"), - &TauriEventSink::new(app.clone()), - id, - minecraft_version, - mod_loader, - source, - modpack, - mod_type, - file_id, - ) - .await - .map_err(tauri::Error::from)?; - - maybe_auto_sync_updated_modpack(&app, updated_modpack).await?; - Ok(()) + app.state::() + .install_mod( + id, + minecraft_version, + mod_loader, + source, + modpack, + mod_type, + file_id, + ) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -105,22 +78,10 @@ pub async fn install_remote_file( id: String, app: AppHandle, ) -> Result<(), tauri::Error> { - let existing_modpacks = get_modpacks(false, app.clone()).await; - let updated_modpack = quadrant_core::mc_mod::install_remote_file( - &mc_folder(&app).map_err(tauri::Error::from)?, - &existing_modpacks, - &TauriEventSink::new(app.clone()), - file, - mod_type, - modpack, - source, - id, - ) - .await - .map_err(tauri::Error::from)?; - - maybe_auto_sync_updated_modpack(&app, updated_modpack).await?; - Ok(()) + app.state::() + .install_remote_file(file, mod_type, modpack, source, id) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -128,58 +89,13 @@ pub async fn identify_modpack( modpack: String, app: AppHandle, ) -> Result, tauri::Error> { - let config = app.store("config.json").map_err(anyhow::Error::from)?; - let curseforge_enabled = config - .get("curseforge") - .and_then(|value| value.as_bool()) - .unwrap_or(false); - let modrinth_enabled = config - .get("modrinth") - .and_then(|value| value.as_bool()) - .unwrap_or(false); - - quadrant_core::mc_mod::identify_modpack( - &mc_folder(&app).map_err(tauri::Error::from)?, - modpack, - curseforge_enabled, - modrinth_enabled, - ) - .await - .map_err(tauri::Error::from) + app.state::() + .identify_modpack(modpack) + .await + .map_err(tauri::Error::from) } #[tauri::command] pub fn get_user_url(username: String, source: ModSource) -> String { quadrant_core::mc_mod::get_user_url(username, source) } - -#[cfg(feature = "quadrant_id")] -async fn maybe_auto_sync_updated_modpack( - app: &AppHandle, - updated_modpack: Option, -) -> Result<(), tauri::Error> { - let Some(modpack) = updated_modpack else { - return Ok(()); - }; - - let config = app.store("config.json").map_err(anyhow::Error::from)?; - let auto_sync = config - .get("autoQuadrantSync") - .and_then(|value| value.as_bool()) - .unwrap_or(false); - - if modpack.last_synced != 0 && auto_sync { - use crate::account::quadrant_sync::sync_modpack; - sync_modpack(modpack, true, app.clone()).await?; - } - - Ok(()) -} - -#[cfg(not(feature = "quadrant_id"))] -async fn maybe_auto_sync_updated_modpack( - _app: &AppHandle, - _updated_modpack: Option, -) -> Result<(), tauri::Error> { - Ok(()) -} diff --git a/src-tauri/src/mc_mod/modrinth.rs b/src-tauri/src/mc_mod/modrinth.rs index f947302f..ed5d2505 100644 --- a/src-tauri/src/mc_mod/modrinth.rs +++ b/src-tauri/src/mc_mod/modrinth.rs @@ -1,24 +1,33 @@ +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; + use crate::mc_mod::{GetModArgs, Mod}; pub use quadrant_core::mc_mod::modrinth::{ModrinthFile, ModrinthHash}; #[tauri::command] -pub async fn get_mod_modrinth(args: GetModArgs) -> Result { - quadrant_core::mc_mod::modrinth::get_mod_modrinth(args) +pub async fn get_mod_modrinth(args: GetModArgs, app: AppHandle) -> Result { + app.state::() + .get_mod_modrinth(args) .await .map_err(tauri::Error::from) } #[tauri::command] -pub async fn get_mod_owners_modrinth(id: String) -> Result, tauri::Error> { - quadrant_core::mc_mod::modrinth::get_mod_owners_modrinth(id) +pub async fn get_mod_owners_modrinth( + id: String, + app: AppHandle, +) -> Result, tauri::Error> { + app.state::() + .get_mod_owners_modrinth(id) .await .map_err(tauri::Error::from) } #[tauri::command] -pub async fn get_mod_deps_modrinth(id: String) -> Result, tauri::Error> { - quadrant_core::mc_mod::modrinth::get_mod_deps_modrinth(id) +pub async fn get_mod_deps_modrinth(id: String, app: AppHandle) -> Result, tauri::Error> { + app.state::() + .get_mod_deps_modrinth(id) .await .map_err(tauri::Error::from) } diff --git a/src-tauri/src/modpacks/general.rs b/src-tauri/src/modpacks/general.rs index 2fe7b509..89e4acde 100644 --- a/src-tauri/src/modpacks/general.rs +++ b/src-tauri/src/modpacks/general.rs @@ -1,11 +1,12 @@ use std::path::{Path, PathBuf}; +use quadrant_host::QuadrantHost; use quadrant_core::ports::Shell; -use tauri::AppHandle; +use tauri::{AppHandle, Manager}; pub use quadrant_core::models::{InstalledModpack, LocalModpack, ModLoader, SyncInfo}; -use crate::tauri_adapter::{TauriEventSink, TauriSettingsStore, TauriShell, mc_folder}; +use crate::tauri_adapter::TauriShell; pub fn get_modpack_path(mc_folder: &Path, modpack: &LocalModpack) -> PathBuf { quadrant_core::models::modpack_path(mc_folder, &modpack.name) @@ -13,16 +14,21 @@ pub fn get_modpack_path(mc_folder: &Path, modpack: &LocalModpack) -> PathBuf { #[tauri::command] pub async fn get_modpacks(hide_free: bool, app: AppHandle) -> Vec { - quadrant_core::modpacks::get_modpacks(&mc_folder(&app).unwrap(), hide_free).unwrap_or_default() + app.state::() + .get_modpacks(hide_free) + .await + .unwrap_or_default() } #[tauri::command] pub fn frontend_apply_modpack(name: String, app: AppHandle) -> Result<(), tauri::Error> { - apply_modpack(name, app).map_err(tauri::Error::from) + app.state::() + .frontend_apply_modpack(name) + .map_err(tauri::Error::from) } pub fn apply_modpack(name: String, app: AppHandle) -> Result<(), anyhow::Error> { - quadrant_core::modpacks::apply_modpack(&mc_folder(&app)?, &name) + app.state::().frontend_apply_modpack(name) } #[tauri::command] @@ -30,14 +36,10 @@ pub async fn install_modpack( mod_config: InstalledModpack, app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::modpacks::install_modpack( - &mc_folder(&app).map_err(tauri::Error::from)?, - mod_config, - &TauriSettingsStore::new(app.clone(), "config.json"), - &TauriEventSink::new(app), - ) - .await - .map_err(tauri::Error::from) + app.state::() + .install_modpack(mod_config) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -47,13 +49,9 @@ pub async fn set_modpack_sync_date( modpack_id: Option, app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::modpacks::set_modpack_sync_date( - &mc_folder(&app).map_err(tauri::Error::from)?, - time, - &modpack, - modpack_id.as_deref(), - ) - .map_err(tauri::Error::from) + app.state::() + .set_modpack_sync_date(time, modpack, modpack_id) + .map_err(tauri::Error::from) } #[tauri::command] @@ -66,11 +64,8 @@ pub async fn export_modpack(modpack: String, app: AppHandle) -> Result<(), tauri return Ok(()); }; - quadrant_core::modpacks::export_modpack_to( - &mc_folder(&app).map_err(tauri::Error::from)?, - &modpack, - &destination, - &TauriEventSink::new(app), - ) - .map_err(tauri::Error::from) + app.state::() + .export_modpack_to(modpack, destination) + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/modpacks/manage_modpack.rs b/src-tauri/src/modpacks/manage_modpack.rs index 0c23cc13..e3faf406 100644 --- a/src-tauri/src/modpacks/manage_modpack.rs +++ b/src-tauri/src/modpacks/manage_modpack.rs @@ -1,11 +1,10 @@ -use anyhow::anyhow; +use quadrant_host::QuadrantHost; use quadrant_core::ports::Shell; -use tauri::AppHandle; -use tauri_plugin_store::StoreExt; +use tauri::{AppHandle, Manager}; -use crate::tauri_adapter::{TauriShell, mc_folder}; +use crate::tauri_adapter::TauriShell; -use super::general::{ModLoader, get_modpacks}; +use super::general::ModLoader; #[tauri::command] pub async fn delete_mod( @@ -13,19 +12,10 @@ pub async fn delete_mod( mod_id: String, app: AppHandle, ) -> Result<(), tauri::Error> { - let modpacks = get_modpacks(false, app.clone()).await; - let modpack = quadrant_core::modpacks::delete_mod( - &mc_folder(&app).map_err(tauri::Error::from)?, - &modpacks, - &modpack_name, - &mod_id, - ) - .map_err(tauri::Error::from)?; - - #[cfg(feature = "quadrant_id")] - maybe_auto_sync(&app, modpack).await?; - - Ok(()) + app.state::() + .delete_mod(modpack_name, mod_id) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -36,21 +26,10 @@ pub async fn update_modpack( mod_loader: Option, app: AppHandle, ) -> Result<(), tauri::Error> { - let modpacks = get_modpacks(false, app.clone()).await; - let modpack = quadrant_core::modpacks::update_modpack( - &mc_folder(&app).map_err(tauri::Error::from)?, - &modpacks, - &modpack_source, - name, - version, - mod_loader, - ) - .map_err(tauri::Error::from)?; - - #[cfg(feature = "quadrant_id")] - maybe_auto_sync(&app, modpack).await?; - - Ok(()) + app.state::() + .update_modpack(modpack_source, name, version, mod_loader) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -60,33 +39,27 @@ pub async fn create_modpack( mod_loader: ModLoader, app: AppHandle, ) -> Result<(), tauri::Error> { - let modpacks = get_modpacks(false, app.clone()).await; - quadrant_core::modpacks::create_modpack( - &mc_folder(&app).map_err(tauri::Error::from)?, - &modpacks, - &name, - &version, - mod_loader, - ) - .map_err(tauri::Error::from) + app.state::() + .create_modpack(name, version, mod_loader) + .await + .map_err(tauri::Error::from) } #[tauri::command] pub async fn delete_modpack(name: String, app: AppHandle) -> Result<(), tauri::Error> { - let modpacks = get_modpacks(false, app.clone()).await; - quadrant_core::modpacks::delete_modpack( - &mc_folder(&app).map_err(tauri::Error::from)?, - &modpacks, - &name, - ) - .map_err(tauri::Error::from) + app.state::() + .delete_modpack(name) + .await + .map_err(tauri::Error::from) } #[tauri::command] pub async fn open_modpacks_folder(app: AppHandle) -> Result<(), tauri::Error> { - let modpacks_path = mc_folder(&app) + let modpacks_path = app + .state::() + .get_modpacks_folder() .map_err(tauri::Error::from)? - .join("modpacks"); + ; TauriShell::new(app) .open_path(&modpacks_path) .map_err(tauri::Error::from) @@ -98,32 +71,8 @@ pub async fn register_mod( modpack: String, app: AppHandle, ) -> Result<(), tauri::Error> { - let modpacks = get_modpacks(false, app.clone()).await; - quadrant_core::modpacks::register_mod( - &mc_folder(&app).map_err(tauri::Error::from)?, - &modpacks, - mod_, - &modpack, - ) - .map_err(tauri::Error::from) -} - -#[cfg(feature = "quadrant_id")] -async fn maybe_auto_sync( - app: &AppHandle, - modpack: quadrant_core::models::LocalModpack, -) -> Result<(), tauri::Error> { - let config = app.store("config.json").map_err(anyhow::Error::from)?; - let auto_sync = config - .get("autoQuadrantSync") - .ok_or_else(|| anyhow!("autoQuadrantSync is not configured"))? - .as_bool() - .unwrap_or_default(); - - if modpack.last_synced != 0 && auto_sync { - use crate::account::quadrant_sync::sync_modpack; - sync_modpack(modpack, true, app.clone()).await?; - } - - Ok(()) + app.state::() + .register_mod(mod_, modpack) + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/other/rss.rs b/src-tauri/src/other/rss.rs index ed717f8d..a582a9e7 100644 --- a/src-tauri/src/other/rss.rs +++ b/src-tauri/src/other/rss.rs @@ -1,8 +1,12 @@ +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; + pub use quadrant_core::models::Article; #[tauri::command] -pub async fn get_news() -> Result, tauri::Error> { - quadrant_core::rss::get_news() +pub async fn get_news(app: AppHandle) -> Result, tauri::Error> { + app.state::() + .get_news() .await .map_err(tauri::Error::from) } diff --git a/src-tauri/src/other/telemetry.rs b/src-tauri/src/other/telemetry.rs index b1a52b3f..ca973548 100644 --- a/src-tauri/src/other/telemetry.rs +++ b/src-tauri/src/other/telemetry.rs @@ -1,37 +1,21 @@ -use tauri::AppHandle; - -use crate::{mc_mod::get_user_agent, tauri_adapter::TauriSettingsStore}; +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; pub use quadrant_core::telemetry::AppInfo; pub async fn get_telemetry_info(app: AppHandle) -> AppInfo { - quadrant_core::telemetry::get_telemetry_info( - &TauriSettingsStore::new(app.clone(), "config.json"), - app.package_info().version.to_string(), - tauri_plugin_os::platform().to_string().to_uppercase(), - ) - .await - .expect("failed to gather telemetry info") + app.state::() + .get_telemetry_info() + .await + .expect("failed to gather telemetry info") } #[tauri::command] pub async fn send_telemetry(app: AppHandle) { - let _ = quadrant_core::telemetry::send_telemetry( - &TauriSettingsStore::new(app.clone(), "config.json"), - &get_user_agent(), - app.package_info().version.to_string(), - tauri_plugin_os::platform().to_string().to_uppercase(), - env!("QUADRANT_API_KEY"), - ) - .await; + let _ = app.state::().send_telemetry().await; } #[tauri::command] pub async fn remove_telemetry(app: AppHandle) { - let _ = quadrant_core::telemetry::remove_telemetry( - &TauriSettingsStore::new(app, "config.json"), - &get_user_agent(), - env!("QUADRANT_API_KEY"), - ) - .await; + let _ = app.state::().remove_telemetry().await; } From 2a265def9116976ffe814323b17829b2119bd85e Mon Sep 17 00:00:00 2001 From: Demir Yerli Date: Sat, 4 Apr 2026 18:27:34 +0300 Subject: [PATCH 02/25] Add dual Electron and Tauri desktop runtime support --- .github/workflows/release.yml | 51 + .github/workflows/validate-desktop.yml | 53 ++ .gitignore | 3 + DEVELOP.md | 8 +- bun.lock | 891 ++++++++++++++---- electron-builder.json | 59 ++ electron/main.mjs | 603 ++++++++++++ electron/preload.mjs | 116 +++ package.json | 31 +- packages/quadrant-node/index.js | 146 +-- packages/quadrant-node/index.test.js | 95 -- packages/quadrant-node/package.json | 7 +- scripts/build-electron.mjs | 22 + scripts/build-napi.mjs | 69 ++ scripts/dev-electron.mjs | 93 ++ scripts/package-electron.mjs | 19 + scripts/write-electron-runtime-config.mjs | 21 + src-tauri/crates/quadrant-napi/index.js | 36 +- src/App.css | 12 + src/App.tsx | 49 +- .../Pages/AccountPage/AccountPage.tsx | 21 +- .../Pages/AccountPage/RegisterPages/Step1.tsx | 4 +- .../Pages/AccountPage/RegisterPages/Step2.tsx | 4 +- src/components/Pages/ApplyPage/Apply.tsx | 6 +- .../CurrentModpackPage/CurrentModpackPage.tsx | 7 +- .../Pages/ModInstallPage/ModInstallPage.tsx | 12 +- .../Pages/SearchPage/SearchPage.tsx | 9 +- .../Pages/SettingsPage/Settings.tsx | 26 +- .../Pages/ShareSyncPage/SharePage.tsx | 6 +- .../Pages/ShareSyncPage/ShareSyncPage.tsx | 2 +- .../Pages/ShareSyncPage/SyncPage.tsx | 2 +- .../SyncedModpack/SyncedModpack.tsx | 2 +- src/components/shared/Mod.tsx | 16 +- src/components/shared/Notifications.tsx | 5 +- src/desktop/browser.ts | 122 +++ src/desktop/contract.ts | 101 ++ src/desktop/electron.ts | 165 ++++ src/desktop/index.ts | 159 ++++ src/desktop/runtime.ts | 23 + src/desktop/tauri.ts | 182 ++++ src/desktop/types.ts | 123 +++ src/tools.ts | 54 +- src/vite-env.d.ts | 10 + 43 files changed, 2967 insertions(+), 478 deletions(-) create mode 100644 .github/workflows/validate-desktop.yml create mode 100644 electron-builder.json create mode 100644 electron/main.mjs create mode 100644 electron/preload.mjs delete mode 100644 packages/quadrant-node/index.test.js create mode 100644 scripts/build-electron.mjs create mode 100644 scripts/build-napi.mjs create mode 100644 scripts/dev-electron.mjs create mode 100644 scripts/package-electron.mjs create mode 100644 scripts/write-electron-runtime-config.mjs create mode 100644 src/desktop/browser.ts create mode 100644 src/desktop/contract.ts create mode 100644 src/desktop/electron.ts create mode 100644 src/desktop/index.ts create mode 100644 src/desktop/runtime.ts create mode 100644 src/desktop/tauri.ts create mode 100644 src/desktop/types.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 500adf6a..c62ba79d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,6 +74,57 @@ jobs: tauriScript: 'bun tauri' tagName: ${{github.ref_name}} + publish-electron: + permissions: + contents: write + strategy: + fail-fast: false + matrix: + include: + - platform: "blacksmith-4vcpu-windows-2025" + os: "windows" + - platform: "blacksmith-4vcpu-ubuntu-2404" + os: "linux" + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - name: install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: install dependencies (ubuntu only) + if: matrix.os == 'linux' + run: | + sudo apt update + sudo apt install -y libsecret-1-dev libarchive-tools rpm + + - name: install frontend dependencies (bun) + run: bun install + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: "./src-tauri -> target" + + - name: Package Electron app + env: + QUADRANT_API_KEY: ${{ secrets.QUADRANT_API_KEY }} + QUADRANT_OAUTH2_CLIENT_ID: ${{ secrets.QUADRANT_OAUTH2_CLIENT_ID }} + QUADRANT_OAUTH2_CLIENT_SECRET: ${{ secrets.QUADRANT_OAUTH2_CLIENT_SECRET }} + QUADRANT_API_BASE_URL: ${{ vars.QUADRANT_API_BASE_URL }} + run: bun run package:electron + + - name: Upload Electron assets to release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ github.ref_name }} + shell: bash + run: | + mapfile -d '' files < <(find dist-electron -maxdepth 1 -type f -print0) + gh release upload "$TAG_NAME" "${files[@]}" --clobber + sync_flathub: name: Sync Flathub needs: publish-tauri diff --git a/.github/workflows/validate-desktop.yml b/.github/workflows/validate-desktop.yml new file mode 100644 index 00000000..f8996731 --- /dev/null +++ b/.github/workflows/validate-desktop.yml @@ -0,0 +1,53 @@ +name: Validate Desktop + +on: + pull_request: + push: + branches: + - main + - master + - release + +jobs: + validate: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Install Linux desktop dependencies + run: | + sudo apt update + sudo apt install -y libwebkit2gtk-4.1-dev xdg-utils libappindicator3-dev librsvg2-dev patchelf libsecret-1-dev libarchive-tools rpm + + - name: Install frontend dependencies + run: bun install + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: "./src-tauri -> target" + + - name: Build shared renderer + run: bun run build + + - name: Build native addon + run: bun run build:napi + + - name: Build Electron shell + env: + QUADRANT_API_KEY: dev + QUADRANT_OAUTH2_CLIENT_ID: dev + QUADRANT_OAUTH2_CLIENT_SECRET: dev + run: node scripts/build-electron.mjs + + - name: Build Tauri shell + env: + QUADRANT_API_KEY: dev + QUADRANT_OAUTH2_CLIENT_ID: dev + QUADRANT_OAUTH2_CLIENT_SECRET: dev + run: bun tauri build --no-bundle diff --git a/.gitignore b/.gitignore index 25a1af32..b90e8dab 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ lerna-debug.log* node_modules dist +dist-electron dist-ssr *.local @@ -39,3 +40,5 @@ src-tauri/versoview target/ AppxContent AppxBundles +electron/runtime-config.generated.json +packages/quadrant-node/native/ diff --git a/DEVELOP.md b/DEVELOP.md index 476a3c72..0d60d695 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -4,4 +4,10 @@ You can develop the Quadrant Next client by: - Installing the needed dependencies for [Tauri](https://tauri.app/start/prerequisites/) and [Rust](https://www.rust-lang.org/). If you're on Linux, you'll also need libsecret-1-dev. - Then install bun and run `bun install` in the root directory of the project. -- After that in order to run the app without any of the proprietary features, you can run `bun tauri dev -- -- --no-default-features`. +- Renderer-only development remains available through `bun run dev`. +- Tauri development is available through `bun run dev:tauri`. +- Electron development is available through `bun run dev:electron`. +- To build the shared renderer only, run `bun run build`. +- To build the native Electron addon explicitly, run `bun run build:napi`. +- To package the Electron app, run `bun run package:electron`. +- To build the Tauri app without any of the proprietary features, run `bun tauri dev -- -- --no-default-features`. diff --git a/bun.lock b/bun.lock index 7b5ab259..99d0bbef 100644 --- a/bun.lock +++ b/bun.lock @@ -1,12 +1,13 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { "name": "quadrant-next", "dependencies": { "@fabianlars/tauri-plugin-oauth": "^2.0.0", "@headlessui/react": "^2.2.9", + "@quadrant/quadrant-node": "file:packages/quadrant-node", "@tauri-apps/api": "^2.10.1", "@tauri-apps/plugin-autostart": "~2.5.1", "@tauri-apps/plugin-cli": "~2.4.1", @@ -20,16 +21,16 @@ "@tauri-apps/plugin-os": "~2.3.2", "@tauri-apps/plugin-store": "2.4.2", "@tauri-apps/plugin-updater": "2.10.0", - "i18next": "26.0.1", + "i18next": "26.0.3", "motion": "12.38.0", "react": "19.2.4", "react-dom": "19.2.4", - "react-i18next": "^17.0.1", + "react-i18next": "^17.0.2", "react-icons": "^5.6.0", }, "devDependencies": { "@choochmeque/tauri-windows-bundle": "^0.1.17", - "@eslint/css": "^1.0.0", + "@eslint/css": "^1.1.0", "@eslint/js": "^10.0.1", "@eslint/json": "^1.2.0", "@tailwindcss/postcss": "4.2.2", @@ -39,7 +40,11 @@ "@vitejs/plugin-react": "^6.0.1", "autoprefixer": "^10.4.27", "babel-plugin-react-compiler": "1.0.0", - "eslint": "^10.1.0", + "chokidar": "^5.0.0", + "electron": "^41.1.1", + "electron-builder": "^26.8.1", + "electron-updater": "^6.8.3", + "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.2", @@ -52,70 +57,94 @@ "prettier-eslint": "^16.4.2", "tailwindcss": "4.2.2", "typescript": "6.0.2", - "typescript-eslint": "^8.57.2", + "typescript-eslint": "^8.58.0", "vite": "^8.0.3", }, }, + "packages/quadrant-node": { + "name": "@quadrant/quadrant-node", + "version": "26.4.0", + }, }, "trustedDependencies": [ "@tailwindcss/oxide", ], "packages": { + "7zip-bin": ["7zip-bin@5.2.0", "", {}, "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A=="], + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - "@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="], + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - "@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], - "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + "@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=="], - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], - "@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="], + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], - "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - "@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], - "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], "@choochmeque/tauri-windows-bundle": ["@choochmeque/tauri-windows-bundle@0.1.17", "", { "dependencies": { "commander": "^14.0.3", "glob": "^13.0.6", "image-js": "^1.4.0" }, "bin": { "tauri-windows-bundle": "dist/cli.js" } }, "sha512-fXlizNfkNquW2kdnXdHA/nqMljK4cE/PuT6HA0Wde4InUSJU0s5z5PtaQhkcAp76MaGCIWOIhFZetZX2YVaoWg=="], - "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + "@develar/schema-utils": ["@develar/schema-utils@2.6.5", "", { "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } }, "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig=="], + + "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], + + "@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="], + + "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], + + "@electron/notarize": ["@electron/notarize@2.5.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", "promise-retry": "^2.0.1" } }, "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A=="], + + "@electron/osx-sign": ["@electron/osx-sign@1.3.3", "", { "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", "fs-extra": "^10.0.0", "isbinaryfile": "^4.0.8", "minimist": "^1.2.6", "plist": "^3.0.5" }, "bin": { "electron-osx-flat": "bin/electron-osx-flat.js", "electron-osx-sign": "bin/electron-osx-sign.js" } }, "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg=="], + + "@electron/rebuild": ["@electron/rebuild@4.0.3", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", "got": "^11.7.0", "graceful-fs": "^4.2.11", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^11.2.0", "ora": "^5.1.0", "read-binary-file-arch": "^1.0.6", "semver": "^7.3.5", "tar": "^7.5.6", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA=="], + + "@electron/universal": ["@electron/universal@2.0.3", "", { "dependencies": { "@electron/asar": "^3.3.1", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "dir-compare": "^4.2.0", "fs-extra": "^11.1.1", "minimatch": "^9.0.3", "plist": "^3.1.0" } }, "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g=="], - "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + "@electron/windows-sign": ["@electron/windows-sign@1.2.2", "", { "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", "fs-extra": "^11.1.1", "minimist": "^1.2.8", "postject": "^1.0.0-alpha.6" }, "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" } }, "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/config-array": ["@eslint/config-array@0.23.3", "", { "dependencies": { "@eslint/object-schema": "^3.0.3", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw=="], + "@eslint/config-array": ["@eslint/config-array@0.23.4", "", { "dependencies": { "@eslint/object-schema": "^3.0.4", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.5.3", "", { "dependencies": { "@eslint/core": "^1.1.1" } }, "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw=="], + "@eslint/config-helpers": ["@eslint/config-helpers@0.5.4", "", { "dependencies": { "@eslint/core": "^1.2.0" } }, "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg=="], - "@eslint/core": ["@eslint/core@1.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ=="], + "@eslint/core": ["@eslint/core@1.2.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A=="], - "@eslint/css": ["@eslint/css@1.0.0", "", { "dependencies": { "@eslint/core": "^1.1.1", "@eslint/css-tree": "^3.6.9", "@eslint/plugin-kit": "^0.6.1" } }, "sha512-2MjyL517F1wrLrmmfaFntKKiImudiXW6Dd80oE7VRIRu64bT5YieRs+WPgdZy9m8izs/tSUqi3j2mBnjUrqJfA=="], + "@eslint/css": ["@eslint/css@1.1.0", "", { "dependencies": { "@eslint/core": "^1.1.1", "@eslint/css-tree": "^3.6.9", "@eslint/plugin-kit": "^0.6.1" } }, "sha512-sNwfLcU3nKXv/v2YglqujwMU4Iv3BDhxldNUd/2FckVab0zdvc9pPlKWxjR6Ap/EU+Y8Pdu853iwvcUpemRhRw=="], "@eslint/css-tree": ["@eslint/css-tree@3.6.9", "", { "dependencies": { "mdn-data": "2.23.0", "source-map-js": "^1.0.1" } }, "sha512-3D5/OHibNEGk+wKwNwMbz63NMf367EoR4mVNNpxddCHKEb2Nez7z62J2U6YjtErSsZDoY0CsccmoUpdEbkogNA=="], @@ -125,27 +154,27 @@ "@eslint/json": ["@eslint/json@1.2.0", "", { "dependencies": { "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanwhocodes/momoa": "^3.3.10", "natural-compare": "^1.4.0" } }, "sha512-CEFEyNgvzu8zn5QwVYDg3FaG+ZKUeUsNYitFpMYJAqoAlnw68EQgNbUfheSmexZr4n0wZPrAkPLuvsLaXO6wRw=="], - "@eslint/object-schema": ["@eslint/object-schema@3.0.3", "", {}, "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ=="], + "@eslint/object-schema": ["@eslint/object-schema@3.0.4", "", {}, "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw=="], "@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.1", "", { "dependencies": { "@eslint/core": "^1.1.1", "levn": "^0.4.1" } }, "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ=="], "@fabianlars/tauri-plugin-oauth": ["@fabianlars/tauri-plugin-oauth@2.0.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.3" } }, "sha512-I1s08ZXrsFuYfNWusAcpLyiCfr5TCvaBrRuKfTG+XQrcaqnAcwjdWH0U5J9QWuMDLwCUMnVxdobtMJzPR8raxQ=="], - "@floating-ui/core": ["@floating-ui/core@1.6.9", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="], + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], - "@floating-ui/dom": ["@floating-ui/dom@1.6.13", "", { "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w=="], + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], "@floating-ui/react": ["@floating-ui/react@0.26.28", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.8", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw=="], - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.2", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A=="], + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], - "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], "@headlessui/react": ["@headlessui/react@2.2.9", "", { "dependencies": { "@floating-ui/react": "^0.26.16", "@react-aria/focus": "^3.20.2", "@react-aria/interactions": "^3.25.0", "@tanstack/react-virtual": "^3.13.9", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], - "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], @@ -157,9 +186,13 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -167,9 +200,13 @@ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], - "@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=="], + "@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -177,23 +214,33 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="], + + "@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], + "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], - "@react-aria/focus": ["@react-aria/focus@3.20.3", "", { "dependencies": { "@react-aria/interactions": "^3.25.1", "@react-aria/utils": "^3.29.0", "@react-types/shared": "^3.29.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-rR5uZUMSY4xLHmpK/I8bP1V6vUNHFo33gTvrvNUsAKKqvMfa7R2nu5A6v97dr5g6tVH6xzpdkPsOJCWh90H2cw=="], + "@quadrant/quadrant-node": ["@quadrant/quadrant-node@workspace:packages/quadrant-node"], - "@react-aria/interactions": ["@react-aria/interactions@3.25.1", "", { "dependencies": { "@react-aria/ssr": "^3.9.8", "@react-aria/utils": "^3.29.0", "@react-stately/flags": "^3.1.1", "@react-types/shared": "^3.29.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-ntLrlgqkmZupbbjekz3fE/n3eQH2vhncx8gUp0+N+GttKWevx7jos11JUBjnJwb1RSOPgRUFcrluOqBp0VgcfQ=="], + "@quadrant/quadrant-node": ["@quadrant/quadrant-node@file:packages/quadrant-node", {}], - "@react-aria/ssr": ["@react-aria/ssr@3.9.8", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw=="], + "@react-aria/focus": ["@react-aria/focus@3.21.5", "", { "dependencies": { "@react-aria/interactions": "^3.27.1", "@react-aria/utils": "^3.33.1", "@react-types/shared": "^3.33.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-V18fwCyf8zqgJdpLQeDU5ZRNd9TeOfBbhLgmX77Zr5ae9XwaoJ1R3SFJG1wCJX60t34AW+aLZSEEK+saQElf3Q=="], - "@react-aria/utils": ["@react-aria/utils@3.29.0", "", { "dependencies": { "@react-aria/ssr": "^3.9.8", "@react-stately/flags": "^3.1.1", "@react-stately/utils": "^3.10.6", "@react-types/shared": "^3.29.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-jSOrZimCuT1iKNVlhjIxDkAhgF7HSp3pqyT6qjg/ZoA0wfqCi/okmrMPiWSAKBnkgX93N8GYTLT3CIEO6WZe9Q=="], + "@react-aria/interactions": ["@react-aria/interactions@3.27.1", "", { "dependencies": { "@react-aria/ssr": "^3.9.10", "@react-aria/utils": "^3.33.1", "@react-stately/flags": "^3.1.2", "@react-types/shared": "^3.33.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-M3wLpTTmDflI0QGNK0PJNUaBXXfeBXue8ZxLMngfc1piHNiH4G5lUvWd9W14XVbqrSCVY8i8DfGrNYpyyZu0tw=="], - "@react-stately/flags": ["@react-stately/flags@3.1.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg=="], + "@react-aria/ssr": ["@react-aria/ssr@3.9.10", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ=="], - "@react-stately/utils": ["@react-stately/utils@3.10.6", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA=="], + "@react-aria/utils": ["@react-aria/utils@3.33.1", "", { "dependencies": { "@react-aria/ssr": "^3.9.10", "@react-stately/flags": "^3.1.2", "@react-stately/utils": "^3.11.0", "@react-types/shared": "^3.33.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-kIx1Sj6bbAT0pdqCegHuPanR9zrLn5zMRiM7LN12rgRf55S19ptd9g3ncahArifYTRkfEU9VIn+q0HjfMqS9/w=="], - "@react-types/shared": ["@react-types/shared@3.29.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-KtM+cDf2CXoUX439rfEhbnEdAgFZX20UP2A35ypNIawR7/PFFPjQDWyA2EnClCcW/dLWJDEPX2U8+EJff8xqmQ=="], + "@react-stately/flags": ["@react-stately/flags@3.1.2", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg=="], + + "@react-stately/utils": ["@react-stately/utils@3.11.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-8LZpYowJ9eZmmYLpudbo/eclIRnbhWIJZ994ncmlKlouNzKohtM8qTC6B1w1pwUbiwGdUoyzLuQbeaIor5Dvcw=="], + + "@react-types/shared": ["@react-types/shared@3.33.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-oJHtjvLG43VjwemQDadlR5g/8VepK56B/xKO2XORPHt9zlW6IZs3tZrYlvH29BMvoqC7RtE7E5UjgbnbFtDGag=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.12", "", { "os": "android", "cpu": "arm64" }, "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA=="], @@ -229,9 +276,13 @@ "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], - "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + "@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="], + + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], - "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + "@swc/helpers": ["@swc/helpers@0.5.21", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg=="], + + "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], @@ -263,9 +314,9 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="], - "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.9", "", { "dependencies": { "@tanstack/virtual-core": "3.13.9" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-SPWC8kwG/dWBf7Py7cfheAPOxuvIv4fFQ54PdmYbg7CpXfsKxkucak43Q0qKsxVthhUJQ1A7CIMAIplq4BjVwA=="], + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.23", "", { "dependencies": { "@tanstack/virtual-core": "3.13.23" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ=="], - "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.9", "", {}, "sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ=="], + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.23", "", {}, "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg=="], "@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="], @@ -319,35 +370,57 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], + + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/fs-extra": ["@types/fs-extra@9.0.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA=="], + + "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], + "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.57.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/type-utils": "8.57.2", "@typescript-eslint/utils": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.57.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w=="], + "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], + + "@types/verror": ["@types/verror@1.10.11", "", {}, "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg=="], + + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@6.21.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.2", "@typescript-eslint/types": "^8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0" } }, "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="], "@typescript-eslint/types": ["@typescript-eslint/types@6.21.0", "", {}, "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.2", "@typescript-eslint/tsconfig-utils": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.57.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="], "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="], @@ -355,6 +428,10 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], + "@xmldom/xmldom": ["@xmldom/xmldom@0.8.12", "", {}, "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg=="], + + "abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -363,9 +440,15 @@ "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + "ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "app-builder-bin": ["app-builder-bin@5.0.0-alpha.12", "", {}, "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w=="], + + "app-builder-lib": ["app-builder-lib@26.8.1", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.3", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.8.1", "electron-builder-squirrel-windows": "26.8.1" } }, "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -389,15 +472,27 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "async-exit-hook": ["async-exit-hook@2.0.1", "", {}, "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw=="], + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], + "autoprefixer": ["autoprefixer@10.4.27", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - "axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="], + "axe-core": ["axe-core@4.11.2", "", {}, "sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -405,17 +500,39 @@ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.7", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA=="], "binary-search": ["binary-search@1.3.6", "", {}, "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA=="], - "brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + + "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "bresenham-zingl": ["bresenham-zingl@0.2.5", "", {}, "sha512-0TCrPvavRM/3Os9QIUDhe8jpAQXgEYjsVO+9IKnkC8cdzorDX5fMRfr3neEFGjcfRItEswHvIvPrqQzCalRYkw=="], - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "builder-util": ["builder-util@26.8.1", "", { "dependencies": { "7zip-bin": "~5.2.0", "@types/debug": "^4.1.6", "app-builder-bin": "5.0.0-alpha.12", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw=="], + + "builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="], + + "cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], + + "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], + + "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], @@ -425,11 +542,31 @@ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "caniuse-lite": ["caniuse-lite@1.0.30001776", "", {}, "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001785", "", {}, "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "cheminfo-types": ["cheminfo-types@1.10.0", "", {}, "sha512-lDoOWfctAQPQrrhydtdb2vV3S8RDuRNp62lzs/gIjNqMNubhsvqr+hI8XQJSy8X1ZXpGvzIQNGBYy4SVrKQNaQ=="], + "cheminfo-types": ["cheminfo-types@1.15.0", "", {}, "sha512-shv45WN2u0yN9EHH1bisNrv+fy4Cw+eLM5lOoriP67mePrwbHZ1kJqg90C8GEU7K1A8gJsicEoVZHcuBbuul/w=="], + + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="], + + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + + "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], + + "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], @@ -439,14 +576,24 @@ "colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], "common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="], + "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="], + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], + + "crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="], + + "cross-dirname": ["cross-dirname@0.1.0", "", {}, "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], @@ -461,27 +608,71 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], + + "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], - "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + + "dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="], "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + "dmg-builder": ["dmg-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" }, "optionalDependencies": { "dmg-license": "^1.0.11" } }, "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg=="], + + "dmg-license": ["dmg-license@1.0.11", "", { "dependencies": { "@types/plist": "^3.0.1", "@types/verror": "^1.10.3", "ajv": "^6.10.0", "crc": "^3.8.0", "iconv-corefoundation": "^1.1.7", "plist": "^3.0.4", "smart-buffer": "^4.0.2", "verror": "^1.10.0" }, "os": "darwin", "bin": { "dmg-license": "bin/dmg-license.js" } }, "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q=="], + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], + + "electron": ["electron@41.1.1", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-8bgvDhBjli+3Z2YCKgzzoBPh6391pr7Xv2h/tTJG4ETgvPvUxZomObbZLs31mUzYb6VrlcDDd9cyWyNKtPm3tA=="], + + "electron-builder": ["electron-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw=="], + + "electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "electron-winstaller": "5.4.0" } }, "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA=="], + + "electron-publish": ["electron-publish@26.8.1", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.331", "", {}, "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q=="], + + "electron-updater": ["electron-updater@6.8.3", "", { "dependencies": { "builder-util-runtime": "9.5.1", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ=="], + + "electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="], + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], "es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="], @@ -489,7 +680,7 @@ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="], + "es-iterator-helpers": ["es-iterator-helpers@1.3.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" } }, "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ=="], "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], @@ -499,15 +690,17 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.1.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.3", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA=="], + "eslint": ["eslint@10.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.4", "@eslint/config-helpers": "^0.5.4", "@eslint/core": "^1.2.0", "@eslint/plugin-kit": "^0.7.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA=="], "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], - "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="], + "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.10", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.16.1", "resolve": "^2.0.0-next.6" } }, "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ=="], "eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="], @@ -535,6 +728,12 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + + "extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="], + "fast-bmp": ["fast-bmp@4.0.1", "", { "dependencies": { "iobuffer": "^6.0.0" } }, "sha512-+KtMijJj+uA8Sl6EXAnhza7US7EXSY5Z9NeiJwT1wopVUksyLMXL5iFmn9FjY8FdkstOkpJI9RuEVXkGpIPSwg=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -553,6 +752,8 @@ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], @@ -563,22 +764,32 @@ "file-type": ["file-type@10.11.0", "", {}, "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw=="], + "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], + "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -593,16 +804,22 @@ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + "globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], @@ -611,6 +828,8 @@ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], @@ -635,15 +854,29 @@ "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + "html-parse-stringify": ["html-parse-stringify@3.0.1", "", { "dependencies": { "void-elements": "3.1.0" } }, "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg=="], + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "i18next": ["i18next@26.0.1", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-vtz5sXU4+nkCm8yEU+JJ6yYIx0mkg9e68W0G0PXpnOsmzLajNsW5o28DJMqbajxfsfq0gV3XdrBudsDQnwxfsQ=="], + "i18next": ["i18next@26.0.3", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-1571kXINxHKY7LksWp8wP+zP0YqHSSpl/OW0Y0owFEf2H3s8gCAffWaZivcz14rMkOvn3R/psiQxVsR9t2Nafg=="], + + "iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "image-js": ["image-js@1.4.0", "", { "dependencies": { "bresenham-zingl": "^0.2.5", "colord": "^2.9.3", "fast-bmp": "^4.0.1", "fast-jpeg": "^3.0.1", "fast-png": "^8.0.0", "image-type": "^4.1.0", "jpeg-js": "^0.4.4", "js-priority-queue": "^0.1.5", "median-quickselect": "^1.0.1", "ml-affine-transform": "^1.0.3", "ml-convolution": "^2.0.0", "ml-matrix": "^6.12.1", "ml-ransac": "^1.0.0", "ml-regression-multivariate-linear": "^2.0.4", "ml-regression-polynomial-2d": "^1.0.0", "ml-spectra-processing": "^14.18.2", "robust-point-in-polygon": "^1.0.3", "skia-canvas": "^3.0.8", "ssim.js": "^3.5.0", "tiff": "^7.1.3", "ts-pattern": "^5.9.0", "uint8-base64": "^1.0.0" } }, "sha512-X3CGc5t936NUME2yfJvEyvkVHc5Kbwvt6VxufIQf5o53nlRBxp1OpatdxDdvRPkArqUw3qRA1HxOIvUE/nniXA=="], + "image-js": ["image-js@1.5.0", "", { "dependencies": { "bresenham-zingl": "^0.2.5", "colord": "^2.9.3", "fast-bmp": "^4.0.1", "fast-jpeg": "^3.0.1", "fast-png": "^8.0.0", "image-type": "^4.1.0", "jpeg-js": "^0.4.4", "js-priority-queue": "^0.1.5", "median-quickselect": "^1.0.1", "ml-affine-transform": "^1.0.3", "ml-convolution": "^2.0.0", "ml-matrix": "^6.12.1", "ml-ransac": "^1.0.0", "ml-regression-multivariate-linear": "^2.0.4", "ml-regression-polynomial-2d": "^1.0.0", "ml-spectra-processing": "^14.18.2", "robust-point-in-polygon": "^1.0.3", "ssim.js": "^3.5.0", "tiff": "^7.1.3", "ts-pattern": "^5.9.0", "uint8-base64": "^1.0.0" }, "optionalDependencies": { "skia-canvas": "^3.0.8" } }, "sha512-ZE/e7wlb5732r3wHXcPtRiirODk/YKYBKzpffd3qfYD1wUA0PrYewYOHLOJGQoi1vj6G9TAQpHIkxV91qm5FBQ=="], "image-type": ["image-type@4.1.0", "", { "dependencies": { "file-type": "^10.10.0" } }, "sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg=="], @@ -661,6 +894,8 @@ "iobuffer": ["iobuffer@6.0.1", "", {}, "sha512-SZWYkWNfjIXIBYSDpXDYIgshqtbOPsi4lviawAEceR1Kqk+sHDlcQjWrzNQsii80AyBY0q5c8HCTNjqo74ul+Q=="], + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + "is-any-array": ["is-any-array@2.0.1", "", {}, "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ=="], "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], @@ -683,10 +918,14 @@ "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], @@ -709,6 +948,8 @@ "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], @@ -717,10 +958,16 @@ "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="], + + "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="], @@ -729,7 +976,7 @@ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], @@ -739,7 +986,11 @@ "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], @@ -749,6 +1000,8 @@ "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], + "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -777,20 +1030,32 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + + "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], + + "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="], "loglevel-colored-level-prefix": ["loglevel-colored-level-prefix@1.0.0", "", { "dependencies": { "chalk": "^1.1.3", "loglevel": "^1.4.1" } }, "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA=="], "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], + "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], + + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "mdn-data": ["mdn-data@2.23.0", "", {}, "sha512-786vq1+4079JSeu2XdcDjrhi/Ry7BWtjDl9WtGPWLiIHb2T66GvIVflZTBoSNZ5JqTtJGYEVMuFA/lbQlMOyDQ=="], @@ -801,12 +1066,36 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "ml-affine-transform": ["ml-affine-transform@1.0.3", "", { "dependencies": { "ml-matrix": "^6.10.4" } }, "sha512-uv13li9HOGuMdSKL+QEdaV25emO9kt17LzGCD+FLQrBsrU8p/r/3kS/GJ7xI+f3/aWghkBK733x0yBG6EwKFJA=="], "ml-array-max": ["ml-array-max@1.2.4", "", { "dependencies": { "is-any-array": "^2.0.0" } }, "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ=="], @@ -847,9 +1136,25 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "next-power-of-two": ["next-power-of-two@1.0.0", "", {}, "sha512-+z6QY1SxkDk6CQJAeaIZKmcNubBCRP7J8DMQUBglz/sSkNsZoJ1kULjqk9skNPPplzs4i9PFhYrvNDdtQleF/A=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + "node-abi": ["node-abi@4.28.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g=="], + + "node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], + + "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="], + + "node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="], + + "node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], + + "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], + + "nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], + + "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], @@ -869,14 +1174,24 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], + "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parenthesis": ["parenthesis@3.1.8", "", {}, "sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw=="], @@ -893,16 +1208,24 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "pe-library": ["pe-library@0.4.1", "", {}, "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw=="], + + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], @@ -913,36 +1236,68 @@ "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + "proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], + + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], - "react-i18next": ["react-i18next@17.0.1", "", { "dependencies": { "@babel/runtime": "^7.29.2", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.0.1", "react": ">= 16.8.0", "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-iG65FGnFHcYyHNuT01ukffYWCOBFTWSdVD8EZd/dCVWgtjFPObcSsvYYNwcsokO/rDcTb5d6D8Acv8MrOdm6Hw=="], + "react-i18next": ["react-i18next@17.0.2", "", { "dependencies": { "@babel/runtime": "^7.29.2", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.0.1", "react": ">= 16.8.0", "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA=="], "react-icons": ["react-icons@5.6.0", "", { "peerDependencies": { "react": "*" } }, "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA=="], "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-relative": ["require-relative@0.8.7", "", {}, "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg=="], - "resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], + "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="], + + "resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="], + + "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], + + "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + "robust-orientation": ["robust-orientation@1.2.1", "", { "dependencies": { "robust-scale": "^1.0.2", "robust-subtract": "^1.0.0", "robust-sum": "^1.0.0", "two-product": "^1.0.2" } }, "sha512-FuTptgKwY6iNuU15nrIJDLjXzCChWB+T4AvksRtwPS/WZ3HuP1CElCm1t+OBfgQKfWbtZIawip+61k7+buRKAg=="], "robust-point-in-polygon": ["robust-point-in-polygon@1.0.3", "", { "dependencies": { "robust-orientation": "^1.0.2" } }, "sha512-pPzz7AevOOcPYnFv4Vs5L0C7BKOq6C/TfAw5EUE58CylbjGiPyMjAnPLzzSuPZ2zftUGwWbmLWPOjPOz61tAcA=="], @@ -959,13 +1314,25 @@ "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sanitize-filename": ["sanitize-filename@1.6.4", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg=="], + + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "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=="], + + "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], @@ -985,18 +1352,44 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], + "skia-canvas": ["skia-canvas@3.0.8", "", { "dependencies": { "detect-libc": "^2.1.1", "follow-redirects": "^1.15.11", "https-proxy-agent": "^7.0.6", "string-split-by": "^1.0.0" } }, "sha512-FSYKxp8Ng2vOeeOBiyPhnn6ui6FirPJXMyjk4PKl8N/OWzVrkMawUgY9zubIWHMdYtyWFn0gfX3QlRwg6HBmdg=="], "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + "slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + "ssim.js": ["ssim.js@3.5.0", "", {}, "sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g=="], + "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + + "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "string-split-by": ["string-split-by@1.0.0", "", { "dependencies": { "parenthesis": "^3.1.5" } }, "sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A=="], + "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=="], + "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], @@ -1009,33 +1402,55 @@ "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="], - "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="], + "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], - "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], + + "tar": ["tar@7.5.13", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng=="], + + "temp": ["temp@0.9.4", "", { "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" } }, "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA=="], + + "temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="], "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], "tiff": ["tiff@7.1.3", "", { "dependencies": { "fflate": "^0.8.2", "iobuffer": "^6.0.0" } }, "sha512-YEEq3fT++2pdta/9P/vGG4QRMdZQoe6W6JNaWnIi6NvAsbeNITwFCtmWwL/BZvOi+uo2I3ohyOkD3sZfme+c6g=="], + "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="], + + "tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], + + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], "ts-pattern": ["ts-pattern@5.9.0", "", {}, "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg=="], @@ -1061,17 +1476,31 @@ "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], - "typescript-eslint": ["typescript-eslint@8.57.2", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.57.2", "@typescript-eslint/parser": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A=="], + "typescript-eslint": ["typescript-eslint@8.58.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA=="], "uint8-base64": ["uint8-base64@1.0.0", "", {}, "sha512-B6ZJfEZL2FAhbIyZZ7WlQq8MPq1X1P1DUA//LsSv/vB4r+Dao5/BPoxgw2t+t1XyvoSfBkE7zI4SYsnGj7BmlQ=="], "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], - "update-browserslist-db": ["update-browserslist-db@1.2.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], + + "unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "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=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="], "vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="], @@ -1079,7 +1508,9 @@ "vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], + + "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], @@ -1091,159 +1522,209 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zod": ["zod@3.25.28", "", {}, "sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], - "@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/generator/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], + "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], - "@babel/generator/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + "@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=="], - "@babel/helper-compilation-targets/browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="], + "@electron/asar/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "@electron/fuses/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=="], - "@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="], + "@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], - "@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg=="], + "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="], + "@electron/notarize/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=="], - "@babel/template/@babel/parser": ["@babel/parser@7.27.2", "", { "dependencies": { "@babel/types": "^7.27.1" }, "bin": "./bin/babel-parser.js" }, "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw=="], + "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], - "@babel/template/@babel/types": ["@babel/types@7.27.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q=="], + "@electron/universal/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], - "@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "@electron/universal/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "@electron/windows-sign/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], - "@eslint/eslintrc/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], "@eslint/eslintrc/globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], - "@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "@eslint/eslintrc/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "@humanwhocodes/config-array/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "@fabianlars/tauri-plugin-oauth/@tauri-apps/api": ["@tauri-apps/api@2.3.0", "", {}, "sha512-33Z+0lX2wgZbx1SPFfqvzI6su63hCBkbzv+5NexeYjIx7WA9htdOKoRR7Dh3dJyltqS5/J8vQFyybiRoaL0hlA=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - "@humanwhocodes/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + "@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=="], - "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - "@tailwindcss/oxide-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" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@tauri-apps/plugin-autostart/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@types/cacheable-request/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], - "@tauri-apps/plugin-cli/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@types/fs-extra/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], - "@tauri-apps/plugin-clipboard-manager/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@types/keyv/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], - "@tauri-apps/plugin-dialog/@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + "@types/plist/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], - "@tauri-apps/plugin-fs/@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + "@types/responselike/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], - "@tauri-apps/plugin-notification/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@types/yauzl/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], - "@tauri-apps/plugin-opener/@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="], - "@tauri-apps/plugin-os/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], - "@tauri-apps/plugin-store/@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2" } }, "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw=="], + "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" } }, "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="], + "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" } }, "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ=="], + "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], - "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], - "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="], + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="], - "@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2" } }, "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw=="], + "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], - "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001760", "", {}, "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw=="], + "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], - "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "eslint-import-resolver-node/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], + + "eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.0", "", { "dependencies": { "@eslint/core": "^1.2.0", "levn": "^0.4.1" } }, "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg=="], + + "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - "eslint-plugin-import/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "eslint-plugin-import/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "eslint-plugin-react/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "eslint-plugin-react/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + + "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "has-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "loglevel-colored-level-prefix/chalk": ["chalk@1.1.3", "", { "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "ml-random/ml-xsadd": ["ml-xsadd@2.0.0", "", {}, "sha512-VoAYUqmPRmzKbbqRejjqceGFp3VF81Qe8XXFGU0UXLxB7Mf4GGvyGq5Qn3k4AiQgDEV6WzobqlPOd+j0+m6IrA=="], + "node-exports-info/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], + "prettier-eslint/eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], - "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], - "react-i18next/use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], "rimraf/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=="], "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.12", "", {}, "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw=="], - "skia-canvas/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + + "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], + + "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], - "typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA=="], + "typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="], "vue-eslint-parser/eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], @@ -1251,57 +1732,75 @@ "vue-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], - "vue-eslint-parser/esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "vue-eslint-parser/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@electron/asar/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "@babel/helper-compilation-targets/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001704", "", {}, "sha512-+L2IgBbV6gXB4ETf0keSvLr7JUrRVbIaB/lrQ1+z8mRcQiisG5k+lG6O4n6Y5q6f5EuNfaYXKgymucphlEXQew=="], + "@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], - "@babel/helper-compilation-targets/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.118", "", {}, "sha512-yNDUus0iultYyVoEFLnQeei7LOQkL8wg8GQpkPCRrOlJXlcCwa6eGKZkxQ9ciHsqZyYbj8Jd94X1CTPzGm+uIA=="], + "@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - "@babel/helper-compilation-targets/browserslist/node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + "@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], - "@babel/helper-compilation-targets/browserslist/update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + "@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.28.0", "", { "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg=="], + "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="], + "@humanwhocodes/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "@babel/helper-module-imports/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - "@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + "@types/cacheable-request/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@babel/helper-module-transforms/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "@types/fs-extra/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@eslint/eslintrc/espree/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "@types/keyv/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "@types/plist/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "@types/responselike/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@humanwhocodes/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "@types/yauzl/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], - "@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="], + "@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], + + "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "electron-winstaller/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], - "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "loglevel-colored-level-prefix/chalk/ansi-styles": ["ansi-styles@2.2.1", "", {}, "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="], @@ -1311,9 +1810,13 @@ "loglevel-colored-level-prefix/chalk/supports-color": ["supports-color@2.0.0", "", {}, "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="], - "prettier-eslint/eslint/@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], + "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - "prettier-eslint/eslint/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "minipass-sized/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "prettier-eslint/eslint/@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], "prettier-eslint/eslint/doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], @@ -1323,29 +1826,39 @@ "prettier-eslint/eslint/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], - "prettier-eslint/eslint/esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], - "prettier-eslint/eslint/file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], "prettier-eslint/eslint/globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], - "prettier-eslint/eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "prettier-eslint/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2" } }, "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw=="], + "temp/rimraf/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=="], - "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], - "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="], + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "vue-eslint-parser/espree/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], + + "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "@eslint/eslintrc/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "@humanwhocodes/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], + + "app-builder-lib/@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], + + "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "eslint-plugin-import/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -1353,20 +1866,28 @@ "eslint-plugin-react/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "loglevel-colored-level-prefix/chalk/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "prettier-eslint/eslint/espree/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "loglevel-colored-level-prefix/chalk/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], "prettier-eslint/eslint/file-entry-cache/flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], - "prettier-eslint/eslint/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "prettier-eslint/eslint/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "prettier-eslint/eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "temp/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "temp/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], } } diff --git a/electron-builder.json b/electron-builder.json new file mode 100644 index 00000000..ce6f5812 --- /dev/null +++ b/electron-builder.json @@ -0,0 +1,59 @@ +{ + "appId": "dev.usequadrant.quadrant", + "productName": "Quadrant", + "directories": { + "output": "dist-electron" + }, + "files": [ + "dist/**/*", + "electron/**/*", + "packages/quadrant-node/**/*", + "package.json" + ], + "extraMetadata": { + "main": "electron/main.mjs" + }, + "asar": true, + "asarUnpack": [ + "packages/quadrant-node/native/*.node" + ], + "npmRebuild": false, + "buildDependenciesFromSource": false, + "protocols": [ + { + "name": "Quadrant", + "schemes": [ + "quadrantnext", + "curseforge", + "modrinth" + ] + } + ], + "publish": [ + { + "provider": "github", + "owner": "quadrantmc", + "repo": "quadrant" + } + ], + "artifactName": "Quadrant-${version}-${os}-${arch}-electron.${ext}", + "win": { + "icon": "src-tauri/icons/icon.ico", + "target": [ + "nsis", + "zip" + ] + }, + "linux": { + "icon": "src-tauri/icons", + "target": [ + "AppImage", + "deb" + ], + "category": "Utility" + }, + "nsis": { + "allowToChangeInstallationDirectory": true, + "oneClick": false + } +} \ No newline at end of file diff --git a/electron/main.mjs b/electron/main.mjs new file mode 100644 index 00000000..56aff6b9 --- /dev/null +++ b/electron/main.mjs @@ -0,0 +1,603 @@ +import { + app, + BrowserWindow, + Menu, + Tray, + clipboard, + dialog, + ipcMain, + nativeTheme, + shell, +} from "electron"; +import { autoUpdater } from "electron-updater"; +import chokidar from "chokidar"; +import { createQuadrantClient } from "@quadrant/quadrant-node"; +import fs from "node:fs"; +import fsPromises from "node:fs/promises"; +import http from "node:http"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.resolve(__dirname, ".."); +const appId = "dev.mrquantumoff.mcmodpackmanager"; +const schemes = ["quadrantnext", "curseforge", "modrinth"]; + +app.setPath("userData", path.join(app.getPath("appData"), appId)); + +const argv = process.argv.slice(1); +const isAutostart = argv.includes("--autostart"); +const updaterDisabledByCli = argv.includes("--noupdater"); +const devServerUrl = process.env.QUADRANT_ELECTRON_DEV_SERVER_URL; + +let mainWindow = null; +let tray = null; +let quadrantClient = null; +let updaterConfigured = false; +let updateDownloaded = false; +const storeCache = new Map(); +const watchRegistry = new Map(); +const oauthServers = new Map(); +const pendingDeepLinks = []; + +function broadcast(channel, payload) { + for (const window of BrowserWindow.getAllWindows()) { + window.webContents.send(channel, payload); + } +} + +function parseDeepLinkUrls(values) { + return values.filter((value) => + schemes.some((scheme) => value.startsWith(`${scheme}:`)), + ); +} + +function flushPendingDeepLinks() { + if (pendingDeepLinks.length === 0) { + return; + } + const urls = pendingDeepLinks.splice(0, pendingDeepLinks.length); + broadcast("quadrant:open-url", urls); +} + +function loadGeneratedRuntimeConfig() { + const runtimeConfigPath = path.join( + rootDir, + "electron", + "runtime-config.generated.json", + ); + + if (!fs.existsSync(runtimeConfigPath)) { + return {}; + } + + return JSON.parse(fs.readFileSync(runtimeConfigPath, "utf8")); +} + +function getRuntimeConfig() { + const generated = loadGeneratedRuntimeConfig(); + return { + oauthClientId: + process.env.QUADRANT_OAUTH2_CLIENT_ID ?? generated.oauthClientId ?? "", + oauthClientSecret: + process.env.QUADRANT_OAUTH2_CLIENT_SECRET ?? + generated.oauthClientSecret ?? + "", + quadrantApiKey: + process.env.QUADRANT_API_KEY ?? generated.quadrantApiKey ?? "", + apiBaseUrl: + process.env.QUADRANT_API_BASE_URL ?? generated.apiBaseUrl ?? null, + }; +} + +function ensureRuntimeSecrets(config) { + if (!config.oauthClientId || !config.oauthClientSecret || !config.quadrantApiKey) { + throw new Error( + "Electron runtime config is incomplete. Run with QUADRANT_OAUTH2_CLIENT_ID, QUADRANT_OAUTH2_CLIENT_SECRET, and QUADRANT_API_KEY available.", + ); + } +} + +function getWindow() { + if (!mainWindow) { + throw new Error("Main window is not ready"); + } + return mainWindow; +} + +function resolveIconPath() { + return path.join(rootDir, "src-tauri", "icons", "128x128.png"); +} + +function resolveTrayIconPath() { + return path.join(rootDir, "public", "tray.png"); +} + +function storeFilePath(storeName) { + return path.join(app.getPath("userData"), storeName); +} + +async function readStore(storeName) { + if (storeCache.has(storeName)) { + return storeCache.get(storeName); + } + + const filePath = storeFilePath(storeName); + await fsPromises.mkdir(path.dirname(filePath), { recursive: true }); + + let data = {}; + if (fs.existsSync(filePath)) { + try { + data = JSON.parse(await fsPromises.readFile(filePath, "utf8")); + } catch (error) { + console.warn(`Failed to parse ${filePath}`, error); + } + } + + storeCache.set(storeName, data); + return data; +} + +async function saveStore(storeName) { + const data = await readStore(storeName); + const filePath = storeFilePath(storeName); + await fsPromises.mkdir(path.dirname(filePath), { recursive: true }); + await fsPromises.writeFile(filePath, JSON.stringify(data, null, 2)); +} + +async function setStoreValue(storeName, key, value) { + const data = await readStore(storeName); + data[key] = value; + storeCache.set(storeName, data); + await saveStore(storeName); + broadcast("quadrant:store:changed", { storeName, key, value }); +} + +function queueDeepLinks(urls) { + if (urls.length === 0) { + return; + } + pendingDeepLinks.push(...urls); + if (mainWindow) { + flushPendingDeepLinks(); + } +} + +async function createQuadrantHostClient() { + if (quadrantClient) { + return quadrantClient; + } + + const runtimeConfig = getRuntimeConfig(); + ensureRuntimeSecrets(runtimeConfig); + + quadrantClient = createQuadrantClient({ + dataDir: app.getPath("userData"), + apiBaseUrl: runtimeConfig.apiBaseUrl, + oauthClientId: runtimeConfig.oauthClientId, + oauthClientSecret: runtimeConfig.oauthClientSecret, + quadrantApiKey: runtimeConfig.quadrantApiKey, + appVersion: app.getVersion(), + osName: process.platform.toUpperCase(), + userAgent: `Quadrant/${app.getVersion()} Electron/${process.versions.electron}`, + }); + + quadrantClient.on("event", (event) => { + broadcast("quadrant:backend-event", event); + }); + + await quadrantClient.initConfig(); + try { + await quadrantClient.startBackgroundWorkers(); + } catch (error) { + console.error("Failed to start background workers", error); + } + + quadrantClient + .invoke("get_versions") + .catch((error) => + console.warn("Failed to warm Minecraft versions cache", error), + ); + + return quadrantClient; +} + +async function configureUpdater() { + if (updaterConfigured) { + return; + } + + autoUpdater.autoDownload = true; + autoUpdater.autoInstallOnAppQuit = false; + + const updateStore = await readStore("updateConfig.json"); + const channel = updateStore.channel ?? "stable"; + autoUpdater.allowPrerelease = channel !== "stable"; + updaterConfigured = true; + + autoUpdater.on("download-progress", (progress) => { + broadcast("quadrant:backend-event", { + event: "updateDownloadProgress", + payload: progress.percent / 100, + }); + }); + + autoUpdater.on("update-downloaded", () => { + updateDownloaded = true; + broadcast("quadrant:backend-event", { + event: "updateDownloadProgress", + payload: 1, + }); + }); + + autoUpdater.on("update-not-available", () => { + updateDownloaded = false; + }); +} + +function isAutoupdateEnabled() { + return app.isPackaged && !updaterDisabledByCli; +} + +async function requestCheckForUpdates() { + if (!isAutoupdateEnabled()) { + return; + } + + await configureUpdater(); + await autoUpdater.checkForUpdates(); +} + +function createMainWindow() { + nativeTheme.themeSource = "dark"; + + const window = new BrowserWindow({ + title: "Quadrant", + width: 1280, + height: 720, + minWidth: 1280, + minHeight: 720, + show: false, + frame: false, + backgroundColor: "#020617", + icon: resolveIconPath(), + webPreferences: { + preload: path.join(__dirname, "preload.mjs"), + contextIsolation: true, + nodeIntegration: false, + sandbox: false, + }, + }); + + window.on("ready-to-show", () => { + if (isAutostart) { + window.hide(); + return; + } + window.show(); + }); + + window.on("closed", () => { + if (mainWindow === window) { + mainWindow = null; + } + }); + + if (devServerUrl) { + window.loadURL(devServerUrl); + window.webContents.openDevTools({ mode: "detach" }); + } else { + window.loadFile(path.join(rootDir, "dist", "index.html")); + } + + return window; +} + +function showMainWindow() { + if (!mainWindow) { + return; + } + mainWindow.show(); + mainWindow.focus(); + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } +} + +function toggleMainWindowVisibility() { + if (!mainWindow) { + return; + } + + if (mainWindow.isVisible()) { + mainWindow.hide(); + } else { + showMainWindow(); + } +} + +function createTray() { + const nextTray = new Tray(resolveTrayIconPath()); + const menu = Menu.buildFromTemplate([ + { + label: "Show/Hide", + click: () => { + toggleMainWindowVisibility(); + }, + }, + { + label: "Quit", + click: () => { + app.quit(); + }, + }, + ]); + + nextTray.setToolTip("Quadrant"); + nextTray.setContextMenu(menu); + nextTray.on("click", () => { + toggleMainWindowVisibility(); + }); + + return nextTray; +} + +function registerProtocolHandlers() { + for (const scheme of schemes) { + if (process.defaultApp) { + if (process.argv.length >= 2) { + app.setAsDefaultProtocolClient(scheme, process.execPath, [ + path.resolve(process.argv[1]), + ]); + } + continue; + } + app.setAsDefaultProtocolClient(scheme); + } +} + +const gotLock = app.requestSingleInstanceLock(); +if (!gotLock) { + app.quit(); +} + +app.on("second-instance", (_event, commandLine) => { + showMainWindow(); + queueDeepLinks(parseDeepLinkUrls(commandLine)); +}); + +app.on("open-url", (event, url) => { + event.preventDefault(); + queueDeepLinks([url]); +}); + +app.whenReady().then(async () => { + registerProtocolHandlers(); + mainWindow = createMainWindow(); + tray = createTray(); + + await createQuadrantHostClient(); + flushPendingDeepLinks(); +}); + +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + app.quit(); + } +}); + +app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0) { + mainWindow = createMainWindow(); + flushPendingDeepLinks(); + return; + } + showMainWindow(); +}); + +app.on("before-quit", async () => { + for (const watcher of watchRegistry.values()) { + await watcher.close(); + } + watchRegistry.clear(); + + for (const server of oauthServers.values()) { + server.close(); + } + oauthServers.clear(); + + if (quadrantClient) { + try { + await quadrantClient.shutdown(); + } catch (error) { + console.error("Failed to shut down Quadrant client", error); + } + } +}); + +ipcMain.handle("quadrant:invoke", async (_event, { command, payload }) => { + const client = await createQuadrantHostClient(); + return client.invoke(command, payload ?? null); +}); + +ipcMain.handle("quadrant:store:get", async (_event, { storeName, key }) => { + const store = await readStore(storeName); + return store[key]; +}); + +ipcMain.handle("quadrant:store:set", async (_event, { storeName, key, value }) => { + await setStoreValue(storeName, key, value); +}); + +ipcMain.handle("quadrant:store:save", async (_event, { storeName }) => { + await saveStore(storeName); +}); + +ipcMain.handle("quadrant:fs-watch:start", async (event, { watchId, targetPath, options }) => { + const watcher = chokidar.watch(targetPath, { + ignoreInitial: true, + awaitWriteFinish: options?.delayMs + ? { + stabilityThreshold: options.delayMs, + pollInterval: Math.max(50, Math.floor(options.delayMs / 2)), + } + : false, + }); + const sendChange = () => { + event.sender.send("quadrant:fs-watch:event", { watchId }); + }; + watcher.on("add", sendChange); + watcher.on("change", sendChange); + watcher.on("unlink", sendChange); + watcher.on("addDir", sendChange); + watcher.on("unlinkDir", sendChange); + watchRegistry.set(watchId, watcher); +}); + +ipcMain.handle("quadrant:fs-watch:stop", async (_event, { watchId }) => { + const watcher = watchRegistry.get(watchId); + if (watcher) { + await watcher.close(); + watchRegistry.delete(watchId); + } +}); + +ipcMain.handle("quadrant:path:join", async (_event, { segments }) => { + return path.join(...segments); +}); + +ipcMain.handle("quadrant:dialog:open", async (_event, { options }) => { + const result = await dialog.showOpenDialog(getWindow(), { + title: options?.title, + properties: [ + options?.directory ? "openDirectory" : "openFile", + ...(options?.multiple ? ["multiSelections"] : []), + ...(options?.directory && options?.recursive ? ["createDirectory"] : []), + ], + defaultPath: options?.defaultPath, + }); + + if (result.canceled) { + return null; + } + + if (options?.multiple) { + return result.filePaths; + } + + return result.filePaths[0] ?? null; +}); + +ipcMain.handle("quadrant:shell:open-external", async (_event, { url }) => { + await shell.openExternal(url); +}); + +ipcMain.handle("quadrant:shell:open-path", async (_event, { targetPath }) => { + const errorMessage = await shell.openPath(targetPath); + if (errorMessage) { + throw new Error(errorMessage); + } +}); + +ipcMain.handle("quadrant:clipboard:read-text", async () => clipboard.readText()); +ipcMain.handle("quadrant:clipboard:write-text", async (_event, { text }) => { + clipboard.writeText(text); +}); + +ipcMain.handle("quadrant:platform", async () => process.platform); +ipcMain.handle("quadrant:app-version", async () => app.getVersion()); +ipcMain.handle("quadrant:runtime-version", async () => process.versions.electron); + +ipcMain.handle("quadrant:updater:check", async () => { + await requestCheckForUpdates(); +}); + +ipcMain.handle("quadrant:updater:install", async () => { + if (!updateDownloaded) { + throw new Error("No downloaded update is available"); + } + autoUpdater.quitAndInstall(); +}); + +ipcMain.handle("quadrant:updater:is-enabled", async () => isAutoupdateEnabled()); + +ipcMain.handle("quadrant:window:minimize", async () => { + getWindow().minimize(); +}); + +ipcMain.handle("quadrant:window:hide", async () => { + getWindow().hide(); +}); + +ipcMain.handle("quadrant:window:set-enabled", async (_event, { enabled }) => { + getWindow().setEnabled(enabled); +}); + +ipcMain.handle("quadrant:window:set-focus", async () => { + getWindow().focus(); +}); + +ipcMain.handle("quadrant:window:unminimize", async () => { + const window = getWindow(); + if (window.isMinimized()) { + window.restore(); + } +}); + +ipcMain.handle("quadrant:window:set-progress-bar", async (_event, { state }) => { + const window = getWindow(); + const normalizedProgress = + typeof state.progress === "number" && state.progress <= 1 + ? state.progress + : state.progress / 100; + const progress = state.status === "none" ? -1 : normalizedProgress; + window.setProgressBar(progress); +}); + +ipcMain.handle("quadrant:oauth:start", async (_event, { options }) => { + const responseHtml = + options?.response ?? + "

You can return to Quadrant now.

"; + const ports = options?.ports ?? [4000, 4001, 4002, 4003, 4004, 4005]; + + for (const port of ports) { + try { + const server = http.createServer((request, response) => { + const url = `http://127.0.0.1:${port}${request.url ?? "/"}`; + broadcast("quadrant:oauth:url", url); + response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); + response.end(responseHtml); + }); + + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, "127.0.0.1", () => resolve(undefined)); + }); + + oauthServers.set(port, server); + return port; + } catch (error) { + console.warn(`Failed to bind OAuth server on ${port}`, error); + } + } + + throw new Error("No available OAuth callback port"); +}); + +ipcMain.handle("quadrant:oauth:cancel", async (_event, { port }) => { + const server = oauthServers.get(port); + if (!server) { + return; + } + + await new Promise((resolve, reject) => { + server.close((error) => { + if (error) { + reject(error); + return; + } + resolve(undefined); + }); + }); + oauthServers.delete(port); +}); diff --git a/electron/preload.mjs b/electron/preload.mjs new file mode 100644 index 00000000..651790b4 --- /dev/null +++ b/electron/preload.mjs @@ -0,0 +1,116 @@ +import { contextBridge, ipcRenderer } from "electron"; +import { randomUUID } from "node:crypto"; + +function listen(channel, listener) { + const wrapped = (_event, payload) => listener(payload); + ipcRenderer.on(channel, wrapped); + return () => { + ipcRenderer.removeListener(channel, wrapped); + }; +} + +const api = { + invoke(command, payload) { + return ipcRenderer.invoke("quadrant:invoke", { command, payload }); + }, + addBackendEventListener(listener) { + return listen("quadrant:backend-event", listener); + }, + storeGet(storeName, key) { + return ipcRenderer.invoke("quadrant:store:get", { storeName, key }); + }, + storeSet(storeName, key, value) { + return ipcRenderer.invoke("quadrant:store:set", { storeName, key, value }); + }, + storeSave(storeName) { + return ipcRenderer.invoke("quadrant:store:save", { storeName }); + }, + addStoreChangeListener(listener) { + return listen("quadrant:store:changed", listener); + }, + async watchPath(targetPath, options, listener) { + const watchId = randomUUID(); + const stopListening = listen("quadrant:fs-watch:event", (payload) => { + if (payload.watchId === watchId) { + listener(); + } + }); + await ipcRenderer.invoke("quadrant:fs-watch:start", { + watchId, + targetPath, + options, + }); + return async () => { + stopListening(); + await ipcRenderer.invoke("quadrant:fs-watch:stop", { watchId }); + }; + }, + joinPath(...segments) { + return ipcRenderer.invoke("quadrant:path:join", { segments }); + }, + openDialog(options) { + return ipcRenderer.invoke("quadrant:dialog:open", { options }); + }, + openExternal(url) { + return ipcRenderer.invoke("quadrant:shell:open-external", { url }); + }, + openPath(targetPath) { + return ipcRenderer.invoke("quadrant:shell:open-path", { targetPath }); + }, + readClipboardText() { + return ipcRenderer.invoke("quadrant:clipboard:read-text"); + }, + writeClipboardText(text) { + return ipcRenderer.invoke("quadrant:clipboard:write-text", { text }); + }, + platform() { + return ipcRenderer.invoke("quadrant:platform"); + }, + getAppVersion() { + return ipcRenderer.invoke("quadrant:app-version"); + }, + getRuntimeVersion() { + return ipcRenderer.invoke("quadrant:runtime-version"); + }, + requestCheckForUpdates() { + return ipcRenderer.invoke("quadrant:updater:check"); + }, + installUpdate() { + return ipcRenderer.invoke("quadrant:updater:install"); + }, + isAutoupdateEnabled() { + return ipcRenderer.invoke("quadrant:updater:is-enabled"); + }, + windowMinimize() { + return ipcRenderer.invoke("quadrant:window:minimize"); + }, + windowHide() { + return ipcRenderer.invoke("quadrant:window:hide"); + }, + windowSetEnabled(enabled) { + return ipcRenderer.invoke("quadrant:window:set-enabled", { enabled }); + }, + windowSetFocus() { + return ipcRenderer.invoke("quadrant:window:set-focus"); + }, + windowUnminimize() { + return ipcRenderer.invoke("quadrant:window:unminimize"); + }, + windowSetProgressBar(state) { + return ipcRenderer.invoke("quadrant:window:set-progress-bar", { state }); + }, + addOpenUrlListener(listener) { + return listen("quadrant:open-url", listener); + }, + startOAuthServer(options) { + return ipcRenderer.invoke("quadrant:oauth:start", { options }); + }, + cancelOAuthServer(port) { + return ipcRenderer.invoke("quadrant:oauth:cancel", { port }); + }, + addOAuthUrlListener(listener) { + return listen("quadrant:oauth:url", listener); + }, +}; + +contextBridge.exposeInMainWorld("quadrantElectron", api); diff --git a/package.json b/package.json index 14e05382..83396b6f 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,27 @@ "private": true, "version": "26.4.0", "type": "module", + "description": "An easy way to manage your Minecraft mods and modpacks.", + "author": "Demir Yerli ", + "workspaces": [ + "packages/*" + ], "scripts": { "dev": "vite", "build": "tsc && vite build", "lint": "eslint .", "preview": "vite preview", + "build:napi": "node scripts/build-napi.mjs", + "dev:tauri": "tauri dev", + "build:tauri": "tauri build", + "dev:electron": "node scripts/dev-electron.mjs", + "build:electron": "node scripts/build-electron.mjs", + "package:electron": "node scripts/package-electron.mjs", "tauri": "tauri", "tauri:windows:build": "tauri-windows-bundle build" }, "dependencies": { + "@quadrant/quadrant-node": "file:packages/quadrant-node", "@fabianlars/tauri-plugin-oauth": "^2.0.0", "@headlessui/react": "^2.2.9", "@tauri-apps/api": "^2.10.1", @@ -27,25 +39,29 @@ "@tauri-apps/plugin-os": "~2.3.2", "@tauri-apps/plugin-store": "2.4.2", "@tauri-apps/plugin-updater": "2.10.0", - "i18next": "26.0.1", + "i18next": "26.0.3", "motion": "12.38.0", "react": "19.2.4", "react-dom": "19.2.4", - "react-i18next": "^17.0.1", + "react-i18next": "^17.0.2", "react-icons": "^5.6.0" }, "devDependencies": { - "@eslint/css": "^1.0.0", + "@eslint/css": "^1.1.0", "@eslint/js": "^10.0.1", "@eslint/json": "^1.2.0", "@tailwindcss/postcss": "4.2.2", + "chokidar": "^5.0.0", + "electron": "^41.1.1", + "electron-builder": "^26.8.1", + "electron-updater": "^6.8.3", "@tauri-apps/cli": "^2.10.1", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", "autoprefixer": "^10.4.27", "babel-plugin-react-compiler": "1.0.0", - "eslint": "^10.1.0", + "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.2", @@ -58,16 +74,11 @@ "prettier-eslint": "^16.4.2", "tailwindcss": "4.2.2", "typescript": "6.0.2", - "typescript-eslint": "^8.57.2", + "typescript-eslint": "^8.58.0", "vite": "^8.0.3", "@choochmeque/tauri-windows-bundle": "^0.1.17" }, "packageManager": "bun@1.3.11", - "pnpm": { - "onlyBuiltDependencies": [ - "esbuild" - ] - }, "trustedDependencies": [ "@tailwindcss/oxide" ] diff --git a/packages/quadrant-node/index.js b/packages/quadrant-node/index.js index 67eb12d9..898f1073 100644 --- a/packages/quadrant-node/index.js +++ b/packages/quadrant-node/index.js @@ -1,80 +1,102 @@ +/** @format */ + import { EventEmitter } from "node:events"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); function loadNativeModule() { - if (process.env.QUADRANT_NAPI_MODULE) { - return require(process.env.QUADRANT_NAPI_MODULE); - } + if (process.env.QUADRANT_NAPI_MODULE) { + return require(process.env.QUADRANT_NAPI_MODULE); + } + + try { + return require("./native/index.js"); + } catch (error) { + if (process.env.NODE_ENV === "development") { + console.warn("Failed to load packaged Quadrant native module", error); + } + } - return require("../../src-tauri/crates/quadrant-napi/index.js"); + return require("../../src-tauri/crates/quadrant-napi/index.js").default; } function nativeMethod(target, ...names) { - for (const name of names) { - if (typeof target[name] === "function") { - return target[name].bind(target); - } - } - throw new Error(`Native method not found. Tried: ${names.join(", ")}`); + for (const name of names) { + if (typeof target[name] === "function") { + return target[name].bind(target); + } + } + throw new Error(`Native method not found. Tried: ${names.join(", ")}`); } function mapOptions(options) { - return { - data_dir: options.dataDir, - mc_folder: options.mcFolder ?? null, - api_base_url: options.apiBaseUrl ?? null, - oauth_client_id: options.oauthClientId, - oauth_client_secret: options.oauthClientSecret, - quadrant_api_key: options.quadrantApiKey, - config_store_name: options.configStoreName ?? null, - update_store_name: options.updateStoreName ?? null, - keyring_service_name: options.keyringServiceName ?? null, - app_version: options.appVersion ?? null, - os_name: options.osName ?? null, - user_agent: options.userAgent ?? null, - }; + return { + data_dir: options.dataDir, + mc_folder: options.mcFolder ?? null, + api_base_url: options.apiBaseUrl ?? null, + oauth_client_id: options.oauthClientId, + oauth_client_secret: options.oauthClientSecret, + quadrant_api_key: options.quadrantApiKey, + config_store_name: options.configStoreName ?? null, + update_store_name: options.updateStoreName ?? null, + keyring_service_name: options.keyringServiceName ?? null, + app_version: options.appVersion ?? null, + os_name: options.osName ?? null, + user_agent: options.userAgent ?? null, + }; } -export function createQuadrantClient(options, nativeModule = loadNativeModule()) { - const host = new nativeModule.QuadrantHostAddon(mapOptions(options)); - const emitter = new EventEmitter(); +export function createQuadrantClient( + options, + nativeModule = loadNativeModule(), +) { + const host = new nativeModule.QuadrantHostAddon(mapOptions(options)); + const emitter = new EventEmitter(); - nativeMethod(host, "onEvent", "on_event")((rawEvent) => { - const event = typeof rawEvent === "string" ? JSON.parse(rawEvent) : rawEvent; - emitter.emit("event", event); - emitter.emit(event.event, event.payload); - }); + nativeMethod( + host, + "onEvent", + "on_event", + )((rawEvent) => { + const event = + typeof rawEvent === "string" ? JSON.parse(rawEvent) : rawEvent; + emitter.emit("event", event); + emitter.emit(event.event, event.payload); + }); - return { - startBackgroundWorkers: () => - nativeMethod(host, "startBackgroundWorkers", "start_background_workers")(), - stopBackgroundWorkers: () => - nativeMethod(host, "stopBackgroundWorkers", "stop_background_workers")(), - shutdown: () => nativeMethod(host, "shutdown")(), - initConfig: () => nativeMethod(host, "initConfig", "init_config")(), - getMinecraftFolder: () => - nativeMethod(host, "getMinecraftFolder", "get_minecraft_folder")(), - invoke: (command, payload = null) => - nativeMethod(host, "invoke")(command, payload), - getModpacks: (hideFree = false) => - nativeMethod(host, "getModpacks", "get_modpacks")(hideFree), - getAccountInfo: () => - nativeMethod(host, "getAccountInfo", "get_account_info")(), - getNews: () => nativeMethod(host, "getNews", "get_news")(), - installMod: (args) => nativeMethod(host, "installMod", "install_mod")(args), - syncModpack: (modpack, overwrite = true) => - nativeMethod(host, "syncModpack", "sync_modpack")(modpack, overwrite), - on: (eventName, listener) => { - emitter.on(eventName, listener); - return () => emitter.off(eventName, listener); - }, - off: (eventName, listener) => { - emitter.off(eventName, listener); - }, - once: (eventName, listener) => { - emitter.once(eventName, listener); - }, - }; + return { + startBackgroundWorkers: () => + nativeMethod( + host, + "startBackgroundWorkers", + "start_background_workers", + )(), + stopBackgroundWorkers: () => + nativeMethod(host, "stopBackgroundWorkers", "stop_background_workers")(), + shutdown: () => nativeMethod(host, "shutdown")(), + initConfig: () => nativeMethod(host, "initConfig", "init_config")(), + getMinecraftFolder: () => + nativeMethod(host, "getMinecraftFolder", "get_minecraft_folder")(), + invoke: (command, payload = null) => + nativeMethod(host, "invoke")(command, payload), + getModpacks: (hideFree = false) => + nativeMethod(host, "getModpacks", "get_modpacks")(hideFree), + getAccountInfo: () => + nativeMethod(host, "getAccountInfo", "get_account_info")(), + getNews: () => nativeMethod(host, "getNews", "get_news")(), + installMod: (args) => nativeMethod(host, "installMod", "install_mod")(args), + syncModpack: (modpack, overwrite = true) => + nativeMethod(host, "syncModpack", "sync_modpack")(modpack, overwrite), + on: (eventName, listener) => { + emitter.on(eventName, listener); + return () => emitter.off(eventName, listener); + }, + off: (eventName, listener) => { + emitter.off(eventName, listener); + }, + once: (eventName, listener) => { + emitter.once(eventName, listener); + }, + }; } diff --git a/packages/quadrant-node/index.test.js b/packages/quadrant-node/index.test.js deleted file mode 100644 index d0983489..00000000 --- a/packages/quadrant-node/index.test.js +++ /dev/null @@ -1,95 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; - -import { createQuadrantClient } from "./index.js"; - -test("createQuadrantClient maps events and invoke-style methods", async () => { - const seen = []; - const fakeNative = { - QuadrantHostAddon: class { - constructor() { - fakeNative.instance = this; - } - - on_event(callback) { - this.callback = callback; - } - - invoke(command, payload) { - return Promise.resolve({ command, payload }); - } - - get_modpacks(hideFree) { - return Promise.resolve([{ name: "demo", hideFree }]); - } - - get_account_info() { - return Promise.resolve({ login: "demo" }); - } - - get_news() { - return Promise.resolve([{ title: "hello" }]); - } - - install_mod() { - return Promise.resolve(); - } - - sync_modpack() { - return Promise.resolve(); - } - - start_background_workers() { - return Promise.resolve(); - } - - stop_background_workers() { - return Promise.resolve(); - } - - shutdown() { - return Promise.resolve(); - } - - init_config() { - return Promise.resolve(); - } - - get_minecraft_folder() { - return Promise.resolve("C:/demo/.minecraft"); - } - }, - }; - - const client = createQuadrantClient( - { - dataDir: "C:/quadrant", - oauthClientId: "client", - oauthClientSecret: "secret", - quadrantApiKey: "api-key", - }, - fakeNative, - ); - - const dispose = client.on("refreshNotifications", (payload) => { - seen.push(payload); - }); - - fakeNative.instance.callback?.( - JSON.stringify({ - event: "refreshNotifications", - payload: [{ notification_id: "n1" }], - }), - ); - - const invokeResult = await client.invoke("get_modpacks", { hideFree: true }); - const modpacks = await client.getModpacks(true); - - assert.deepEqual(invokeResult, { - command: "get_modpacks", - payload: { hideFree: true }, - }); - assert.equal(modpacks[0].name, "demo"); - assert.deepEqual(seen, [[{ notification_id: "n1" }]]); - dispose(); -}); diff --git a/packages/quadrant-node/package.json b/packages/quadrant-node/package.json index c30a5bea..dfe1c517 100644 --- a/packages/quadrant-node/package.json +++ b/packages/quadrant-node/package.json @@ -3,5 +3,10 @@ "version": "26.4.0", "type": "module", "main": "index.js", - "types": "index.d.ts" + "types": "index.d.ts", + "files": [ + "index.js", + "index.d.ts", + "native/**/*" + ] } diff --git a/scripts/build-electron.mjs b/scripts/build-electron.mjs new file mode 100644 index 00000000..a1737030 --- /dev/null +++ b/scripts/build-electron.mjs @@ -0,0 +1,22 @@ +/** @format */ + +import { spawnSync } from "node:child_process"; +import path from "node:path"; + +const rootDir = path.resolve(import.meta.dirname, ".."); + +function run(command, args) { + const result = spawnSync(command, args, { + cwd: rootDir, + stdio: "inherit", + shell: process.platform === "win32", + }); + + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} + +run("node", ["scripts/write-electron-runtime-config.mjs"]); +run("bun", ["run", "build"]); +run("node", ["scripts/build-napi.mjs", "--release"]); diff --git a/scripts/build-napi.mjs b/scripts/build-napi.mjs new file mode 100644 index 00000000..2c60dc34 --- /dev/null +++ b/scripts/build-napi.mjs @@ -0,0 +1,69 @@ +import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; + +const rootDir = path.resolve(import.meta.dirname, ".."); +const srcTauriDir = path.join(rootDir, "src-tauri"); +const release = process.argv.includes("--release"); +const profile = release ? "release" : "debug"; + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: rootDir, + stdio: "inherit", + shell: process.platform === "win32", + ...options, + }); + + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} + +function findNativeBinary(directory) { + const entries = readdirSync(directory, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + const found = findNativeBinary(fullPath); + if (found) { + return found; + } + continue; + } + if ( + entry.isFile() && + entry.name.toLowerCase().includes("quadrant_napi") && + (entry.name.endsWith(".node") || + entry.name.endsWith(".dll") || + entry.name.endsWith(".so") || + entry.name.endsWith(".dylib")) + ) { + return fullPath; + } + } + return null; +} + +run("cargo", [ + "build", + "--manifest-path", + path.join("src-tauri", "Cargo.toml"), + "-p", + "quadrant-napi", + ...(release ? ["--release"] : []), +]); + +const targetDir = path.join(srcTauriDir, "target", profile); +const builtNode = findNativeBinary(targetDir); + +if (!builtNode || !existsSync(builtNode)) { + throw new Error(`Could not find built N-API binary under ${targetDir}`); +} + +const nativeDir = path.join(rootDir, "packages", "quadrant-node", "native"); +mkdirSync(nativeDir, { recursive: true }); +cpSync(path.join(srcTauriDir, "crates", "quadrant-napi", "index.js"), path.join(nativeDir, "index.js")); +cpSync(builtNode, path.join(nativeDir, "index.node")); + +console.log(`Copied ${builtNode} to ${path.join(nativeDir, "index.node")}`); diff --git a/scripts/dev-electron.mjs b/scripts/dev-electron.mjs new file mode 100644 index 00000000..a77cb441 --- /dev/null +++ b/scripts/dev-electron.mjs @@ -0,0 +1,93 @@ +import { spawn } from "node:child_process"; +import http from "node:http"; +import path from "node:path"; + +const rootDir = path.resolve(import.meta.dirname, ".."); +const devUrl = "http://127.0.0.1:1420"; + +function spawnProcess(command, args, extraEnv = {}) { + return spawn(command, args, { + cwd: rootDir, + stdio: "inherit", + shell: process.platform === "win32", + env: { + ...process.env, + ...extraEnv, + }, + }); +} + +async function waitForUrl(url, timeoutMs = 120000) { + const startedAt = Date.now(); + + while (Date.now() - startedAt < timeoutMs) { + const isReady = await new Promise((resolve) => { + const request = http.get(url, (response) => { + response.resume(); + resolve(response.statusCode !== undefined && response.statusCode < 500); + }); + request.on("error", () => resolve(false)); + request.setTimeout(2000, () => { + request.destroy(); + resolve(false); + }); + }); + + if (isReady) { + return; + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + throw new Error(`Timed out waiting for ${url}`); +} + +const prepareConfig = spawnProcess("node", [ + "scripts/write-electron-runtime-config.mjs", +]); + +await new Promise((resolve, reject) => { + prepareConfig.on("exit", (code) => { + if (code === 0) { + resolve(undefined); + return; + } + reject(new Error(`write-electron-runtime-config exited with ${code}`)); + }); +}); + +const napiBuild = spawnProcess("node", ["scripts/build-napi.mjs"]); +await new Promise((resolve, reject) => { + napiBuild.on("exit", (code) => { + if (code === 0) { + resolve(undefined); + return; + } + reject(new Error(`build-napi exited with ${code}`)); + }); +}); + +const viteProcess = spawnProcess("bun", ["run", "dev", "--", "--host", "127.0.0.1"]); + +const cleanup = () => { + viteProcess.kill(); +}; + +process.on("SIGINT", cleanup); +process.on("SIGTERM", cleanup); + +await waitForUrl(devUrl); + +const electronProcess = spawnProcess( + "bunx", + ["electron", "electron/main.mjs"], + { + QUADRANT_ELECTRON_DEV_SERVER_URL: devUrl, + }, +); + +electronProcess.on("exit", (code) => { + cleanup(); + process.exit(code ?? 0); +}); diff --git a/scripts/package-electron.mjs b/scripts/package-electron.mjs new file mode 100644 index 00000000..6289ea96 --- /dev/null +++ b/scripts/package-electron.mjs @@ -0,0 +1,19 @@ +import { spawnSync } from "node:child_process"; +import path from "node:path"; + +const rootDir = path.resolve(import.meta.dirname, ".."); + +function run(command, args) { + const result = spawnSync(command, args, { + cwd: rootDir, + stdio: "inherit", + shell: process.platform === "win32", + }); + + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} + +run("node", ["scripts/build-electron.mjs"]); +run("bunx", ["electron-builder", "--config", "electron-builder.json"]); diff --git a/scripts/write-electron-runtime-config.mjs b/scripts/write-electron-runtime-config.mjs new file mode 100644 index 00000000..ecfd085b --- /dev/null +++ b/scripts/write-electron-runtime-config.mjs @@ -0,0 +1,21 @@ +import { mkdirSync, writeFileSync } from "node:fs"; +import path from "node:path"; + +const rootDir = path.resolve(import.meta.dirname, ".."); +const outputPath = path.join( + rootDir, + "electron", + "runtime-config.generated.json", +); + +const config = { + oauthClientId: process.env.QUADRANT_OAUTH2_CLIENT_ID ?? "", + oauthClientSecret: process.env.QUADRANT_OAUTH2_CLIENT_SECRET ?? "", + quadrantApiKey: process.env.QUADRANT_API_KEY ?? "", + apiBaseUrl: process.env.QUADRANT_API_BASE_URL ?? "", +}; + +mkdirSync(path.dirname(outputPath), { recursive: true }); +writeFileSync(outputPath, JSON.stringify(config, null, 2)); + +console.log(`Wrote Electron runtime config to ${outputPath}`); diff --git a/src-tauri/crates/quadrant-napi/index.js b/src-tauri/crates/quadrant-napi/index.js index 9b086960..3e89780c 100644 --- a/src-tauri/crates/quadrant-napi/index.js +++ b/src-tauri/crates/quadrant-napi/index.js @@ -1,26 +1,34 @@ +/** + * eslint-disable @typescript-eslint/no-require-imports + * + * @format + */ + +/** @format */ + const candidates = [ - process.env.QUADRANT_NAPI_BINDING, - "./index.node", - "./quadrant-napi.node", - "./quadrant_napi.node", + process.env.QUADRANT_NAPI_BINDING, + "./index.node", + "./quadrant-napi.node", + "./quadrant_napi.node", ].filter(Boolean); let loaded; let lastError; for (const candidate of candidates) { - try { - loaded = require(candidate); - break; - } catch (error) { - lastError = error; - } + try { + loaded = require(candidate); + break; + } catch (error) { + lastError = error; + } } if (!loaded) { - throw new Error( - `Failed to load Quadrant N-API binding. Set QUADRANT_NAPI_BINDING to the built addon path. Last error: ${lastError}`, - ); + throw new Error( + `Failed to load Quadrant N-API binding. Set QUADRANT_NAPI_BINDING to the built addon path. Last error: ${lastError}`, + ); } -module.exports = loaded; +export default loaded; diff --git a/src/App.css b/src/App.css index 5a878143..0fda02b2 100644 --- a/src/App.css +++ b/src/App.css @@ -55,6 +55,18 @@ option { user-select: none; /* Standard syntax */ } +[data-tauri-drag-region] { + -webkit-app-region: drag; +} + button { + -webkit-app-region: no-drag; @apply cursor-pointer transition-shadow duration-150 ease-linear; } + +input, +select, +textarea, +a { + -webkit-app-region: no-drag; +} diff --git a/src/App.tsx b/src/App.tsx index 696b21cd..df588b41 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,6 @@ import { I18nextProvider, useTranslation } from "react-i18next"; import ApplyPage from "./components/Pages/ApplyPage/Apply"; import SettingsPage from "./components/Pages/SettingsPage/Settings"; import quadrantLocale from "./i18n"; -import { LazyStore } from "@tauri-apps/plugin-store"; import { MdAccountCircle, MdArchive, @@ -33,17 +32,23 @@ import { import CurrentModpackPage from "./components/Pages/CurrentModpackPage/CurrentModpackPage"; import { AnimatePresence, motion, MotionConfig } from "motion/react"; import SearchPage from "./components/Pages/SearchPage/SearchPage"; -import { onOpenUrl } from "@tauri-apps/plugin-deep-link"; -import { getCurrentWindow, ProgressBarStatus } from "@tauri-apps/api/window"; import { getMod, requestCheckForUpdates } from "./tools"; import ModInstallPage from "./components/Pages/ModInstallPage/ModInstallPage"; import AccountPage from "./components/Pages/AccountPage/AccountPage"; -import { invoke } from "@tauri-apps/api/core"; import ShareSyncPage from "./components/Pages/ShareSyncPage/ShareSyncPage"; import Button from "./components/core/Button"; -import { listen } from "@tauri-apps/api/event"; import Notifications from "./components/shared/Notifications"; -import { platform } from "@tauri-apps/plugin-os"; +import { + createDesktopStore, + getCurrentDesktopWindow, + installUpdate, + invoke, + isProductionBuild, + listen, + onOpenUrl, + platform, + ProgressBarStatus, +} from "./desktop"; interface PageWithScroll { scrollPositionX: number; @@ -106,11 +111,8 @@ function App() { const [page, setPage] = useState(pages[0]); const [content, setContent] = useState(pages[0]); const [updateDownloadProgress, setUpdateDownloadProgress] = useState(0); - const configRef = useRef(null); - if (configRef.current === null) { - configRef.current = new LazyStore("config.json"); - } - const config = configRef.current!; + const configRef = useRef(createDesktopStore("config.json")); + const config = configRef.current; const [contentHistory, setContentHistory] = useState([]); const [extendedNavigation, setExtendedNavigation] = useState(false); const [isLinux, setIsLinux] = useState(false); @@ -128,6 +130,10 @@ function App() { let requestUpdatesTimeout: ReturnType | null = null; const effect = async () => { + if (await isProductionBuild()) { + document.addEventListener("contextmenu", disableContextMenu); + } + const updateDownloadUnlisten = await listen( "updateDownloadProgress", (e: any) => { @@ -153,21 +159,6 @@ function App() { cleanupFns.push(updateDownloadUnlisten); } - const disableRightClickUnlisten = await listen( - "disableRightClick", - () => { - document.addEventListener("contextmenu", disableContextMenu); - }, - ); - if (isUnmounted) { - disableRightClickUnlisten(); - } else { - cleanupFns.push(() => { - disableRightClickUnlisten(); - document.removeEventListener("contextmenu", disableContextMenu); - }); - } - const exportProgressUnlisten = await listen( "quadrantExportProgress", async (e: any) => { @@ -299,7 +290,7 @@ function App() { } console.log("deep link:", urls); for (const gottenUrl of urls) { - const url = URL.parse(gottenUrl); + const url = new URL(gottenUrl); const actionType = url?.protocol; console.log("url:", url); await currentWindow.setFocus(); @@ -626,7 +617,7 @@ function App() { }; } }, [snackbarEnabled, snackbarState.timeout]); - const currentWindow = getCurrentWindow(); + const currentWindow = getCurrentDesktopWindow(); return ( @@ -716,7 +707,7 @@ function App() { } onClick={async () => { if (updateDownloadProgress === 1) { - await invoke("install_update"); + await installUpdate(); } }} > diff --git a/src/components/Pages/AccountPage/AccountPage.tsx b/src/components/Pages/AccountPage/AccountPage.tsx index cb45fc3d..fc662d96 100644 --- a/src/components/Pages/AccountPage/AccountPage.tsx +++ b/src/components/Pages/AccountPage/AccountPage.tsx @@ -6,15 +6,18 @@ import { AccountInfo } from "../../../intefaces"; import { clearAccountToken, getAccountInfo, openIn } from "../../../tools"; import Button from "../../core/Button"; import CircularProgress from "../../core/CircularProgress"; -import { listen } from "@tauri-apps/api/event"; import { useTranslation } from "react-i18next"; -import { LazyStore } from "@tauri-apps/plugin-store"; import { MdOpenInBrowser, MdOutlineAccountCircle } from "react-icons/md"; import { ContentContext } from "../../../intefaces"; import FirstRegisterStep from "./RegisterPages/Step1"; -import { start } from "@fabianlars/tauri-plugin-oauth"; -import { cancel, onUrl as onOAuth } from "@fabianlars/tauri-plugin-oauth"; -import { invoke } from "@tauri-apps/api/core"; +import { + cancelOAuthServer, + createDesktopStore, + invoke, + listen, + onOAuthUrl, + startOAuthServer, +} from "../../../desktop"; export default function AccountPage() { const { t } = useTranslation(); @@ -37,7 +40,7 @@ export default function AccountPage() { } }; - const config = new LazyStore("config.json"); + const config = createDesktopStore("config.json"); const context = useContext(ContentContext); const showLoginFailureWarning = () => { @@ -153,7 +156,7 @@ export default function AccountPage() { await config.set("oauthState", randomString); try { - const port = await start({ + const port = await startOAuthServer({ response: "

" + t("returnToTheApp") + @@ -179,7 +182,7 @@ export default function AccountPage() { authUrl.searchParams.set("state", randomString); openIn(authUrl.toString()); - unlistenOAuth = await onOAuth(async (rawUrl) => { + unlistenOAuth = await onOAuthUrl(async (rawUrl) => { try { const url = new URL(rawUrl); const oAuthState = await config.get("oauthState"); @@ -205,7 +208,7 @@ export default function AccountPage() { setAccountInfo(null); showLoginFailureWarning(); } finally { - await cancel(port); + await cancelOAuthServer(port); unlistenOAuth?.(); } }); diff --git a/src/components/Pages/AccountPage/RegisterPages/Step1.tsx b/src/components/Pages/AccountPage/RegisterPages/Step1.tsx index 2919e528..354c6823 100644 --- a/src/components/Pages/AccountPage/RegisterPages/Step1.tsx +++ b/src/components/Pages/AccountPage/RegisterPages/Step1.tsx @@ -2,9 +2,9 @@ import { Field, Input, Label } from "@headlessui/react"; import { useTranslation } from "react-i18next"; import Button from "../../../core/Button"; import { useContext, useState } from "react"; -import { fetch } from "@tauri-apps/plugin-http"; import { ContentContext } from "../../../../intefaces"; import SecondRegisterStep from "./Step2"; +import { desktopFetch } from "../../../../desktop"; export default function FirstRegisterStep() { const { t } = useTranslation(); @@ -21,7 +21,7 @@ export default function FirstRegisterStep() { } const url = "https://api.mrquantumoff.dev/api/v3/account/registration/request"; - const response = await fetch(url, { + const response = await desktopFetch(url, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/src/components/Pages/AccountPage/RegisterPages/Step2.tsx b/src/components/Pages/AccountPage/RegisterPages/Step2.tsx index 26260b06..b85d859a 100644 --- a/src/components/Pages/AccountPage/RegisterPages/Step2.tsx +++ b/src/components/Pages/AccountPage/RegisterPages/Step2.tsx @@ -5,7 +5,7 @@ import { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import Button from "../../../core/Button"; import { ContentContext } from "../../../../intefaces"; -import { fetch } from "@tauri-apps/plugin-http"; +import { desktopFetch } from "../../../../desktop"; interface SecondRegisterStepProps { email: string; @@ -49,7 +49,7 @@ export default function SecondRegisterStep({ email }: SecondRegisterStepProps) { login: username, name: name, }; - const request = await fetch( + const request = await desktopFetch( "https://api.mrquantumoff.dev/api/v3/account/registration/confirm", { body: JSON.stringify(body), diff --git a/src/components/Pages/ApplyPage/Apply.tsx b/src/components/Pages/ApplyPage/Apply.tsx index ee60772f..0e650be8 100644 --- a/src/components/Pages/ApplyPage/Apply.tsx +++ b/src/components/Pages/ApplyPage/Apply.tsx @@ -32,8 +32,6 @@ import { Select, } from "@headlessui/react"; import "./Apply.css"; -import { watch } from "@tauri-apps/plugin-fs"; -import * as path from "@tauri-apps/api/path"; import { MdArchive, MdCheck, @@ -48,8 +46,8 @@ import { } from "react-icons/md"; import LoaderOptions from "../../shared/LoaderOption"; import { ContentContext } from "../../../intefaces"; -import { listen } from "@tauri-apps/api/event"; import ModpackView from "../../shared/Pages/ModpackView"; +import { joinPath, listen, watch } from "../../../desktop"; export default function ApplyPage() { const [modpacks, setModpacks] = useState([]); @@ -104,7 +102,7 @@ export default function ApplyPage() { }); const unwatch = await watch( - await path.join(await getMinecraftFolder(false)), + await joinPath(await getMinecraftFolder(false)), async () => { if (!isUnmounted) { await updateModpacks(); diff --git a/src/components/Pages/CurrentModpackPage/CurrentModpackPage.tsx b/src/components/Pages/CurrentModpackPage/CurrentModpackPage.tsx index ef3b8840..a976f44a 100644 --- a/src/components/Pages/CurrentModpackPage/CurrentModpackPage.tsx +++ b/src/components/Pages/CurrentModpackPage/CurrentModpackPage.tsx @@ -2,9 +2,8 @@ import { useEffect, useRef, useState } from "react"; import { getMinecraftFolder, getModpacks } from "../../../tools"; import { LocalModpack } from "../../../intefaces"; import ModpackView from "../../shared/Pages/ModpackView"; -import { watch, type UnwatchFn } from "@tauri-apps/plugin-fs"; -import * as path from "@tauri-apps/api/path"; import { motion } from "motion/react"; +import { joinPath, watch } from "../../../desktop"; export default function CurrentModpackPage() { const [currentModpack, setCurrentModpack] = useState(); @@ -31,7 +30,7 @@ export default function CurrentModpackPage() { setCurrentModpack(newModpack); } - const mcFolder = await path.join(await getMinecraftFolder(false), "mods"); + const mcFolder = await joinPath(await getMinecraftFolder(false), "mods"); const unwatchMods = await watch( mcFolder, @@ -48,7 +47,7 @@ export default function CurrentModpackPage() { return unwatchMods; }; - let unwatch: UnwatchFn | undefined; + let unwatch: (() => void | Promise) | undefined; effect() .then((result) => { if (result) { diff --git a/src/components/Pages/ModInstallPage/ModInstallPage.tsx b/src/components/Pages/ModInstallPage/ModInstallPage.tsx index 6ce1b7e5..ca6b9388 100644 --- a/src/components/Pages/ModInstallPage/ModInstallPage.tsx +++ b/src/components/Pages/ModInstallPage/ModInstallPage.tsx @@ -25,12 +25,11 @@ import { installMod, openIn, } from "../../../tools"; -import { LazyStore, load } from "@tauri-apps/plugin-store"; import { Field, Fieldset, Label, Select } from "@headlessui/react"; import Mod from "../../shared/Mod"; import LoaderOptions from "../../shared/LoaderOption"; -import { listen } from "@tauri-apps/api/event"; import LinearProgress from "../../core/LinearProgress"; +import { createDesktopStore, listen } from "../../../desktop"; export interface IModInstallPageProps { mod: IMod; @@ -57,7 +56,7 @@ export default function ModInstallPage(props: IModInstallPageProps) { const [clipIcons, setClipIcons] = useState(true); const [modInstallProgress, setModInstallProgress] = useState(0); const [modDownloadProgress, setModDownloadProgress] = useState(0); - const config = new LazyStore("config.json"); + const config = createDesktopStore("config.json"); useEffect(() => { const effect = async () => { setVersions(await getVersions()); @@ -242,8 +241,8 @@ export default function ModInstallPage(props: IModInstallPageProps) { onChange={async (e) => { e.preventDefault(); setVersion(e.target.value); - const config = await load("config.json"); await config.set("lastUsedVersion", e.target.value); + await config.save(); }} value={version} > @@ -276,8 +275,8 @@ export default function ModInstallPage(props: IModInstallPageProps) { onChange={async (e) => { e.preventDefault(); setLoader(e.target.value); - const config = await load("config.json"); await config.set("lastUsedAPI", e.target.value); + await config.save(); }} value={loader} autoComplete="off" @@ -306,11 +305,10 @@ export default function ModInstallPage(props: IModInstallPageProps) { setModpack(e.target.value); setLoader(modpack.modLoader.toString()); setVersion(modpack.version); - const config = await load("config.json"); - await config.set("lastUsedVersion", modpack.version); await config.set("lastUsedAPI", modpack.modLoader.toString()); await config.set("lastUsedModpack", modpack.name); + await config.save(); }} > {modpacks.map((modpack) => ( diff --git a/src/components/Pages/SearchPage/SearchPage.tsx b/src/components/Pages/SearchPage/SearchPage.tsx index 83df019c..956a90d1 100644 --- a/src/components/Pages/SearchPage/SearchPage.tsx +++ b/src/components/Pages/SearchPage/SearchPage.tsx @@ -10,7 +10,6 @@ import { ModSource, ModType, } from "../../../intefaces"; -import { LazyStore } from "@tauri-apps/plugin-store"; import { getModpacks, getVersions, searchMods } from "../../../tools"; import Mod from "../../shared/Mod"; import Button from "../../core/Button"; @@ -36,6 +35,7 @@ import { Select, } from "@headlessui/react"; import LoaderOptions from "../../shared/LoaderOption"; +import { createDesktopStore } from "../../../desktop"; export default function SearchPage() { const [searchQuery, setSearchQuery] = useState(""); @@ -49,11 +49,8 @@ export default function SearchPage() { const [version, setVersion] = useState(""); const [modpack, setModpack] = useState(""); const [loader, setLoader] = useState(ModLoader.Unknown); - const configRef = useRef(null); - if (configRef.current === null) { - configRef.current = new LazyStore("config.json"); - } - const configStore = configRef.current!; + const configRef = useRef(createDesktopStore("config.json")); + const configStore = configRef.current; const search = async (forceSearch: boolean = false) => { if (searchQuery.trim() === "" && !forceSearch) { return; diff --git a/src/components/Pages/SettingsPage/Settings.tsx b/src/components/Pages/SettingsPage/Settings.tsx index ce9ad521..fe6e33c8 100644 --- a/src/components/Pages/SettingsPage/Settings.tsx +++ b/src/components/Pages/SettingsPage/Settings.tsx @@ -2,7 +2,6 @@ import { useTranslation } from "react-i18next"; import Button from "../../core/Button"; -import { LazyStore } from "@tauri-apps/plugin-store"; import { getMinecraftFolder, openIn, @@ -12,15 +11,20 @@ import { Field, Label, Select, Switch } from "@headlessui/react"; import quadrantLocale from "../../../i18n"; import { useEffect, useState } from "react"; import "./SettingsPage.css"; -import { open } from "@tauri-apps/plugin-dialog"; -import { getTauriVersion, getVersion } from "@tauri-apps/api/app"; -import { invoke } from "@tauri-apps/api/core"; import { motion } from "motion/react"; +import { + createDesktopStore, + getAppVersion, + getRuntimeVersion, + invoke, + isAutoupdateEnabled, + openDialog, +} from "../../../desktop"; export default function SettingsPage() { const { t } = useTranslation(); - const box = new LazyStore("config.json"); - const updateChannelBox = new LazyStore("updateConfig.json"); + const box = createDesktopStore("config.json"); + const updateChannelBox = createDesktopStore("updateConfig.json"); const [currentLocale, setCurrentLocale] = useState("en"); const [updateChannel, setUpdateChannel] = useState("stable"); @@ -71,12 +75,12 @@ export default function SettingsPage() { console.log("Minecraft folder: " + (await getMinecraftFolder(false))); setMcFolder(await getMinecraftFolder(false)); setSyncSettings((await box.get("syncSettings")) || false); - setCurrentVersion(await getVersion()); - setCurrentTauriVersion(await getTauriVersion()); + setCurrentVersion(await getAppVersion()); + setCurrentTauriVersion(await getRuntimeVersion()); setExtendedNavigation( (await box.get("extendedNavigation")) ?? false, ); - setShowUpdateSettings(await invoke("is_autoupdate_enabled")); + setShowUpdateSettings(await isAutoupdateEnabled()); }; initializeValues(); @@ -162,13 +166,13 @@ export default function SettingsPage() {