diff --git a/packages/hoppscotch-desktop/package.json b/packages/hoppscotch-desktop/package.json index 0d50168a..b5edc567 100644 --- a/packages/hoppscotch-desktop/package.json +++ b/packages/hoppscotch-desktop/package.json @@ -1,7 +1,7 @@ { "name": "hoppscotch-desktop", "private": true, - "version": "0.1.0", + "version": "25.2.0-0", "type": "module", "scripts": { "dev": "vite", @@ -19,8 +19,10 @@ "@hoppscotch/plugin-appload": "github:CuriousCorrelation/tauri-plugin-appload", "@hoppscotch/ui": "0.2.1", "@tauri-apps/api": "2.1.1", + "@tauri-apps/plugin-process": "2.2.0", "@tauri-apps/plugin-shell": "2.0.1", "@tauri-apps/plugin-store": "2.2.0", + "@tauri-apps/plugin-updater": "2.5.1", "@vueuse/core": "11.1.0", "fp-ts": "2.16.9", "vue": "^3.3.4", diff --git a/packages/hoppscotch-desktop/src-tauri/Cargo.lock b/packages/hoppscotch-desktop/src-tauri/Cargo.lock index 43c08cb7..b4a1e4c7 100644 --- a/packages/hoppscotch-desktop/src-tauri/Cargo.lock +++ b/packages/hoppscotch-desktop/src-tauri/Cargo.lock @@ -2023,7 +2023,7 @@ dependencies = [ [[package]] name = "hoppscotch-desktop" -version = "0.1.0" +version = "25.2.0" dependencies = [ "axum", "portpicker", @@ -2035,6 +2035,7 @@ dependencies = [ "tauri-plugin-deep-link", "tauri-plugin-dialog", "tauri-plugin-fs", + "tauri-plugin-process", "tauri-plugin-relay", "tauri-plugin-shell", "tauri-plugin-store", @@ -5066,6 +5067,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "tauri-plugin-process" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40cc553ab29581c8c43dfa5fb0c9d5aee8ba962ad3b42908eea26c79610441b7" +dependencies = [ + "tauri", + "tauri-plugin", +] + [[package]] name = "tauri-plugin-relay" version = "0.1.0" diff --git a/packages/hoppscotch-desktop/src-tauri/Cargo.toml b/packages/hoppscotch-desktop/src-tauri/Cargo.toml index 75d00786..4a714f6f 100644 --- a/packages/hoppscotch-desktop/src-tauri/Cargo.toml +++ b/packages/hoppscotch-desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hoppscotch-desktop" -version = "0.1.0" +version = "25.2.0" description = "Desktop App for hoppscotch.io" authors = ["CuriousCorrelation"] edition = "2021" @@ -34,9 +34,8 @@ axum = "0.8.1" tower-http = { version = "0.6.2", features = ["cors"] } portpicker = "0.1.1" tokio = "1.43.0" - -[target.'cfg(any(target_os = "macos", windows, target_os = "linux"))'.dependencies] -tauri-plugin-updater = "2.3.1" +tauri-plugin-process = "2.2.0" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] +tauri-plugin-updater = "2.3.1" tauri-plugin-window-state = "2.2.1" diff --git a/packages/hoppscotch-desktop/src-tauri/capabilities/default.json b/packages/hoppscotch-desktop/src-tauri/capabilities/default.json index bae2b66a..2ea3f542 100644 --- a/packages/hoppscotch-desktop/src-tauri/capabilities/default.json +++ b/packages/hoppscotch-desktop/src-tauri/capabilities/default.json @@ -14,6 +14,7 @@ "shell:allow-open", "store:default", "dialog:default", + "process:default", "updater:default", "fs:allow-write-text-file", "deep-link:default", diff --git a/packages/hoppscotch-desktop/src-tauri/capabilities/desktop.json b/packages/hoppscotch-desktop/src-tauri/capabilities/desktop.json index de53dc6c..71ca4a19 100644 --- a/packages/hoppscotch-desktop/src-tauri/capabilities/desktop.json +++ b/packages/hoppscotch-desktop/src-tauri/capabilities/desktop.json @@ -6,6 +6,7 @@ "linux" ], "permissions": [ + "updater:default", "window-state:default" ] } \ No newline at end of file diff --git a/packages/hoppscotch-desktop/src-tauri/src/lib.rs b/packages/hoppscotch-desktop/src-tauri/src/lib.rs index 3492790d..a8c48a19 100644 --- a/packages/hoppscotch-desktop/src-tauri/src/lib.rs +++ b/packages/hoppscotch-desktop/src-tauri/src/lib.rs @@ -6,6 +6,7 @@ use std::sync::OnceLock; use tauri::Emitter; use tauri_plugin_appload::VendorConfigBuilder; use tauri_plugin_deep_link::DeepLinkExt; +use tauri_plugin_window_state::StateFlags; static SERVER_PORT: OnceLock = OnceLock::new(); @@ -19,16 +20,22 @@ pub fn run() { tracing::info!("Starting Hoppscotch Desktop v{}", env!("CARGO_PKG_VERSION")); tauri::Builder::default() - .plugin(tauri_plugin_window_state::Builder::new().build()) + .plugin( + tauri_plugin_window_state::Builder::new() + .with_state_flags( + StateFlags::SIZE + | StateFlags::POSITION + | StateFlags::MAXIMIZED + | StateFlags::FULLSCREEN, + ) + .with_denylist(&["main"]) + .build(), + ) + .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_dialog::init()) - .setup(|app| { - let _ = app - .handle() - .plugin(tauri_plugin_updater::Builder::new().build())?; - Ok(()) - }) .setup(|app| { let handle = app.handle().clone(); tracing::info!(app_name = %app.package_info().name, "Configuring deep link handler"); diff --git a/packages/hoppscotch-desktop/src-tauri/tauri.conf.json b/packages/hoppscotch-desktop/src-tauri/tauri.conf.json index 030f2663..48863d55 100644 --- a/packages/hoppscotch-desktop/src-tauri/tauri.conf.json +++ b/packages/hoppscotch-desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Hoppscotch", - "version": "25.1.0-0", + "version": "25.2.0", "identifier": "io.hoppscotch.desktop", "build": { "beforeDevCommand": "pnpm dev", @@ -12,14 +12,12 @@ "app": { "windows": [ { - "title": "hoppscotch-desktop", - "decorations": false, + "title": "main", "width": 500, "height": 600, + "decorations": false, "alwaysOnTop": true, - "resizable": false, - "visible": false, - "shadow": true + "resizable": false } ], "security": { @@ -34,13 +32,6 @@ } } }, - "plugins": { - "deep-link": { - "desktop": { - "schemes": ["io.hoppscotch.desktop"] - } - } - }, "bundle": { "active": true, "targets": "all", @@ -54,6 +45,11 @@ ] }, "plugins": { + "deep-link": { + "desktop": { + "schemes": ["io.hoppscotch.desktop"] + } + }, "updater": { "active": true, "endpoints": ["https://releases.hoppscotch.com/hoppscotch-selfhost-desktop.json"], diff --git a/packages/hoppscotch-desktop/src/components.d.ts b/packages/hoppscotch-desktop/src/components.d.ts index cf602dd2..7b939288 100644 --- a/packages/hoppscotch-desktop/src/components.d.ts +++ b/packages/hoppscotch-desktop/src/components.d.ts @@ -8,6 +8,7 @@ export {} declare module 'vue' { export interface GlobalComponents { HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary'] + HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'] LayoutHeader: typeof import('./components/layout/LayoutHeader.vue')['default'] LayoutSidebar: typeof import('./components/layout/LayoutSidebar.vue')['default'] diff --git a/packages/hoppscotch-desktop/src/types/index.ts b/packages/hoppscotch-desktop/src/types/index.ts index 8880357a..12703429 100644 --- a/packages/hoppscotch-desktop/src/types/index.ts +++ b/packages/hoppscotch-desktop/src/types/index.ts @@ -8,3 +8,31 @@ export interface RecentInstance { export interface StoreSchema { recentInstances: RecentInstance[] } + +export enum UpdateStatus { + IDLE = "idle", + CHECKING = "checking", + AVAILABLE = "available", + NOT_AVAILABLE = "not_available", + DOWNLOADING = "downloading", + INSTALLING = "installing", + READY_TO_RESTART = "ready_to_restart", + ERROR = "error" +} + +export enum CheckResult { + AVAILABLE, + NOT_AVAILABLE, + TIMEOUT, + ERROR +} + +export interface UpdateState { + status: UpdateStatus; + version?: string; + message?: string; + progress?: { + downloaded: number; + total?: number; + }; +} diff --git a/packages/hoppscotch-desktop/src/utils/updater.ts b/packages/hoppscotch-desktop/src/utils/updater.ts new file mode 100644 index 00000000..529636d4 --- /dev/null +++ b/packages/hoppscotch-desktop/src/utils/updater.ts @@ -0,0 +1,156 @@ +import { check, type DownloadEvent } from "@tauri-apps/plugin-updater"; +import { relaunch } from "@tauri-apps/plugin-process"; +import { type LazyStore } from "@tauri-apps/plugin-store"; +import { UpdateStatus, CheckResult, UpdateState } from "~/types"; + +export class UpdaterService { + constructor(private store: LazyStore) {} + + async initialize(): Promise { + await this.saveUpdateState({ + status: UpdateStatus.IDLE + }); + } + + async checkForUpdates(timeout = 2000): Promise { + try { + await this.saveUpdateState({ + status: UpdateStatus.CHECKING + }); + + // Create a timeout promise, this is just to make sure we don't keep checking for updates indefinitely + // NOTE: Also `checkUpdate` tends to hang indefinitely in dev mode, but works in build + const timeoutPromise = new Promise((resolve) => { + setTimeout(() => { + console.log("Update check timeout reached, proceeding with app load"); + resolve(null); + }, timeout); + }); + + const updateResult = await Promise.race([ + check({ timeout }), + timeoutPromise + ]); + + // If we got a timeout (null), we treat it as no update available + // NOTE: We could maybe show more info but for now this works fine + if (!updateResult) { + console.log("Update check timed out or no update available"); + await this.saveUpdateState({ + status: UpdateStatus.NOT_AVAILABLE + }); + return CheckResult.TIMEOUT; + } + + const hasUpdates = updateResult.available; + + await this.saveUpdateState( + hasUpdates + ? { + status: UpdateStatus.AVAILABLE, + version: updateResult.version, + message: updateResult.body + } + : { + status: UpdateStatus.NOT_AVAILABLE + } + ); + + console.log("Update check result:", { + available: updateResult.available, + currentVersion: updateResult.currentVersion, + version: updateResult.version + }); + + return hasUpdates ? CheckResult.AVAILABLE : CheckResult.NOT_AVAILABLE; + } catch (error) { + console.error("Error checking for updates:", error); + await this.saveUpdateState({ + status: UpdateStatus.ERROR, + message: String(error) + }); + return CheckResult.ERROR; + } + } + + async downloadAndInstall(): Promise { + try { + const updateResult = await check(); + + if (!updateResult) { + throw new Error("No update available to install"); + } + + await this.saveUpdateState({ + status: UpdateStatus.DOWNLOADING, + progress: { + downloaded: 0, + total: undefined + } + }); + + let totalBytes: number | undefined; + let downloadedBytes = 0; + + await updateResult.downloadAndInstall( + (event: DownloadEvent) => { + if (event.event === 'Started') { + totalBytes = event.data.contentLength; + this.saveUpdateState({ + status: UpdateStatus.DOWNLOADING, + progress: { + downloaded: 0, + total: totalBytes + } + }); + } else if (event.event === 'Progress') { + downloadedBytes += event.data.chunkLength; + this.saveUpdateState({ + status: UpdateStatus.DOWNLOADING, + progress: { + downloaded: downloadedBytes, + total: totalBytes + } + }); + } else if (event.event === 'Finished') { + this.saveUpdateState({ + status: UpdateStatus.INSTALLING + }); + } + } + ); + + // If we reach here, it means the app hasn't restarted automatically + // Mark as ready to restart + await this.saveUpdateState({ + status: UpdateStatus.READY_TO_RESTART + }); + + } catch (error) { + console.error("Error installing updates:", error); + await this.saveUpdateState({ + status: UpdateStatus.ERROR, + message: String(error) + }); + throw error; + } + } + + async restartApp(): Promise { + try { + await relaunch(); + } catch (error) { + console.error("Failed to restart app:", error); + throw error; + } + } + + private async saveUpdateState(state: UpdateState): Promise { + try { + await this.store.set("updateState", state); + await this.store.save(); + } catch (error) { + console.error("Failed to save update state:", error); + } + } +} diff --git a/packages/hoppscotch-desktop/src/views/Home.vue b/packages/hoppscotch-desktop/src/views/Home.vue index 14bd721c..b68142e2 100644 --- a/packages/hoppscotch-desktop/src/views/Home.vue +++ b/packages/hoppscotch-desktop/src/views/Home.vue @@ -1,26 +1,91 @@ @@ -29,14 +94,25 @@ import { ref, onMounted } from "vue"; import { LazyStore } from '@tauri-apps/plugin-store'; import { load } from "@hoppscotch/plugin-appload"; -import { invoke } from "@tauri-apps/api/core"; +import { getVersion } from '@tauri-apps/api/app'; -import IconLucideCloud from "~icons/lucide/cloud" -import IconLucideAlertCircle from "~icons/lucide/alert-circle" -import IconLucideRefreshCw from "~icons/lucide/refresh-cw" +import { UpdateStatus, CheckResult, UpdateState } from "~/types"; +import { UpdaterService } from "~/utils/updater"; -const HOME_STORE_PATH = "hopp.store.json"; -const APP_STORE_PATH = "hoppscotch.hoppscotch.store"; +import IconLucideAlertCircle from "~icons/lucide/alert-circle"; +import IconLucideRefreshCw from "~icons/lucide/refresh-cw"; +import IconLucideDownload from "~icons/lucide/download"; + +const APP_STORE_PATH = "hoppscotch-desktop.store"; + +enum AppState { + LOADING = "loading", + UPDATE_AVAILABLE = "update_available", + UPDATE_IN_PROGRESS = "update_in_progress", + UPDATE_READY = "update_ready", + ERROR = "error", + LOADED = "loaded" +} interface VendoredInstance { type: "vendored"; @@ -51,188 +127,115 @@ interface ConnectionState { message?: string; } -interface StoredData { - schemaVersion: number; - metadata: { - createdAt: string; - updatedAt: string; - namespace: string; - }; - data: unknown; -} - -interface StoreData { - data: { - [namespace: string]: { - [key: string]: StoredData; - }; - }; -} - -const home_store = new LazyStore(HOME_STORE_PATH); -const app_store = new LazyStore(APP_STORE_PATH); -const isLoading = ref(true); +const appStore = new LazyStore(APP_STORE_PATH); +const appState = ref(AppState.LOADING); +const updateStatus = ref(""); +const updateMessage = ref(""); +const downloadProgress = ref<{ downloaded: number; total?: number }>({ downloaded: 0 }); const error = ref(""); +const statusMessage = ref("Initializing..."); +const appVersion = ref("..."); + +const updaterService = new UpdaterService(appStore); + +const formatBytes = (bytes: number): string => { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +}; const saveConnectionState = async (state: ConnectionState) => { try { - await home_store.set("connectionState", state); - await home_store.save(); + await appStore.set("connectionState", state); + await appStore.save(); } catch (err) { console.error("Failed to save connection state:", err); } }; -const migrateFromLocalStorage = async () => { - const keyMappings = { - settings: "settings", - collections: "restCollections", - collectionsGraphql: "gqlCollections", - environments: "environments", - history: "restHistory", - graphqlHistory: "gqlHistory", - WebsocketRequest: "websocket", - SocketIORequest: "socketio", - SSERequest: "sse", - MQTTRequest: "mqtt", - globalEnv: "globalEnv", - restTabState: "restTabs", - gqlTabState: "gqlTabs", - secretEnvironments: "secretEnvironments" - }; +const setupUpdateStateWatcher = async () => { + const unsubscribe = await appStore.onKeyChange("updateState", (newValue) => { + if (!newValue) return; - console.log("Starting migration from localStorage to home_store..."); - let migratedCount = 0; + updateStatus.value = newValue.status; + updateMessage.value = newValue.message || ""; - const storeData: StoreData = { - data: { - "persistence.v1": {} + if (newValue.progress) { + downloadProgress.value = newValue.progress; } - }; - console.log("Current localStorage keys:", Object.keys(localStorage)); - - for (const [oldKey, newKey] of Object.entries(keyMappings)) { - const data = localStorage.getItem(oldKey); - if (data) { - try { - console.log(`Migrating ${oldKey} to persistence.v1.${newKey}...`); - let parsedData; - - try { - parsedData = JSON.parse(data); - } catch (e) { - console.error(`Failed to parse ${oldKey} data:`, e); - continue; - } - - const storedData: StoredData = { - schemaVersion: 1, - metadata: { - createdAt: new Date().toISOString(), - namespace: "persistence.v1", - updatedAt: new Date().toISOString(), - }, - data: parsedData - }; - - storeData.data["persistence.v1"][newKey] = storedData; - - localStorage.removeItem(oldKey); - migratedCount++; - console.log(`Successfully migrated ${oldKey}`); - } catch (err) { - console.error(`Failed to migrate ${oldKey}:`, err); - } + if (newValue.status === UpdateStatus.AVAILABLE) { + appState.value = AppState.UPDATE_AVAILABLE; + } else if (newValue.status === UpdateStatus.ERROR) { + error.value = newValue.message || "Unknown error"; + appState.value = AppState.ERROR; + } else if (newValue.status === UpdateStatus.DOWNLOADING || newValue.status === UpdateStatus.INSTALLING) { + appState.value = AppState.UPDATE_IN_PROGRESS; + } else if (newValue.status === UpdateStatus.READY_TO_RESTART) { + appState.value = AppState.UPDATE_READY; } - } + }); - await app_store.set('data', storeData.data); - await app_store.save(); - console.log(`Migration complete. Migrated ${migratedCount} items.`); + return unsubscribe; }; -interface UpdateCheckResult { - status: "completed" | "timeout"; - hasUpdates?: boolean; -} +const installUpdate = async () => { + try { + appState.value = AppState.UPDATE_IN_PROGRESS; + await updaterService.downloadAndInstall(); + // In a rare occurance where we reach here but automatic restart didn't happen, + // we'll just show a restart button instead + } catch (err) { + const errorMessage = err instanceof Error ? err.message : String(err); + error.value = `Failed to install update: ${errorMessage}`; + appState.value = AppState.ERROR; + } +}; + +const skipUpdate = async () => { + await loadVendored(); +}; + +const restartApp = async () => { + try { + await updaterService.restartApp(); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : String(err); + error.value = `Failed to restart app: ${errorMessage}`; + appState.value = AppState.ERROR; + } +}; const loadVendored = async () => { - isLoading.value = true; - error.value = ""; - try { - console.log("Initializing home_store and starting migration process"); - await home_store.init(); - await app_store.init(); + statusMessage.value = "Loading application..."; - console.log(`Using home_store path: ${HOME_STORE_PATH}`); - console.log("home_Store initialized successfully"); + const vendoredInstance: VendoredInstance = { + type: "vendored", + displayName: "Vendored", + version: "vendored" + }; - try { - await migrateFromLocalStorage(); - console.log("Migration completed successfully"); - } catch (migrationError) { - console.error("Migration error:", migrationError); + await saveConnectionState({ + status: "connected", + instance: vendoredInstance + }); + + console.log("Loading vendored app..."); + const loadResp = await load({ + bundleName: "Hoppscotch", + window: { title: "Hoppscotch" } + }); + + if (!loadResp.success) { + throw new Error("Failed to load Hoppscotch Vendored"); } - let shouldProceedWithLoad = true; - - try { - console.log("Checking for updates before loading app..."); - - const timeoutPromise: Promise = new Promise((resolve) => { - setTimeout(() => { - console.log("Update check timeout reached, proceeding with app load"); - resolve({ status: "timeout" }); - }, 2000); // TODO: 2s shoud be good? - }); - - const result = await Promise.race([ - invoke('check_updates_available').then(hasUpdates => ({ status: "completed", hasUpdates })), - timeoutPromise - ]); - - console.log("Update check result:", result); - - if (result.status === "completed" && result.hasUpdates) { - console.log("Updates available, handling before loading app"); - shouldProceedWithLoad = false; - - await invoke('install_updates_and_restart'); - // This point would only be reached if install_updates_and_restart - // doesn't actually restart the app - return; - } - } catch (updateError) { - console.error("Update check error:", updateError); - // Continue with loading the app despite update check errors - } - - if (shouldProceedWithLoad) { - const vendoredInstance: VendoredInstance = { - type: "vendored", - displayName: "Vendored", - version: "vendored" - }; - - await saveConnectionState({ - status: "connected", - instance: vendoredInstance - }); - - console.log("Loading vendored app..."); - const loadResp = await load({ - bundleName: "Hoppscotch", - window: { title: "Hoppscotch" } - }); - - if (!loadResp.success) { - throw new Error("Failed to load Hoppscotch Vendored"); - } - - console.log("Vendored app loaded successfully"); - } + console.log("Vendored app loaded successfully"); } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); console.error("Error loading vendored app:", errorMessage); @@ -244,23 +247,48 @@ const loadVendored = async () => { message: errorMessage }); - isLoading.value = false; + appState.value = AppState.ERROR; + } +}; + +const initialize = async () => { + appState.value = AppState.LOADING; + error.value = ""; + downloadProgress.value = { downloaded: 0 }; + + try { + try { + appVersion.value = await getVersion(); + } catch (error) { + console.error("Failed to get app version:", error); + appVersion.value = "unknown"; + } + + statusMessage.value = "Initializing stores..."; + await appStore.init(); + await updaterService.initialize(); + + await setupUpdateStateWatcher(); + + statusMessage.value = "Checking for updates..."; + const checkResult = await updaterService.checkForUpdates(); + + if (checkResult === CheckResult.AVAILABLE) { + console.log("Updates available, prompting for install"); + appState.value = AppState.UPDATE_AVAILABLE; + return; + } + + await loadVendored(); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : String(err); + console.error("Initialization error:", errorMessage); + error.value = errorMessage; + appState.value = AppState.ERROR; } }; onMounted(() => { - loadVendored(); + initialize(); }); - - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34f97ada..0e4069c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -984,12 +984,18 @@ importers: '@tauri-apps/api': specifier: 2.1.1 version: 2.1.1 + '@tauri-apps/plugin-process': + specifier: 2.2.0 + version: 2.2.0 '@tauri-apps/plugin-shell': specifier: 2.0.1 version: 2.0.1 '@tauri-apps/plugin-store': specifier: 2.2.0 version: 2.2.0 + '@tauri-apps/plugin-updater': + specifier: 2.5.1 + version: 2.5.1 '@vueuse/core': specifier: 11.1.0 version: 11.1.0(vue@3.5.12(typescript@5.7.2)) @@ -5247,6 +5253,9 @@ packages: '@tauri-apps/plugin-fs@2.0.2': resolution: {integrity: sha512-4YZaX2j7ta81M5/DL8aN10kTnpUkEpkPo1FTYPT8Dd0ImHe3azM8i8MrtjrDGoyBYLPO3zFv7df/mSCYF8oA0Q==} + '@tauri-apps/plugin-process@2.2.0': + resolution: {integrity: sha512-uypN2Crmyop9z+KRJr3zl71OyVFgTuvHFjsJ0UxxQ/J5212jVa5w4nPEYjIewcn8bUEXacRebwE6F7owgrbhSw==} + '@tauri-apps/plugin-shell@2.0.0': resolution: {integrity: sha512-OpW2+ycgJLrEoZityWeWYk+6ZWP9VyiAfbO+N/O8VfLkqyOym8kXh7odKDfINx9RAotkSGBtQM4abyKfJDkcUg==} @@ -5256,6 +5265,9 @@ packages: '@tauri-apps/plugin-store@2.2.0': resolution: {integrity: sha512-hJTRtuJis4w5fW1dkcgftsYxKXK0+DbAqurZ3CURHG5WkAyyZgbxpeYctw12bbzF9ZbZREXZklPq8mocCC3Sgg==} + '@tauri-apps/plugin-updater@2.5.1': + resolution: {integrity: sha512-7fNJraKRbKkxguMY5lG2W20pBvAUkLu+cqnbu0UcK7DqeZgrAnNECcGBIDG6fJ6C+0fAp2V2dMIgznhffOBCcg==} + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -17430,6 +17442,10 @@ snapshots: dependencies: '@tauri-apps/api': 2.1.1 + '@tauri-apps/plugin-process@2.2.0': + dependencies: + '@tauri-apps/api': 2.1.1 + '@tauri-apps/plugin-shell@2.0.0': dependencies: '@tauri-apps/api': 2.0.2 @@ -17442,6 +17458,10 @@ snapshots: dependencies: '@tauri-apps/api': 2.1.1 + '@tauri-apps/plugin-updater@2.5.1': + dependencies: + '@tauri-apps/api': 2.1.1 + '@trysound/sax@0.2.0': optional: true