diff --git a/.gitignore b/.gitignore index 3995e6c..e5c7dc0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,3 @@ node_modules/ *.p12 .DS_Store media/teaser/out/ - -# desktop app (apps/desktop): build output, compiled sidecars, Tauri generated schemas -apps/desktop/dist/ -apps/desktop/src-tauri/target/ -apps/desktop/src-tauri/binaries/ -apps/desktop/src-tauri/gen/ -apps/desktop/.screens/ -.scratch/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d47dfb..f206e63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,40 +1,5 @@ # Changelog -## Unreleased - -- **Desktop machine interface** (`serve --stdio`): JSON-RPC session for the coming desktop app, built on the MCP core. - Typed methods with argument schemas, structured results, progress notifications, cancellation, `project.inspect` - and `portfolio.status`: apps across iOS and Android with identity, config check, release trains (current and next - version on both stores) and catalog parity (`live` reads App Store and Play status). `system.tools` reports ffmpeg, - Chrome, Xcode tools, simctl, adb and git with versions; `STOREBOUND_TOOL_PATH` / `STOREBOUND_CHROME` point the CLI - at app-managed copies. Writes are dry runs unless the host - sends a single-use confirm token bound to the method, arguments and a short expiry (HMAC with a per-session secret - handed over on stdin). No submit / release / publish method exists. Under `serve`, terminal confirmations always - answer no. -- `--json` on `check` (and `check --aso`), `apple testflight status`, `apple ppo status`, `apple next-build`, - `play next-version-code`, `analytics compare` and `prices import` (read-only diff); new `inspect` command. -- **Desktop app shell** (`apps/desktop`, not released yet): Tauri 2 + React 19, app-centric (Portfolio, App with - Releases / Catalog / Listings / Health / Reviews / TestFlight, App folders, Accounts, Dependencies, Settings), store - keys in the OS keychain, native confirm dialogs with the dry run before any write. See `docs/desktop.md`. -- `--json` on `catalog diff` goes through the same machine-readable result line. -- **`storebound doctor`** (`--json`): ffmpeg, ffprobe, Chrome, playwright, Xcode tools, simctl, adb and git with - versions (the same detection as the desktop Dependencies screen) and the install commands for this OS. It runs none. -- `playwright` is an optional peer dependency (it was only documented before). The compiled desktop sidecar bundles - `playwright-core` and renders with the app's Chrome for Testing (`STOREBOUND_CHROME`), so desktop users need neither. -- `inspect --icon` / `inspect --listings` (listing completeness per language on both stores, rendered screenshots), - `reviews drafts [--json]` (local reply drafts with the send checks), `reviews approve [--undo] [--dry]`, - `init --dry`; templates are embedded so `init` works from a single-file binary. -- `apple status --json` includes the phased release of the newest live versions; release trains now count a staged - rollout or iOS phased release (also halted / paused) as the current train. -- **Desktop D2** (`apps/desktop`): redesigned shell (command palette, toasts with progress and cancel, skeletons, - illustrated empty states, first-run guide), release timeline per app, catalog parity matrix, listing completeness, - health sparklines against the Play thresholds, reviews inbox with star distribution and draft approval, TestFlight - groups and builds, store readiness with fix hints; offline cache of the last reads (SQLite, "last read" stamps); - one-click ffmpeg (pinned SHA-256) and Chrome for Testing (Google's code signature) downloads into app data; - English and Turkish with an i18n check, 7:1 text contrast checked in both themes. -- `tools/build-sidecars.ts`: single-file executables for macOS (arm64, x64), Linux (x64, arm64) and Windows (x64), - re-signed ad hoc on macOS and checked for build-machine paths. The MCP server now also runs from such a binary. - ## 0.5.0 · 2026-10-11 Apps and games: storebound is for every mobile app, not only games, and the in-app catalog is complete on both stores. diff --git a/README.md b/README.md index a632148..1c6d164 100644 --- a/README.md +++ b/README.md @@ -175,15 +175,12 @@ run in the background with a log (`storebound shots render > .storebound/render. | `init [--profile app\|game\|subscription-app]` | local | copy the templates of a profile (default app), gitignore `.storebound/` | | `install --agent ` | local | skills + MCP entry for an AI tool | | `mcp` | local | stdio MCP server wrapping the commands | -| `serve --stdio` | local | JSON-RPC session for the storebound desktop app (see [Desktop machine interface](#desktop-machine-interface)) | -| `inspect [--json]` | local | offline summary of the config: name, store ids, languages, products | | `check` | local | config, copy limits, data safety, products, subscriptions, profile (delete-account URL, demo account, terms URL), files; warns on untranslated release notes | | `catalog diff [--store] [--threshold 10]` | API, read-only | one catalog vs both stores: missing / extra / mismatched products and subscriptions, US and override prices, locales, offers, per-region price differences between the stores; exit 1 on mismatches | | `check --aso` | local | keyword audit: App Store keyword field rules, Play stuffing / claims | | `validate [--apple] [--play]` | API, read-only | readiness report with ordered blockers plus the local renders checks (`--no-local` skips); exit 1 while any blocker is left | | `whats-new [--since ]` | local | draft player-facing release notes from git log into both listing files | | `privacy-manifest --ios ` | local | PrivacyInfo.xcprivacy, required-reason APIs, SDKs without manifests | -| `doctor [--json]` | local | which local tools are here (ffmpeg, Chrome, playwright, Xcode, adb, git) and how to install the missing ones | | `icons` | local | App Store 1024 (no alpha), Play 512, web, Capacitor sources, Android legacy launcher icons | @@ -380,19 +377,6 @@ per-request `_meta`, `resultType`, cacheable `tools/list`) and, on the same proc Manual config for a tool that `install` does not know: command `bunx` with args `["storebound", "mcp"]` (or the absolute path of `bin/storebound` plus `mcp`). -### Desktop machine interface - -`storebound serve --stdio` is what the desktop app (`apps/desktop`, in progress) runs as its sidecar: newline-delimited -JSON-RPC on the same core as the MCP server (tool list, validation, forbidden flags, child-process runner), with typed -methods (`check`, `validate`, `apple.status`, `play.listing`, ... plus `project.inspect` and `portfolio.status` over -many project folders), structured results (the commands' `--json` output), progress notifications and cancellation. -The first request is `session.hello` with a per-session secret only the host knows. Write methods are dry runs unless -the call carries a single-use confirm token (HMAC over method, arguments and expiry) that the host mints after the -owner confirmed a native dialog showing that dry run. There is no method that submits, releases or publishes; Play -testing releases and phased release changes stay out of the desktop profile too. Types: `cli/lib/desktop-protocol.ts`. -`bun tools/build-sidecars.ts` compiles the CLI for macOS, Linux and Windows (`bun build --compile`). Design, safety -model and the Dependencies screen (tool sources and licenses): [docs/desktop.md](docs/desktop.md). - ## Safety model - **Credentials** live in env vars or the gitignored `.storebound/credentials.json`, never in the config or the repo. diff --git a/apps/desktop/index.html b/apps/desktop/index.html deleted file mode 100644 index 3036910..0000000 --- a/apps/desktop/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - storebound - - -
- - - diff --git a/apps/desktop/package.json b/apps/desktop/package.json deleted file mode 100644 index 3255438..0000000 --- a/apps/desktop/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@storebound/desktop", - "version": "0.1.0", - "private": true, - "license": "MIT", - "type": "module", - "description": "storebound desktop: every game's releases on both stores in one window (Tauri shell around the storebound CLI sidecar).", - "scripts": { - "dev": "vite --host 127.0.0.1 --port 1420 --strictPort", - "build": "tsc --noEmit && vite build", - "typecheck": "tsc --noEmit", - "test": "bun test tests", - "sidecar": "bun ../../tools/build-sidecars.ts --host", - "tauri": "tauri", - "app:dev": "bun run sidecar && tauri dev", - "app:build:debug": "bun run sidecar && tauri build --debug --no-bundle" - }, - "dependencies": { - "@tauri-apps/api": "^2.12.0", - "react": "^19.2.0", - "react-dom": "^19.2.0" - }, - "devDependencies": { - "@tauri-apps/cli": "^2.12.1", - "@types/react": "^19.2.0", - "@types/react-dom": "^19.2.0", - "@vitejs/plugin-react": "^4.7.0", - "typescript": "5.9.3", - "vite": "^6.3.0" - } -} diff --git a/apps/desktop/scripts/screens.ts b/apps/desktop/scripts/screens.ts deleted file mode 100644 index 157489b..0000000 --- a/apps/desktop/scripts/screens.ts +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bun -// Screenshots of every desktop screen in light and dark, from the Vite dev server in fixture mode (no Tauri, no store -// accounts), plus a few interaction checks (command palette, toast progress, keyboard focus). Dev tool only. -// cd apps/desktop && VITE_FIXTURES=1 bunx vite --port 1431 & -// bun scripts/screens.ts --base http://127.0.0.1:1431 --out [--only portfolio,app:health] [--themes light,dark] -// Uses playwright (root devDependency) with the installed Google Chrome (channel "chrome"). -import { mkdirSync } from 'node:fs'; -import { join } from 'node:path'; -import { chromium, type Page } from 'playwright'; - -const arg = (k: string, d: string) => { const i = process.argv.indexOf(`--${k}`); return i >= 0 ? process.argv[i + 1] ?? d : d; }; -const base = arg('base', 'http://127.0.0.1:1431'); -const out = arg('out', join(import.meta.dir, '..', '.screens')); -const only = arg('only', '').split(',').filter(Boolean); -const themes = arg('themes', 'light,dark').split(','); -const lang = arg('lang', 'en'); -const suffix = arg('suffix', ''); - -export const SCREENS: [string, string][] = [ - ['portfolio', 'portfolio'], ['releases', 'app:releases'], ['releases-2', 'app:releases:2'], ['catalog', 'app:catalog'], ['listings', 'app:listings'], - ['health', 'app:health'], ['reviews', 'app:reviews'], ['testflight', 'app:testflight'], ['checks', 'app:checks'], - ['apps', 'apps'], ['accounts', 'accounts'], ['dependencies', 'dependencies'], ['settings', 'settings'], - ['palette', 'palette'], ['welcome-1', 'welcome:1'], ['welcome-2', 'welcome:2'], ['welcome-3', 'welcome:3'], ['welcome-4', 'welcome:4'], ['welcome-5', 'welcome:5'], -]; - -async function settle(page: Page) { - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(900); -} - -mkdirSync(out, { recursive: true }); -const browser = await chromium.launch({ channel: 'chrome', headless: true }); -const files: string[] = []; -for (const theme of themes) { - const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1, colorScheme: theme === 'dark' ? 'dark' : 'light', reducedMotion: 'reduce' }); - const page = await ctx.newPage(); - const errors: string[] = []; - page.on('pageerror', e => errors.push(String(e))); - page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); }); - for (const [name, start] of SCREENS) { - if (only.length && !only.includes(name)) continue; - await page.goto(`${base}/?start=${encodeURIComponent(start)}&theme=${theme}&lang=${lang}`); - await settle(page); - const f = join(out, `d2-${name}${suffix}-${theme}${lang === 'en' ? '' : `-${lang}`}.png`); - await page.screenshot({ path: f }); - files.push(f); - } - if (!only.length || only.includes('toast')) { - // a background job: Read the stores, caught mid-way with its progress toast - await page.goto(`${base}/?start=portfolio&theme=${theme}&lang=${lang}`); - await settle(page); - await page.getByRole('button', { name: /Read the stores|Mağazaları oku/ }).first().click(); - await page.waitForTimeout(900); - const f = join(out, `d2-toast${suffix}-${theme}${lang === 'en' ? '' : `-${lang}`}.png`); - await page.screenshot({ path: f }); - files.push(f); - } - if (errors.length) console.error(`${theme}: page errors:\n ${[...new Set(errors)].join('\n ')}`); - await ctx.close(); -} -await browser.close(); -console.log(files.join('\n')); diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock deleted file mode 100644 index d262f3f..0000000 --- a/apps/desktop/src-tauri/Cargo.lock +++ /dev/null @@ -1,6262 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" - -[[package]] -name = "alloc-stdlib" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android_system_properties" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "arbitrary" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed" -dependencies = [ - "derive_arbitrary", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "async-signal" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" - -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" -dependencies = [ - "serde_core", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "blocking" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "brotli" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "6.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.13.2", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.21", -] - -[[package]] -name = "cargo_toml" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f4b26e751e711a5302649417f2da046dce6391b2ea30a4820f37462314f0b9" -dependencies = [ - "semver", - "serde", - "toml 1.1.8+spec-1.1.0", -] - -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - -[[package]] -name = "cc" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a649af8a827553c29fb0cb4bd4a6f1a0dd695bd3232b9bc98bd9c8a3ffbb8b" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3" -dependencies = [ - "fnv", - "uuid", - "web-time", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "combine" -version = "4.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.17", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "cookie" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" -dependencies = [ - "bitflags 2.13.2", - "core-foundation 0.10.1", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.13.2", - "core-foundation 0.10.1", - "libc", -] - -[[package]] -name = "core_detect" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a" -dependencies = [ - "quote", - "syn 3.0.7", -] - -[[package]] -name = "ctor" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" - -[[package]] -name = "darling" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 3.0.7", -] - -[[package]] -name = "darling_macro" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" -dependencies = [ - "darling_core", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "dbus" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" -dependencies = [ - "libc", - "libdbus-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "dbus-secret-service" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" -dependencies = [ - "aes", - "block-padding", - "cbc", - "dbus", - "fastrand", - "hkdf", - "num", - "once_cell", - "sha2", - "zeroize", -] - -[[package]] -name = "defmt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" -dependencies = [ - "defmt-parser", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror 2.0.21", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "serde_core", -] - -[[package]] -name = "derive_arbitrary" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.119", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.13.2", - "block2", - "libc", - "objc2", -] - -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "dlv-list" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" -dependencies = [ - "const-random", -] - -[[package]] -name = "dom_query" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc" -dependencies = [ - "bit-set", - "cssparser", - "foldhash", - "html5ever", - "precomputed-hash", - "selectors", - "tendril", -] - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "embed-resource" -version = "3.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6168487b5e7eb2673e6efe1200b1848016e436a6ca0b8031bf04bd9d493a249f" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 1.1.8+spec-1.1.0", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "encoding_rs" -version = "0.8.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" -dependencies = [ - "cfg-if", - "core_detect", - "multiversion_no_op", - "rustversion", - "scopeguard", - "simdutf8", -] - -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "filetime" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" -dependencies = [ - "cfg-if", - "libc", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" - -[[package]] -name = "flate2" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" -dependencies = [ - "crc32fast", - "miniz_oxide 0.9.1", - "zlib-rs", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-executor" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "futures-sink" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", -] - -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.13.2", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "hashlink" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" -dependencies = [ - "hashbrown 0.16.1", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "html5ever" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" -dependencies = [ - "log", - "markup5ever", -] - -[[package]] -name = "http" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" -dependencies = [ - "atomic-waker", - "bytes", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" -dependencies = [ - "base64 0.23.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "httparse", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" -dependencies = [ - "byteorder", - "png 0.17.16", -] - -[[package]] -name = "icu_collections" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" - -[[package]] -name = "icu_properties" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - -[[package]] -name = "icu_provider" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4" -dependencies = [ - "cfb", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "ipnet" -version = "2.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jiff" -version = "0.2.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" -dependencies = [ - "defmt", - "jiff-core", - "jiff-static", - "jiff-tzdb-platform", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", - "windows-link", -] - -[[package]] -name = "jiff-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" -dependencies = [ - "defmt", - "log", -] - -[[package]] -name = "jiff-static" -version = "0.2.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" -dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jiff-tzdb" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7" - -[[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" -dependencies = [ - "jiff-tzdb", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.21", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "js-sys" -version = "0.3.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 2.0.21", -] - -[[package]] -name = "jsonptr" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keyboard-types" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" -dependencies = [ - "bitflags 2.13.2", - "serde", -] - -[[package]] -name = "keyring" -version = "3.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" -dependencies = [ - "byteorder", - "dbus-secret-service", - "log", - "secret-service", - "security-framework 2.11.1", - "security-framework 3.7.0", - "windows-sys 0.60.2", - "zeroize", -] - -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.190" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" - -[[package]] -name = "libdbus-sys" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" -dependencies = [ - "pkg-config", -] - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libredox" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" -dependencies = [ - "libc", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "mac-notification-sys" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" -dependencies = [ - "cc", - "log", - "objc2", - "objc2-foundation", - "time", - "uuid", -] - -[[package]] -name = "markup5ever" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" -dependencies = [ - "log", - "tendril", - "web_atoms", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minisign-verify" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871285dc19d8d0ebe0eef3d0e99a205f2a71363b122632cbbfa4a6c370a960ce" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "muda" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.21", - "windows-sys 0.61.2", -] - -[[package]] -name = "multiversion_no_op" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.13.2", - "jni-sys 0.3.1", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.13.2", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "notify-rust" -version = "4.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4587364a9a0074333429b3df75a30a205340c56a536ca3eb6ca0e59b87bbf8af" -dependencies = [ - "futures-lite", - "log", - "mac-notification-sys", - "serde", - "tauri-winrt-notification", - "zbus 5.19.0", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "objc2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.13.2", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.13.2", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.13.2", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.13.2", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-osa-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-app-kit", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.13.2", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.13.2", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "open" -version = "5.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" -dependencies = [ - "dunce", - "is-wsl", - "libc", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-multimap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" -dependencies = [ - "dlv-list", - "hashbrown 0.14.5", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "os_pipe" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "osakit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" -dependencies = [ - "objc2", - "objc2-foundation", - "objc2-osa-kit", - "serde", - "serde_json", - "thiserror 2.0.21", -] - -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros", - "phf_shared", - "serde", -] - -[[package]] -name = "phf_codegen" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" - -[[package]] -name = "plist" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb" -dependencies = [ - "base64 0.23.1", - "indexmap 2.14.2", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", -] - -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.13.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "portable-atomic-util" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "potential_utf" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.17+spec-1.1.0", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.2", -] - -[[package]] -name = "redox_users" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" -dependencies = [ - "libredox", - "thiserror 2.0.21", -] - -[[package]] -name = "ref-cast" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "reqwest" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" -dependencies = [ - "base64 0.23.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "rfd" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" -dependencies = [ - "block2", - "dispatch2", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.60.2", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rsqlite-vfs" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" -dependencies = [ - "hashbrown 0.16.1", - "thiserror 2.0.21", -] - -[[package]] -name = "rusqlite" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" -dependencies = [ - "bitflags 2.13.2", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", - "sqlite-wasm-rs", -] - -[[package]] -name = "rust-ini" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" -dependencies = [ - "bitflags 2.13.2", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework 3.7.0", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni 0.22.4", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework 3.7.0", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" - -[[package]] -name = "rustls-webpki" -version = "0.103.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.119", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "secret-service" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" -dependencies = [ - "aes", - "cbc", - "futures-util", - "generic-array", - "hkdf", - "num", - "once_cell", - "rand 0.8.8", - "serde", - "sha2", - "zbus 4.4.0", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.13.2", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.13.2", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "selectors" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d" -dependencies = [ - "bitflags 2.13.2", - "cssparser", - "derive_more", - "log", - "new_debug_unreachable", - "phf", - "phf_codegen", - "precomputed-hash", - "rustc-hash", - "servo_arc", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "serde_json" -version = "1.0.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1741ab7a6cc54a03a89b5d563ed60075c277d9e3cfa73ad0c1f23f23974703c6" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_with" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f" -dependencies = [ - "base64 0.23.1", - "bs58", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.14.2", - "jiff", - "schemars 0.9.0", - "schemars 1.2.2", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "servo_arc" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "sha1" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shared_child" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "607549934f6cc26b89cfecfdc46fa90f1e5d1536a68349b0c3a4f9d1c0d37959" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "siphasher" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "sqlite-wasm-rs" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" -dependencies = [ - "cc", - "js-sys", - "rsqlite-vfs", - "wasm-bindgen", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "storebound-desktop" -version = "0.1.0" -dependencies = [ - "base64 0.22.1", - "getrandom 0.3.4", - "hmac", - "keyring", - "reqwest", - "rusqlite", - "rustls", - "serde", - "serde_json", - "sha2", - "tauri", - "tauri-build", - "tauri-plugin-deep-link", - "tauri-plugin-dialog", - "tauri-plugin-notification", - "tauri-plugin-opener", - "tauri-plugin-process", - "tauri-plugin-shell", - "tauri-plugin-single-instance", - "tauri-plugin-updater", - "tauri-plugin-window-state", - "tempfile", - "tokio", - "url", - "zip", -] - -[[package]] -name = "string_cache" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - -[[package]] -name = "string_cache_codegen" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "swift-rs" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefd87076fd385308ee4aff597256c902f1fbb4d031de1558efc12f8ffefadd9" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags 2.13.2", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.37.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37f381f4e048e6cdf038b5705f8cf14ad108279d46eb968140a7b291aba9400" -dependencies = [ - "bitflags 2.13.2", - "block2", - "core-foundation 0.10.1", - "core-graphics", - "crossbeam-channel", - "dbus", - "dispatch2", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni 0.21.1", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "parking_lot", - "percent-encoding", - "raw-window-handle", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "windows-core", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tar" -version = "0.4.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d061d042ccd2df9c9f1508c1e63d835278c0a4c8801eb41764080e51f2eaaf34" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni 0.21.1", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.21", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows", -] - -[[package]] -name = "tauri-build" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59563ca5b331fd97f27ee9672ec8d36d33bb3c9e2c9f6710a1a5687256052e45" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e50213e463086ecc283d7e9f51c30cc0fd304ad457425158018be26dbf0d0a80" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png 0.18.1", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.119", - "tauri-utils", - "thiserror 2.0.21", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8129fb43bb6e0d6b73ad3315cd172beaab17cd28899c594b1b32c80edbbfd99c" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1140cf34a3b3b836a13103dcab17f18831d5cc3534cbd435dc01a5c6daa65aa2" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "walkdir", -] - -[[package]] -name = "tauri-plugin-deep-link" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac7a92a46ab5c88f44532ca50906d6e448a948d4ddf8c5376ab125e3260f736" -dependencies = [ - "dunce", - "plist", - "rust-ini", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.21", - "tracing", - "url", - "windows-registry", - "windows-result", -] - -[[package]] -name = "tauri-plugin-dialog" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca325d6e66e9170f2e1b9631722e4e544bcbc51781575d266abdaba697bd51" -dependencies = [ - "log", - "raw-window-handle", - "rfd", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.21", - "url", -] - -[[package]] -name = "tauri-plugin-fs" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213fae2d85d4b76b06de5915d1862db9fa74541171f9d2c15da6ac9bd6e788c6" -dependencies = [ - "anyhow", - "dunce", - "glob", - "log", - "objc2-foundation", - "percent-encoding", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.21", - "toml 1.1.8+spec-1.1.0", - "url", -] - -[[package]] -name = "tauri-plugin-notification" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf3cce3ea492b3a8a7f431a4e5dd5d31dc41b2a4a243faa660cbe8095a577bd" -dependencies = [ - "log", - "notify-rust", - "rand 0.9.5", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "thiserror 2.0.21", - "time", - "url", - "zbus 5.19.0", -] - -[[package]] -name = "tauri-plugin-opener" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85dead674ee41937d042ee855d785f07ee67206f19402200c12b9255964d5ac8" -dependencies = [ - "dunce", - "glob", - "objc2-app-kit", - "objc2-foundation", - "open", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.21", - "url", - "windows", - "zbus 5.19.0", -] - -[[package]] -name = "tauri-plugin-process" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74ae3d8549ae564c0397377373e262f340908eb23a5b9fbe03f2a00007dc0038" -dependencies = [ - "tauri", - "tauri-plugin", -] - -[[package]] -name = "tauri-plugin-shell" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8567d42b2c7264355daa1df415ec77f52aa42e885924c86a969be2e920e05521" -dependencies = [ - "encoding_rs", - "libc", - "log", - "open", - "os_pipe", - "regex", - "schemars 0.8.22", - "serde", - "serde_json", - "shared_child", - "tauri", - "tauri-plugin", - "thiserror 2.0.21", - "tokio", - "windows-sys 0.60.2", -] - -[[package]] -name = "tauri-plugin-single-instance" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c142ed88deee222bd2d979269d35c73b6c1c0f6ebd5b79b4ff80066fcad6af1" -dependencies = [ - "serde", - "serde_json", - "tauri", - "tauri-plugin-deep-link", - "thiserror 2.0.21", - "tokio", - "tracing", - "windows-sys 0.61.2", - "zbus 5.19.0", -] - -[[package]] -name = "tauri-plugin-updater" -version = "2.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "855a336eb389d2c1502244fd48fa2d8cb487a430f691bbaed025195f99a04489" -dependencies = [ - "base64 0.22.1", - "dirs", - "flate2", - "futures-util", - "http", - "infer", - "libc", - "log", - "minisign-verify", - "osakit", - "percent-encoding", - "reqwest", - "rustls", - "semver", - "serde", - "serde_json", - "tar", - "tauri", - "tauri-plugin", - "tempfile", - "thiserror 2.0.21", - "time", - "tokio", - "url", - "windows-sys 0.61.2", - "zip", -] - -[[package]] -name = "tauri-plugin-window-state" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6815cab7b212f9cfe16a9d79d8d22f9e14159e4a9460f7dec1467167a008e3" -dependencies = [ - "bitflags 2.13.2", - "log", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.21", -] - -[[package]] -name = "tauri-runtime" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a57a2a9b8c8b7fc00d35b510fe808ded7f9f1b44fc1cdda6aee38632ea5413" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni 0.21.1", - "objc2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.21", - "url", - "webkit2gtk", - "webview2-com", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e7e0c9f80a5130773cfabb6df4dc924518fb1b2c923726966804634b57d22" -dependencies = [ - "gtk", - "http", - "jni 0.21.1", - "log", - "objc2", - "objc2-app-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff55a614843b9a3f010f211df637c94df2607175b8d0f6e0f4a7c19e18621ee" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dom_query", - "dunce", - "glob", - "http", - "infer", - "json-patch", - "log", - "memchr", - "phf", - "plist", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.21", - "toml 1.1.8+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" -dependencies = [ - "dunce", - "embed-resource", - "toml 1.1.8+spec-1.1.0", -] - -[[package]] -name = "tauri-winrt-notification" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37a6c354fd28fc9e322ed9bd47e3959576dad28c9d58ea1cf888cce1c7ccb36" -dependencies = [ - "thiserror 2.0.21", - "windows", - "windows-version", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "tendril" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" -dependencies = [ - "new_debug_unreachable", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" -dependencies = [ - "thiserror-impl 2.0.21", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "time" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinystr" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" - -[[package]] -name = "tokio" -version = "1.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "1.1.8+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" -dependencies = [ - "indexmap 2.14.2", - "serde_core", - "serde_spanned 1.1.2", - "toml_datetime 1.1.2+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 1.0.4", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.14.2", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.14.2", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.25.17+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" -dependencies = [ - "indexmap 2.14.2", - "toml_datetime 1.1.2+spec-1.1.0", - "toml_parser", - "winnow 1.0.4", -] - -[[package]] -name = "toml_parser" -version = "1.1.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" -dependencies = [ - "winnow 1.0.4", -] - -[[package]] -name = "toml_writer" -version = "1.1.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags 2.13.2", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tray-icon" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.21", - "windows-sys 0.61.2", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "uds_windows" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" -dependencies = [ - "memoffset", - "tempfile", - "windows-sys 0.61.2", -] - -[[package]] -name = "unicode-ident" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", - "serde_derive", -] - -[[package]] -name = "urlpattern" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b" -dependencies = [ - "icu_properties", - "regex", - "serde", - "url", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" -dependencies = [ - "js-sys", - "tokio", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.7", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web_atoms" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" -dependencies = [ - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webview2-com" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f89fca7a704cee10dcb3654c1dbb8941d1783132f1917358af75bec37a7d7e6" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "webview2-com-sys" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a07132775117d6065853d9d1178157b8c90e228de47129d6bce2c7edebedfb" -dependencies = [ - "thiserror 2.0.21", - "windows", - "windows-core", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window-vibrancy" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111e51caca442cafd9bab396628ac4d814880eaea4e63dfd76a12e9f342b5580" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "windows-sys 0.61.2", - "windows-version", -] - -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core", - "windows-link", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core", - "windows-link", -] - -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" -dependencies = [ - "cfg-if", - "windows-sys 0.61.2", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "writeable" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" - -[[package]] -name = "wry" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a819957a01b3119af85e638a38d242af76dbc87d130dca67bfd0441072e21ff0" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dom_query", - "dpi", - "dunce", - "gdkx11", - "gtk", - "http", - "javascriptcore-rs", - "jni 0.21.1", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.21", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "windows-core", - "windows-version", - "x11-dl", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix", -] - -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", - "synstructure", -] - -[[package]] -name = "zbus" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" -dependencies = [ - "async-broadcast", - "async-process", - "async-recursion", - "async-trait", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix", - "ordered-stream", - "rand 0.8.8", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.52.0", - "xdg-home", - "zbus_macros 4.4.0", - "zbus_names 3.0.0", - "zvariant 4.2.0", -] - -[[package]] -name = "zbus" -version = "5.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "libc", - "ordered-stream", - "rustix", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow 1.0.4", - "zbus_macros 5.19.0", - "zbus_names 4.3.4", - "zvariant 5.15.0", -] - -[[package]] -name = "zbus_macros" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" -dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", - "zvariant_utils 2.1.0", -] - -[[package]] -name = "zbus_macros" -version = "5.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" -dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 3.0.7", - "zbus_names 4.3.4", - "zvariant 5.15.0", - "zvariant_utils 4.2.0", -] - -[[package]] -name = "zbus_names" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" -dependencies = [ - "serde", - "static_assertions", - "zvariant 4.2.0", -] - -[[package]] -name = "zbus_names" -version = "4.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" -dependencies = [ - "serde", - "winnow 1.0.4", - "zvariant 5.15.0", -] - -[[package]] -name = "zcheapstr" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" -dependencies = [ - "serde", -] - -[[package]] -name = "zerocopy" -version = "0.8.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerotrie" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.7", -] - -[[package]] -name = "zip" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" -dependencies = [ - "arbitrary", - "crc32fast", - "flate2", - "indexmap 2.14.2", - "memchr", -] - -[[package]] -name = "zlib-rs" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zvariant" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "zvariant_derive 4.2.0", -] - -[[package]] -name = "zvariant" -version = "5.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow 1.0.4", - "zcheapstr", - "zvariant_derive 5.15.0", - "zvariant_utils 4.2.0", -] - -[[package]] -name = "zvariant_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" -dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", - "zvariant_utils 2.1.0", -] - -[[package]] -name = "zvariant_derive" -version = "5.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" -dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 3.0.7", - "zvariant_utils 4.2.0", -] - -[[package]] -name = "zvariant_utils" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zvariant_utils" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 3.0.7", - "winnow 1.0.4", -] diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml deleted file mode 100644 index 301fe2e..0000000 --- a/apps/desktop/src-tauri/Cargo.toml +++ /dev/null @@ -1,59 +0,0 @@ -[package] -name = "storebound-desktop" -version = "0.1.0" -description = "storebound desktop: every game's releases on both stores in one window" -license = "MIT" -edition = "2021" -rust-version = "1.85" -publish = false - -[lib] -name = "storebound_desktop" -path = "src/lib.rs" - -[[bin]] -name = "storebound-desktop" -path = "src/main.rs" - -[build-dependencies] -tauri-build = { version = "2", features = [] } - -[dependencies] -tauri = { version = "2.12", features = [] } -# Spawns the storebound sidecar from Rust only; no capability grants the plugin's JS API. -tauri-plugin-shell = "2" -tauri-plugin-single-instance = { version = "2", features = ["deep-link"] } -tauri-plugin-deep-link = "2" -tauri-plugin-window-state = "2" -# Opener, dialog, notification, updater and process are driven from Rust only (no JS permissions). -tauri-plugin-opener = "2" -tauri-plugin-dialog = "2" -tauri-plugin-notification = "2" -tauri-plugin-updater = "2" -tauri-plugin-process = "2" -# Store credentials: macOS Keychain, Windows Credential Manager, Secret Service on Linux. -keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service", "crypto-rust"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -hmac = "0.12" -sha2 = "0.10" -base64 = "0.22" -getrandom = "0.3" -url = "2" -tokio = { version = "1", features = ["sync", "time"] } -# Offline cache of the last store reads (app data cache.sqlite); SQLite compiled in, nothing system-wide. -rusqlite = { version = "0.38", features = ["bundled"] } -# Dependencies screen downloads (ffmpeg, Chrome for Testing): the same HTTP stack the updater already brings. -reqwest = { version = "0.13", default-features = false, features = ["rustls-no-provider", "system-proxy"] } -rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } -zip = { version = "4", default-features = false, features = ["deflate-flate2-zlib-rs"] } - -[dev-dependencies] -tempfile = "3" - -[profile.release] -codegen-units = 1 -lto = true -opt-level = "s" -panic = "abort" -strip = true diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs deleted file mode 100644 index a76dc9d..0000000 --- a/apps/desktop/src-tauri/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -// App command manifest: every #[tauri::command] gets an `allow-` permission, and a command no capability lists -// is denied. The list lives in src/commands_list.rs so tests/security.rs can compare it with the capability files. -include!("src/commands_list.rs"); - -fn main() { - tauri_build::try_build( - tauri_build::Attributes::new() - .app_manifest(tauri_build::AppManifest::new().commands(COMMANDS)), - ) - .expect("tauri build script failed"); -} diff --git a/apps/desktop/src-tauri/capabilities/main.json b/apps/desktop/src-tauri/capabilities/main.json deleted file mode 100644 index 2ef3bf4..0000000 --- a/apps/desktop/src-tauri/capabilities/main.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "main-window", - "description": "The main window with the app's own bundled pages only (no remote content). It may listen to app events (sidecar progress, tool downloads, deep links) and call the storebound app commands; every command re-checks the window label and page origin in Rust. No plugin API is granted to the webview: the sidecar is spawned from Rust with fixed arguments, dialogs, the opener, notifications, the updater and process control are driven from Rust only, and credentials never reach the page.", - "windows": [ - "main" - ], - "local": true, - "permissions": [ - "core:event:allow-listen", - "core:event:allow-unlisten", - "allow-app-info", - "allow-sidecar-status", - "allow-sidecar-call", - "allow-cache-get", - "allow-cache-clear", - "allow-job-cancel", - "allow-write-with-confirm", - "allow-projects-list", - "allow-project-add", - "allow-project-init", - "allow-project-remove", - "allow-project-set-accounts", - "allow-accounts-list", - "allow-account-add-play", - "allow-account-add-apple", - "allow-account-remove", - "allow-portfolio-status", - "allow-open-external", - "allow-tools-info", - "allow-tools-install", - "allow-tools-uninstall" - ] -} diff --git a/apps/desktop/src-tauri/icons/128x128.png b/apps/desktop/src-tauri/icons/128x128.png deleted file mode 100644 index 92ac645..0000000 Binary files a/apps/desktop/src-tauri/icons/128x128.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/128x128@2x.png b/apps/desktop/src-tauri/icons/128x128@2x.png deleted file mode 100644 index ab8b36b..0000000 Binary files a/apps/desktop/src-tauri/icons/128x128@2x.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/32x32.png b/apps/desktop/src-tauri/icons/32x32.png deleted file mode 100644 index 7f2ebdc..0000000 Binary files a/apps/desktop/src-tauri/icons/32x32.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon.icns b/apps/desktop/src-tauri/icons/icon.icns deleted file mode 100644 index 7708163..0000000 Binary files a/apps/desktop/src-tauri/icons/icon.icns and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon.ico b/apps/desktop/src-tauri/icons/icon.ico deleted file mode 100644 index 27bd503..0000000 Binary files a/apps/desktop/src-tauri/icons/icon.ico and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon.png b/apps/desktop/src-tauri/icons/icon.png deleted file mode 100644 index b0979b2..0000000 Binary files a/apps/desktop/src-tauri/icons/icon.png and /dev/null differ diff --git a/apps/desktop/src-tauri/icons/icon.svg b/apps/desktop/src-tauri/icons/icon.svg deleted file mode 100644 index 7980110..0000000 --- a/apps/desktop/src-tauri/icons/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/desktop/src-tauri/permissions/autogenerated/account_add_apple.toml b/apps/desktop/src-tauri/permissions/autogenerated/account_add_apple.toml deleted file mode 100644 index 898ea53..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/account_add_apple.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-account-add-apple" -description = "Enables the account_add_apple command without any pre-configured scope." -commands.allow = ["account_add_apple"] - -[[permission]] -identifier = "deny-account-add-apple" -description = "Denies the account_add_apple command without any pre-configured scope." -commands.deny = ["account_add_apple"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/account_add_play.toml b/apps/desktop/src-tauri/permissions/autogenerated/account_add_play.toml deleted file mode 100644 index fc4044e..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/account_add_play.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-account-add-play" -description = "Enables the account_add_play command without any pre-configured scope." -commands.allow = ["account_add_play"] - -[[permission]] -identifier = "deny-account-add-play" -description = "Denies the account_add_play command without any pre-configured scope." -commands.deny = ["account_add_play"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/account_remove.toml b/apps/desktop/src-tauri/permissions/autogenerated/account_remove.toml deleted file mode 100644 index 1bd8a73..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/account_remove.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-account-remove" -description = "Enables the account_remove command without any pre-configured scope." -commands.allow = ["account_remove"] - -[[permission]] -identifier = "deny-account-remove" -description = "Denies the account_remove command without any pre-configured scope." -commands.deny = ["account_remove"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/accounts_list.toml b/apps/desktop/src-tauri/permissions/autogenerated/accounts_list.toml deleted file mode 100644 index 54a88f0..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/accounts_list.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-accounts-list" -description = "Enables the accounts_list command without any pre-configured scope." -commands.allow = ["accounts_list"] - -[[permission]] -identifier = "deny-accounts-list" -description = "Denies the accounts_list command without any pre-configured scope." -commands.deny = ["accounts_list"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/app_info.toml b/apps/desktop/src-tauri/permissions/autogenerated/app_info.toml deleted file mode 100644 index 6ef3c4a..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/app_info.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-app-info" -description = "Enables the app_info command without any pre-configured scope." -commands.allow = ["app_info"] - -[[permission]] -identifier = "deny-app-info" -description = "Denies the app_info command without any pre-configured scope." -commands.deny = ["app_info"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/cache_clear.toml b/apps/desktop/src-tauri/permissions/autogenerated/cache_clear.toml deleted file mode 100644 index 935428c..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/cache_clear.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-cache-clear" -description = "Enables the cache_clear command without any pre-configured scope." -commands.allow = ["cache_clear"] - -[[permission]] -identifier = "deny-cache-clear" -description = "Denies the cache_clear command without any pre-configured scope." -commands.deny = ["cache_clear"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/cache_get.toml b/apps/desktop/src-tauri/permissions/autogenerated/cache_get.toml deleted file mode 100644 index c6b73dc..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/cache_get.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-cache-get" -description = "Enables the cache_get command without any pre-configured scope." -commands.allow = ["cache_get"] - -[[permission]] -identifier = "deny-cache-get" -description = "Denies the cache_get command without any pre-configured scope." -commands.deny = ["cache_get"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/job_cancel.toml b/apps/desktop/src-tauri/permissions/autogenerated/job_cancel.toml deleted file mode 100644 index 4ff0353..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/job_cancel.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-job-cancel" -description = "Enables the job_cancel command without any pre-configured scope." -commands.allow = ["job_cancel"] - -[[permission]] -identifier = "deny-job-cancel" -description = "Denies the job_cancel command without any pre-configured scope." -commands.deny = ["job_cancel"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/open_external.toml b/apps/desktop/src-tauri/permissions/autogenerated/open_external.toml deleted file mode 100644 index c9f4753..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/open_external.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-open-external" -description = "Enables the open_external command without any pre-configured scope." -commands.allow = ["open_external"] - -[[permission]] -identifier = "deny-open-external" -description = "Denies the open_external command without any pre-configured scope." -commands.deny = ["open_external"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/portfolio_status.toml b/apps/desktop/src-tauri/permissions/autogenerated/portfolio_status.toml deleted file mode 100644 index 16aff36..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/portfolio_status.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-portfolio-status" -description = "Enables the portfolio_status command without any pre-configured scope." -commands.allow = ["portfolio_status"] - -[[permission]] -identifier = "deny-portfolio-status" -description = "Denies the portfolio_status command without any pre-configured scope." -commands.deny = ["portfolio_status"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/project_add.toml b/apps/desktop/src-tauri/permissions/autogenerated/project_add.toml deleted file mode 100644 index af9c7d4..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/project_add.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-project-add" -description = "Enables the project_add command without any pre-configured scope." -commands.allow = ["project_add"] - -[[permission]] -identifier = "deny-project-add" -description = "Denies the project_add command without any pre-configured scope." -commands.deny = ["project_add"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/project_init.toml b/apps/desktop/src-tauri/permissions/autogenerated/project_init.toml deleted file mode 100644 index f042840..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/project_init.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-project-init" -description = "Enables the project_init command without any pre-configured scope." -commands.allow = ["project_init"] - -[[permission]] -identifier = "deny-project-init" -description = "Denies the project_init command without any pre-configured scope." -commands.deny = ["project_init"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/project_remove.toml b/apps/desktop/src-tauri/permissions/autogenerated/project_remove.toml deleted file mode 100644 index 758b2ba..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/project_remove.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-project-remove" -description = "Enables the project_remove command without any pre-configured scope." -commands.allow = ["project_remove"] - -[[permission]] -identifier = "deny-project-remove" -description = "Denies the project_remove command without any pre-configured scope." -commands.deny = ["project_remove"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/project_set_accounts.toml b/apps/desktop/src-tauri/permissions/autogenerated/project_set_accounts.toml deleted file mode 100644 index e17a6c7..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/project_set_accounts.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-project-set-accounts" -description = "Enables the project_set_accounts command without any pre-configured scope." -commands.allow = ["project_set_accounts"] - -[[permission]] -identifier = "deny-project-set-accounts" -description = "Denies the project_set_accounts command without any pre-configured scope." -commands.deny = ["project_set_accounts"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/projects_list.toml b/apps/desktop/src-tauri/permissions/autogenerated/projects_list.toml deleted file mode 100644 index deaa39c..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/projects_list.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-projects-list" -description = "Enables the projects_list command without any pre-configured scope." -commands.allow = ["projects_list"] - -[[permission]] -identifier = "deny-projects-list" -description = "Denies the projects_list command without any pre-configured scope." -commands.deny = ["projects_list"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/sidecar_call.toml b/apps/desktop/src-tauri/permissions/autogenerated/sidecar_call.toml deleted file mode 100644 index 556e1a3..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/sidecar_call.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-sidecar-call" -description = "Enables the sidecar_call command without any pre-configured scope." -commands.allow = ["sidecar_call"] - -[[permission]] -identifier = "deny-sidecar-call" -description = "Denies the sidecar_call command without any pre-configured scope." -commands.deny = ["sidecar_call"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/sidecar_status.toml b/apps/desktop/src-tauri/permissions/autogenerated/sidecar_status.toml deleted file mode 100644 index 9c06486..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/sidecar_status.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-sidecar-status" -description = "Enables the sidecar_status command without any pre-configured scope." -commands.allow = ["sidecar_status"] - -[[permission]] -identifier = "deny-sidecar-status" -description = "Denies the sidecar_status command without any pre-configured scope." -commands.deny = ["sidecar_status"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/tools_info.toml b/apps/desktop/src-tauri/permissions/autogenerated/tools_info.toml deleted file mode 100644 index 789f7f4..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/tools_info.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-tools-info" -description = "Enables the tools_info command without any pre-configured scope." -commands.allow = ["tools_info"] - -[[permission]] -identifier = "deny-tools-info" -description = "Denies the tools_info command without any pre-configured scope." -commands.deny = ["tools_info"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/tools_install.toml b/apps/desktop/src-tauri/permissions/autogenerated/tools_install.toml deleted file mode 100644 index 39e7086..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/tools_install.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-tools-install" -description = "Enables the tools_install command without any pre-configured scope." -commands.allow = ["tools_install"] - -[[permission]] -identifier = "deny-tools-install" -description = "Denies the tools_install command without any pre-configured scope." -commands.deny = ["tools_install"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/tools_uninstall.toml b/apps/desktop/src-tauri/permissions/autogenerated/tools_uninstall.toml deleted file mode 100644 index 3d8fe2a..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/tools_uninstall.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-tools-uninstall" -description = "Enables the tools_uninstall command without any pre-configured scope." -commands.allow = ["tools_uninstall"] - -[[permission]] -identifier = "deny-tools-uninstall" -description = "Denies the tools_uninstall command without any pre-configured scope." -commands.deny = ["tools_uninstall"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/write_with_confirm.toml b/apps/desktop/src-tauri/permissions/autogenerated/write_with_confirm.toml deleted file mode 100644 index 0a642da..0000000 --- a/apps/desktop/src-tauri/permissions/autogenerated/write_with_confirm.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -[[permission]] -identifier = "allow-write-with-confirm" -description = "Enables the write_with_confirm command without any pre-configured scope." -commands.allow = ["write_with_confirm"] - -[[permission]] -identifier = "deny-write-with-confirm" -description = "Denies the write_with_confirm command without any pre-configured scope." -commands.deny = ["write_with_confirm"] diff --git a/apps/desktop/src-tauri/src/accounts.rs b/apps/desktop/src-tauri/src/accounts.rs deleted file mode 100644 index 06f6b10..0000000 --- a/apps/desktop/src-tauri/src/accounts.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Store accounts. The secret part (Play service account JSON, App Store Connect .p8 key with its key id and issuer) -//! goes into the OS credential store under the service `dev.storebound.desktop`; only a label, the kind and the -//! creation time are kept in the app data folder. Secrets are never sent to the webview: they are read from the -//! keychain right before a sidecar call and handed to it in that call's params (memory only), which passes them to -//! the storebound command as environment variables. -use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; - -pub const KEYRING_SERVICE: &str = "dev.storebound.desktop"; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum AccountKind { - Play, - Apple, -} - -/// What the app keeps on disk and shows: no secret, no key id, no email. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Account { - pub id: String, - pub label: String, - pub kind: AccountKind, - pub created: String, -} - -/// The keychain entry of one account (JSON). -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "kind", rename_all = "lowercase")] -pub enum Secret { - Play { service_account: String }, - Apple { key_id: String, issuer_id: String, p8: String }, -} - -impl Secret { - pub fn kind(&self) -> AccountKind { - match self { - Secret::Play { .. } => AccountKind::Play, - Secret::Apple { .. } => AccountKind::Apple, - } - } - - /// The env names storebound reads (cli/lib/credentials.ts), as a sidecar `credentials` object. - pub fn to_credentials(&self, into: &mut Map) { - match self { - Secret::Play { service_account } => { - into.insert("PLAY_SERVICE_ACCOUNT".into(), Value::String(service_account.clone())); - } - Secret::Apple { key_id, issuer_id, p8 } => { - into.insert("ASC_KEY_ID".into(), Value::String(key_id.clone())); - into.insert("ASC_ISSUER_ID".into(), Value::String(issuer_id.clone())); - into.insert("ASC_P8".into(), Value::String(p8.clone())); - } - } - } -} - -/// A Google service account JSON key: client_email and a PEM private key. -pub fn check_service_account(text: &str) -> Result { - let v: Value = serde_json::from_str(text).map_err(|_| "not a JSON file".to_string())?; - let email = v.get("client_email").and_then(Value::as_str).unwrap_or_default(); - let key = v.get("private_key").and_then(Value::as_str).unwrap_or_default(); - if email.is_empty() || !key.contains("PRIVATE KEY") { - return Err("not a Google service account key (client_email / private_key missing)".into()); - } - // Store the compact form: no file path, nothing else from the disk. - serde_json::to_string(&v).map_err(|e| e.to_string()) -} - -/// App Store Connect key id: 10 upper-case letters or digits. -pub fn check_key_id(s: &str) -> Result { - let s = s.trim(); - if s.len() == 10 && s.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()) { - Ok(s.to_string()) - } else { - Err("key id: 10 letters or digits, as shown in App Store Connect > Users and Access > Integrations".into()) - } -} - -/// App Store Connect issuer id: a UUID. -pub fn check_issuer_id(s: &str) -> Result { - let s = s.trim().to_ascii_lowercase(); - let parts: Vec<&str> = s.split('-').collect(); - let lens = [8, 4, 4, 4, 12]; - let ok = parts.len() == 5 - && parts.iter().zip(lens).all(|(p, n)| p.len() == n && p.chars().all(|c| c.is_ascii_hexdigit())); - if ok { - Ok(s) - } else { - Err("issuer id: the UUID shown above the keys in App Store Connect".into()) - } -} - -pub fn check_p8(text: &str) -> Result { - if text.contains("-----BEGIN PRIVATE KEY-----") && text.contains("-----END PRIVATE KEY-----") { - Ok(text.trim().to_string() + "\n") - } else { - Err("not an App Store Connect .p8 key (BEGIN PRIVATE KEY missing)".into()) - } -} - -pub fn check_label(s: &str) -> Result { - let s = s.trim(); - if s.is_empty() || s.chars().count() > 60 || s.chars().any(char::is_control) { - return Err("name: 1 to 60 characters".into()); - } - Ok(s.to_string()) -} - -/// Where secrets live. The OS keychain in the app; a map in tests. -pub trait SecretStore: Send + Sync { - fn set(&self, id: &str, secret: &Secret) -> Result<(), String>; - fn get(&self, id: &str) -> Result; - fn delete(&self, id: &str) -> Result<(), String>; -} - -pub struct Keychain; - -impl SecretStore for Keychain { - fn set(&self, id: &str, secret: &Secret) -> Result<(), String> { - let json = serde_json::to_string(secret).map_err(|e| e.to_string())?; - keyring::Entry::new(KEYRING_SERVICE, id).and_then(|e| e.set_password(&json)).map_err(|e| format!("keychain: {e}")) - } - fn get(&self, id: &str) -> Result { - let json = keyring::Entry::new(KEYRING_SERVICE, id).and_then(|e| e.get_password()).map_err(|e| format!("keychain: {e}"))?; - serde_json::from_str(&json).map_err(|_| "keychain: unreadable entry".to_string()) - } - fn delete(&self, id: &str) -> Result<(), String> { - match keyring::Entry::new(KEYRING_SERVICE, id).and_then(|e| e.delete_credential()) { - Ok(()) | Err(keyring::Error::NoEntry) => Ok(()), - Err(e) => Err(format!("keychain: {e}")), - } - } -} diff --git a/apps/desktop/src-tauri/src/cache.rs b/apps/desktop/src-tauri/src/cache.rs deleted file mode 100644 index 3bafb86..0000000 --- a/apps/desktop/src-tauri/src/cache.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! Offline cache of the last successful reads (app data `cache.sqlite`): store status, health, reviews, TestFlight, -//! catalog parity, listings, icons. The window shows a cached value with its "last read" time while nothing newer is -//! there, and says "unknown" when there is none; a cached value is never shown as fresh. Secrets are never cached -//! (results only, never params), and a project's rows go when the project is removed. -use std::path::Path; -use std::sync::Mutex; - -use rusqlite::{params, Connection, OptionalExtension}; -use serde::Serialize; -use serde_json::Value; - -pub const FILE: &str = "cache.sqlite"; -/// Results above this size are not kept (logs of a long render, say); the window reads them live. -pub const MAX_VALUE_BYTES: usize = 4 * 1024 * 1024; - -pub struct Cache { - db: Mutex, -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -pub struct Entry { - pub value: Value, - /// When it was read (RFC 3339 UTC). - pub at: String, -} - -/// Cache key: project id, method and the canonical arguments (the same canonical JSON as confirm tokens). -pub fn key(project: &str, method: &str, args: &Value) -> String { - let a = crate::confirm::canonical(args).unwrap_or_else(|_| args.to_string()); - format!("{project}\u{1f}{method}\u{1f}{a}") -} - -impl Cache { - pub fn open(dir: &Path) -> Result { - std::fs::create_dir_all(dir).map_err(|e| e.to_string())?; - Cache::with(Connection::open(dir.join(FILE)).map_err(|e| e.to_string())?) - } - - pub fn in_memory() -> Result { - Cache::with(Connection::open_in_memory().map_err(|e| e.to_string())?) - } - - fn with(db: Connection) -> Result { - db.execute_batch( - "PRAGMA journal_mode=WAL; - CREATE TABLE IF NOT EXISTS reads ( - key TEXT PRIMARY KEY, - project TEXT NOT NULL, - method TEXT NOT NULL, - value TEXT NOT NULL, - at TEXT NOT NULL - ); - CREATE INDEX IF NOT EXISTS reads_project ON reads(project);", - ) - .map_err(|e| e.to_string())?; - Ok(Cache { db: Mutex::new(db) }) - } - - pub fn put(&self, project: &str, method: &str, args: &Value, value: &Value, at: &str) -> Result<(), String> { - let text = value.to_string(); - if text.len() > MAX_VALUE_BYTES { - return Ok(()); - } - let db = self.db.lock().map_err(|_| "cache poisoned")?; - db.execute( - "INSERT INTO reads(key, project, method, value, at) VALUES (?1, ?2, ?3, ?4, ?5) - ON CONFLICT(key) DO UPDATE SET value = excluded.value, at = excluded.at", - params![key(project, method, args), project, method, text, at], - ) - .map_err(|e| e.to_string())?; - Ok(()) - } - - pub fn get(&self, project: &str, method: &str, args: &Value) -> Result, String> { - let db = self.db.lock().map_err(|_| "cache poisoned")?; - let row: Option<(String, String)> = db - .query_row("SELECT value, at FROM reads WHERE key = ?1", params![key(project, method, args)], |r| Ok((r.get(0)?, r.get(1)?))) - .optional() - .map_err(|e| e.to_string())?; - Ok(row.and_then(|(v, at)| serde_json::from_str(&v).ok().map(|value| Entry { value, at }))) - } - - /// Every cached method of a project with its time (for the "last read" stamps). - pub fn stamps(&self, project: &str) -> Result, String> { - let db = self.db.lock().map_err(|_| "cache poisoned")?; - let mut st = db.prepare("SELECT method, MAX(at) FROM reads WHERE project = ?1 GROUP BY method ORDER BY method").map_err(|e| e.to_string())?; - let rows = st.query_map(params![project], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?))).map_err(|e| e.to_string())?; - rows.collect::, _>>().map_err(|e| e.to_string()) - } - - pub fn forget_project(&self, project: &str) -> Result<(), String> { - let db = self.db.lock().map_err(|_| "cache poisoned")?; - db.execute("DELETE FROM reads WHERE project = ?1", params![project]).map_err(|e| e.to_string())?; - Ok(()) - } - - pub fn clear(&self) -> Result<(), String> { - let db = self.db.lock().map_err(|_| "cache poisoned")?; - db.execute("DELETE FROM reads", []).map_err(|e| e.to_string())?; - Ok(()) - } -} diff --git a/apps/desktop/src-tauri/src/commands.rs b/apps/desktop/src-tauri/src/commands.rs deleted file mode 100644 index aaca49b..0000000 --- a/apps/desktop/src-tauri/src/commands.rs +++ /dev/null @@ -1,543 +0,0 @@ -//! App commands. Each one first checks the calling window and page (policy::require_main). Secrets never cross into -//! the webview: account files are picked in native dialogs and read here, and credentials go from the keychain -//! straight into the sidecar call. -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use std::sync::Mutex; - -use serde::Serialize; -use serde_json::{json, Map, Value}; -use tauri::{AppHandle, Emitter, Manager, State, WebviewWindow}; -use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind}; -use tauri_plugin_opener::OpenerExt; - -use crate::accounts::{self, Account, AccountKind, Secret, SecretStore}; -use crate::cache::{Cache, Entry}; -use crate::downloads; -use crate::policy::{self, Kind}; -use crate::sidecar::{valid_job, Fail, Progress, Sidecar}; -use crate::store::{self, Project, State as Saved}; - -pub struct AppState { - pub sidecar: Sidecar, - pub data_dir: PathBuf, - pub saved: Mutex, - pub secrets: Box, - pub cache: Cache, - pub downloads: downloads::Jobs, -} - -type Res = Result; - -fn guard(w: &WebviewWindow) -> Res<()> { - let url = w.url().map_err(|e| Fail::host(e.to_string()))?; - policy::require_main(w.label(), &url, cfg!(debug_assertions)).map_err(Fail::host) -} - -fn saved(s: &AppState) -> Res> { - s.saved.lock().map_err(|_| Fail::host("state poisoned")) -} - -fn persist(s: &AppState, st: &Saved) -> Res<()> { - store::save(&s.data_dir, st).map_err(Fail::host) -} - -fn job_of(job: Option) -> Res> { - match job { - Some(j) if !valid_job(&j) => Err(Fail::host("bad job id")), - j => Ok(j), - } -} - -/// Project folder + credentials of its mapped accounts, as sidecar params. -fn project_params(s: &AppState, project_id: &str) -> Res<(Project, Map)> { - let p = saved(s)?.projects.iter().find(|p| p.id == project_id).cloned().ok_or_else(|| Fail::host("unknown project"))?; - let mut creds = Map::new(); - for id in [&p.play_account, &p.apple_account].into_iter().flatten() { - // A missing keychain entry is not fatal: the command says which credential it lacks. - if let Ok(secret) = s.secrets.get(id) { - secret.to_credentials(&mut creds); - } - } - Ok((p, creds)) -} - -#[derive(Serialize)] -pub struct AppInfo { - version: &'static str, - identifier: String, - platform: &'static str, - debug: bool, - /// Shown as "~" in paths, so screenshots do not carry the account name. - home: Option, -} - -#[tauri::command] -pub fn app_info(window: WebviewWindow, app: AppHandle) -> Res { - guard(&window)?; - Ok(AppInfo { - version: env!("CARGO_PKG_VERSION"), - identifier: app.config().identifier.clone(), - platform: std::env::consts::OS, - debug: cfg!(debug_assertions), - home: app.path().home_dir().ok().map(|h| h.to_string_lossy().to_string()), - }) -} - -#[tauri::command] -pub async fn sidecar_status(window: WebviewWindow, app: AppHandle, state: State<'_, AppState>) -> Res { - guard(&window)?; - let r = state.sidecar.get(&app).await?; - let tools = r.call("system.tools", json!({})).await.unwrap_or(Value::Null); - Ok(json!({ "running": r.alive(), "version": r.version, "protocol": r.protocol, "methods": r.methods, "tools": tools })) -} - -/// A read (or dry-run write) through the sidecar. Writes from here are always dry runs (policy::sanitize_js_call). -/// Successful reads of a project are kept in the offline cache (results only, never the credentials). -#[tauri::command] -pub async fn sidecar_call(window: WebviewWindow, app: AppHandle, state: State<'_, AppState>, method: String, project_id: Option, params: Option, job: Option) -> Res { - guard(&window)?; - let job = job_of(job)?; - let r = state.sidecar.get(&app).await?; - let kind = r.kind(&method); - let mut p = policy::sanitize_js_call(&method, kind, ¶ms.unwrap_or(Value::Null)).map_err(Fail::host)?; - let args = p.get("args").cloned().unwrap_or_else(|| json!({})); - let mut cache_project = None; - if matches!(kind, Some(Kind::Read | Kind::Write)) { - let id = project_id.ok_or_else(|| Fail::host(format!("{method} needs a project")))?; - let (proj, creds) = project_params(&state, &id)?; - let o = p.as_object_mut().ok_or_else(|| Fail::host("params"))?; - o.insert("project".into(), Value::String(proj.path)); - if !creds.is_empty() && method != "project.inspect" { - o.insert("credentials".into(), Value::Object(creds)); - } - if kind == Some(Kind::Read) { - cache_project = Some(id); - } - } - let res = r.call_job(&method, p, job.as_deref()).await?; - if let Some(id) = cache_project { - // only results that read something: a failed store read must not hide the last good one - if res["ok"] == Value::Bool(true) && !res["data"].is_null() { - let _ = state.cache.put(&id, &method, &args, &res, &store::now_iso()); - } - } - Ok(res) -} - -/// The last cached result of a read (`project_id` "*" for the portfolio), with its time; null when never read. -#[tauri::command] -pub fn cache_get(window: WebviewWindow, state: State<'_, AppState>, method: String, project_id: String, args: Option) -> Res> { - guard(&window)?; - state.cache.get(&project_id, &method, &args.unwrap_or_else(|| json!({}))).map_err(Fail::host) -} - -#[tauri::command] -pub fn cache_clear(window: WebviewWindow, state: State<'_, AppState>) -> Res<()> { - guard(&window)?; - state.cache.clear().map_err(Fail::host) -} - -/// Stops a running job: its sidecar requests (`$/cancelRequest`) and a running download. -#[tauri::command] -pub async fn job_cancel(window: WebviewWindow, state: State<'_, AppState>, job: String) -> Res { - guard(&window)?; - if !valid_job(&job) { - return Err(Fail::host("bad job id")); - } - let mut hit = state.downloads.cancel(&job); - if let Some(r) = state.sidecar.current().await { - hit |= r.cancel_job(&job) > 0; - hit |= r.cancel_job(&format!("{job}:p")) > 0; - } - Ok(hit) -} - -const DIALOG_LINES: usize = 28; -const DIALOG_CHARS: usize = 2_400; - -/// Last lines of a dry run for the dialog (native alerts do not scroll well). -pub fn dialog_excerpt(output: &str) -> String { - let lines: Vec<&str> = output.lines().filter(|l| !l.trim().is_empty()).collect(); - let start = lines.len().saturating_sub(DIALOG_LINES); - let mut s = lines[start..].join("\n"); - if s.chars().count() > DIALOG_CHARS { - s = s.chars().rev().take(DIALOG_CHARS).collect::>().into_iter().rev().collect(); - s = format!("...{s}"); - } - if start > 0 { - s = format!("({start} earlier lines in the app log)\n{s}"); - } - s -} - -/// The home folder as "~" in dialog text, so a screenshot or screen share does not carry the account name. -pub fn tilde(text: &str, home: Option<&str>) -> String { - match home.map(|h| h.trim_end_matches(['/', '\\'])) { - Some(h) if h.len() > 1 => text.replace(h, "~"), - _ => text.to_string(), - } -} - -/// Human summary shown above the dry run in the confirm dialog. -pub fn dialog_summary(title: &str, description: &str, project: &str, network: bool, destructive: bool) -> String { - let mut s = format!("{title} · {project}\n\n{description}\n\n"); - s.push_str(if network { "This changes your store records (not public until you submit or release in the console)." } else { "This changes local files only." }); - if destructive { - s.push_str("\nIt replaces what is there now."); - } - s -} - -/// Native Apply / Cancel dialog; true on Apply. -async fn confirm_dialog(window: &WebviewWindow, title: String, text: String) -> Res { - let w = window.clone(); - tauri::async_runtime::spawn_blocking(move || { - w.app_handle() - .dialog() - .message(text) - .title(title) - .kind(MessageDialogKind::Warning) - .buttons(MessageDialogButtons::OkCancelCustom("Apply".into(), "Cancel".into())) - .parent(&w) - .blocking_show() - }) - .await - .map_err(|e| Fail::host(e.to_string())) -} - -/// Dry run, then a native dialog with the summary and the dry run, then (only on Apply) the real run with a confirm -/// token minted here for exactly this method, project and arguments. -#[tauri::command] -pub async fn write_with_confirm(window: WebviewWindow, app: AppHandle, state: State<'_, AppState>, method: String, project_id: String, args: Option, job: Option) -> Res { - guard(&window)?; - let job = job_of(job)?; - let r = state.sidecar.get(&app).await?; - if r.kind(&method) != Some(Kind::Write) || method == "project.init" { - return Err(Fail::host(format!("{method} is not a write method of an app folder"))); - } - let d = r.descriptor(&method).cloned().ok_or_else(|| Fail::host("unknown method"))?; - let args = match args.unwrap_or(json!({})) { - Value::Object(o) => Value::Object(o), - _ => return Err(Fail::host("args must be an object")), - }; - let (proj, creds) = project_params(&state, &project_id)?; - let mut dry_params = json!({ "project": proj.path, "args": args, "dryRun": true }); - if !creds.is_empty() { - dry_params["credentials"] = Value::Object(creds.clone()); - } - let dry = r.call_job(&method, dry_params, job.as_deref()).await?; - if dry["ok"] != Value::Bool(true) { - return Ok(json!({ "confirmed": false, "stage": "dry-run", "dryRun": dry })); - } - let text = format!( - "{}\n\nDry run ({}):\n{}", - dialog_summary(&d.title, &d.description, &proj.name, d.network, d.destructive), - dry["command"].as_str().unwrap_or_default(), - dialog_excerpt(dry["output"].as_str().unwrap_or_default()) - ); - let home = app.path().home_dir().ok().map(|h| h.to_string_lossy().to_string()); - if !confirm_dialog(&window, format!("Apply: {}", d.title), tilde(&text, home.as_deref())).await? { - return Ok(json!({ "confirmed": false, "stage": "dialog", "dryRun": dry })); - } - let bound = json!({ "project": proj.path, "args": args }); - let token = r.mint(&method, &bound).map_err(Fail::host)?; - let mut real = json!({ "project": proj.path, "args": args, "dryRun": false, "confirmToken": token }); - if !creds.is_empty() { - real["credentials"] = Value::Object(creds); - } - let result = r.call_job(&method, real, job.as_deref()).await?; - Ok(json!({ "confirmed": true, "stage": "done", "dryRun": dry, "result": result })) -} - -#[tauri::command] -pub fn projects_list(window: WebviewWindow, state: State<'_, AppState>) -> Res> { - guard(&window)?; - Ok(saved(&state)?.projects.clone()) -} - -const CONFIG_NAMES: [&str; 3] = ["storebound.config.ts", "storebound.config.js", "storebound.config.json"]; - -pub fn has_config(dir: &Path) -> bool { - CONFIG_NAMES.iter().any(|n| dir.join(n).is_file()) -} - -async fn pick_folder(window: &WebviewWindow, title: &'static str) -> Res> { - let w = window.clone(); - let picked = tauri::async_runtime::spawn_blocking(move || w.app_handle().dialog().file().set_title(title).set_parent(&w).blocking_pick_folder()) - .await - .map_err(|e| Fail::host(e.to_string()))?; - let Some(picked) = picked else { return Ok(None) }; - let dir = picked.into_path().map_err(|e| Fail::host(e.to_string()))?; - Ok(Some(std::fs::canonicalize(&dir).unwrap_or(dir))) -} - -/// Adds a folder that has a config (inspected by the sidecar) to the list. -async fn add_folder(app: &AppHandle, state: &AppState, dir: &Path) -> Res { - let path = dir.to_string_lossy().to_string(); - if saved(state)?.projects.iter().any(|p| p.path == path) { - return Err(Fail::host("this folder is already in the list")); - } - let r = state.sidecar.get(app).await?; - let summary = r.call("project.inspect", json!({ "project": path })).await?; - let project = Project { - id: store::new_id()?, - path, - name: summary["name"].as_str().unwrap_or("?").to_string(), - play_account: None, - apple_account: None, - added: store::now_iso(), - }; - let mut st = saved(state)?; - st.projects.push(project.clone()); - persist(state, &st)?; - Ok(project) -} - -/// Native folder picker; the folder must contain storebound.config.ts and load (project.inspect). None = cancelled. -#[tauri::command] -pub async fn project_add(window: WebviewWindow, app: AppHandle, state: State<'_, AppState>) -> Res> { - guard(&window)?; - let Some(dir) = pick_folder(&window, "Add an app folder (with storebound.config.ts)").await? else { return Ok(None) }; - if !has_config(&dir) { - return Err(Fail::host(format!("no storebound.config.ts in {}", dir.display()))); - } - add_folder(&app, &state, &dir).await.map(Some) -} - -pub const PROFILES: [&str; 3] = ["app", "game", "subscription-app"]; - -/// `storebound init` for a new app folder: native folder picker, the dry run in a native dialog, then the templates -/// for `profile` are written (existing files kept) and the folder is added. None = cancelled at any step. -#[tauri::command] -pub async fn project_init(window: WebviewWindow, app: AppHandle, state: State<'_, AppState>, profile: String) -> Res> { - guard(&window)?; - if !PROFILES.contains(&profile.as_str()) { - return Err(Fail::host("profile: app, game or subscription-app")); - } - let Some(dir) = pick_folder(&window, "Choose the app's folder (storebound files go into it)").await? else { return Ok(None) }; - if has_config(&dir) { - return Err(Fail::host("this folder already has a storebound config: add it instead")); - } - let path = dir.to_string_lossy().to_string(); - let args = json!({ "profile": profile }); - let r = state.sidecar.get(&app).await?; - let dry = r.call("project.init", json!({ "project": path, "args": args, "dryRun": true })).await?; - if dry["ok"] != Value::Bool(true) { - return Err(Fail::host(format!("init dry run failed: {}", dialog_excerpt(dry["output"].as_str().unwrap_or_default())))); - } - let home = app.path().home_dir().ok().map(|h| h.to_string_lossy().to_string()); - let text = format!( - "Start an app folder · {profile} profile\n\n{path}\n\nCopies the storebound templates into this folder; existing files are kept. Every answer in them is a starting point to edit.\n\n{}", - dialog_excerpt(dry["output"].as_str().unwrap_or_default()) - ); - if !confirm_dialog(&window, "Apply: Start an app folder".into(), tilde(&text, home.as_deref())).await? { - return Ok(None); - } - let token = r.mint("project.init", &json!({ "project": path, "args": args })).map_err(Fail::host)?; - let res = r.call("project.init", json!({ "project": path, "args": args, "dryRun": false, "confirmToken": token })).await?; - if res["ok"] != Value::Bool(true) { - return Err(Fail::host(format!("init failed: {}", dialog_excerpt(res["output"].as_str().unwrap_or_default())))); - } - add_folder(&app, &state, &dir).await.map(Some) -} - -#[tauri::command] -pub fn project_remove(window: WebviewWindow, state: State<'_, AppState>, id: String) -> Res<()> { - guard(&window)?; - let mut st = saved(&state)?; - st.projects.retain(|p| p.id != id); - let _ = state.cache.forget_project(&id); - persist(&state, &st) -} - -#[tauri::command] -pub fn project_set_accounts(window: WebviewWindow, state: State<'_, AppState>, id: String, play: Option, apple: Option) -> Res { - guard(&window)?; - let mut st = saved(&state)?; - let kind_of = |acc: &Option| acc.as_ref().map(|a| st.accounts.iter().find(|x| &x.id == a).map(|x| x.kind)); - if let Some(k) = kind_of(&play) { - if k != Some(AccountKind::Play) { - return Err(Fail::host("not a Google Play account")); - } - } - if let Some(k) = kind_of(&apple) { - if k != Some(AccountKind::Apple) { - return Err(Fail::host("not an App Store Connect account")); - } - } - let p = st.projects.iter_mut().find(|p| p.id == id).ok_or_else(|| Fail::host("unknown project"))?; - p.play_account = play; - p.apple_account = apple; - let out = p.clone(); - persist(&state, &st)?; - Ok(out) -} - -#[tauri::command] -pub fn accounts_list(window: WebviewWindow, state: State<'_, AppState>) -> Res> { - guard(&window)?; - Ok(saved(&state)?.accounts.clone()) -} - -fn add_account(state: &AppState, label: &str, secret: Secret) -> Res { - let label = accounts::check_label(label).map_err(Fail::host)?; - let acc = Account { id: store::new_id()?, label, kind: secret.kind(), created: store::now_iso() }; - state.secrets.set(&acc.id, &secret).map_err(Fail::host)?; - let mut st = saved(state)?; - st.accounts.push(acc.clone()); - if let Err(e) = persist(state, &st) { - let _ = state.secrets.delete(&acc.id); - return Err(e); - } - Ok(acc) -} - -async fn pick_file(window: &WebviewWindow, title: &'static str, filter: (&'static str, &'static [&'static str])) -> Res> { - let w = window.clone(); - let picked = tauri::async_runtime::spawn_blocking(move || w.app_handle().dialog().file().set_title(title).add_filter(filter.0, filter.1).set_parent(&w).blocking_pick_file()) - .await - .map_err(|e| Fail::host(e.to_string()))?; - let Some(p) = picked else { return Ok(None) }; - let path = p.into_path().map_err(|e| Fail::host(e.to_string()))?; - let meta = std::fs::metadata(&path).map_err(|e| Fail::host(e.to_string()))?; - if meta.len() > 64 * 1024 { - return Err(Fail::host("file too large for a key")); - } - std::fs::read_to_string(&path).map(Some).map_err(|e| Fail::host(e.to_string())) -} - -/// Google Play: pick the service account JSON key in a native dialog; it goes to the keychain only. -#[tauri::command] -pub async fn account_add_play(window: WebviewWindow, state: State<'_, AppState>, label: String) -> Res> { - guard(&window)?; - accounts::check_label(&label).map_err(Fail::host)?; - let Some(text) = pick_file(&window, "Google Play service account key (JSON)", ("JSON key", &["json"])).await? else { return Ok(None) }; - let sa = accounts::check_service_account(&text).map_err(Fail::host)?; - add_account(&state, &label, Secret::Play { service_account: sa }).map(Some) -} - -/// App Store Connect: key id + issuer id typed in the app, the .p8 picked in a native dialog; all to the keychain. -#[tauri::command] -pub async fn account_add_apple(window: WebviewWindow, state: State<'_, AppState>, label: String, key_id: String, issuer_id: String) -> Res> { - guard(&window)?; - accounts::check_label(&label).map_err(Fail::host)?; - let key_id = accounts::check_key_id(&key_id).map_err(Fail::host)?; - let issuer_id = accounts::check_issuer_id(&issuer_id).map_err(Fail::host)?; - let Some(text) = pick_file(&window, "App Store Connect API key (.p8)", ("API key", &["p8"])).await? else { return Ok(None) }; - let p8 = accounts::check_p8(&text).map_err(Fail::host)?; - add_account(&state, &label, Secret::Apple { key_id, issuer_id, p8 }).map(Some) -} - -#[tauri::command] -pub fn account_remove(window: WebviewWindow, state: State<'_, AppState>, id: String) -> Res<()> { - guard(&window)?; - state.secrets.delete(&id).map_err(Fail::host)?; - let mut st = saved(&state)?; - st.accounts.retain(|a| a.id != id); - for p in st.projects.iter_mut() { - if p.play_account.as_deref() == Some(id.as_str()) { - p.play_account = None; - } - if p.apple_account.as_deref() == Some(id.as_str()) { - p.apple_account = None; - } - } - persist(&state, &st) -} - -/// Store reads run at most this many apps at once (each is two status reads). -const LIVE_PARALLEL: usize = 4; - -/// `portfolio.status` over every project. Offline unless `live`; live reads every app with its own accounts, several -/// at once, and sends one progress line per finished app (job progress n / total). Each live app result is cached; -/// the whole live result too (project "*"), so the portfolio shows the last read when offline. -#[tauri::command] -pub async fn portfolio_status(window: WebviewWindow, app: AppHandle, state: State<'_, AppState>, live: Option, job: Option) -> Res { - guard(&window)?; - let job = job_of(job)?; - let projects = saved(&state)?.projects.clone(); - if projects.is_empty() { - return Ok(json!({ "apps": [], "at": store::now_iso() })); - } - let r = state.sidecar.get(&app).await?; - if live != Some(true) { - let paths: Vec<&str> = projects.iter().map(|p| p.path.as_str()).collect(); - return r.call_job("portfolio.status", json!({ "projects": paths }), job.as_deref()).await; - } - let total = projects.len(); - let sem = Arc::new(tokio::sync::Semaphore::new(LIVE_PARALLEL)); - let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let mut handles = Vec::new(); - for p in &projects { - let (_, creds) = project_params(&state, &p.id)?; - let (r, sem, done, app, job, path, id) = (r.clone(), sem.clone(), done.clone(), app.clone(), job.clone(), p.path.clone(), p.id.clone()); - handles.push(tauri::async_runtime::spawn(async move { - let _permit = sem.acquire_owned().await.ok(); - let sub = job.as_ref().map(|j| format!("{j}:p")); - let res = r.call_job("portfolio.status", json!({ "projects": [path], "live": true, "credentials": creds }), sub.as_deref()).await; - let n = done.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1; - let _ = app.emit_to(policy::MAIN, "sidecar-progress", Progress { params: json!({ "requestId": 0, "n": n, "total": total, "project": path }), job: job.clone() }); - (id, res) - })); - } - let mut all = Vec::new(); - let at = store::now_iso(); - for h in handles { - let (id, res) = h.await.map_err(|e| Fail::host(e.to_string()))?; - let res = res?; - for a in res["apps"].as_array().cloned().unwrap_or_default() { - let _ = state.cache.put(&id, "portfolio.app", &json!({}), &a, &at); - all.push(a); - } - } - let out = json!({ "apps": all, "at": at }); - let _ = state.cache.put("*", "portfolio.status", &json!({ "live": true }), &out, &at); - Ok(out) -} - -#[tauri::command] -pub fn open_external(window: WebviewWindow, app: AppHandle, url: String) -> Res<()> { - guard(&window)?; - let u = url::Url::parse(&url).map_err(|_| Fail::host("not a URL"))?; - if !policy::external_allowed(&u) { - return Err(Fail::host("only the store consoles and storebound.dev open from here")); - } - app.opener().open_url(u.as_str(), None::<&str>).map_err(|e| Fail::host(e.to_string())) -} - -/// What the Dependencies screen can download on this platform, and what is installed. -#[tauri::command] -pub fn tools_info(window: WebviewWindow, state: State<'_, AppState>) -> Res> { - guard(&window)?; - downloads::info(&state.data_dir).map_err(Fail::host) -} - -/// Downloads, verifies and installs ffmpeg or Chrome for Testing into the app data folder (progress: `tool-download` -/// events with the job id; job_cancel stops it). The sidecar restarts afterwards so it sees the new tool. -#[tauri::command] -pub async fn tools_install(window: WebviewWindow, app: AppHandle, state: State<'_, AppState>, tool: String, job: String) -> Res { - guard(&window)?; - if !valid_job(&job) || !matches!(tool.as_str(), "ffmpeg" | "chrome") { - return Err(Fail::host("bad tool or job")); - } - let flag = state.downloads.start(&job).map_err(Fail::host)?; - let handle = app.clone(); - let res = downloads::install(&state.data_dir, &tool, &job, flag, move |e| { - let _ = handle.emit_to(policy::MAIN, "tool-download", e); - }) - .await; - state.downloads.finish(&job); - if res.is_ok() { - state.sidecar.shutdown().await; - } - res.map_err(Fail::host) -} - -#[tauri::command] -pub async fn tools_uninstall(window: WebviewWindow, state: State<'_, AppState>, tool: String) -> Res<()> { - guard(&window)?; - downloads::uninstall(&state.data_dir, &tool).map_err(Fail::host)?; - state.sidecar.shutdown().await; - Ok(()) -} diff --git a/apps/desktop/src-tauri/src/commands_list.rs b/apps/desktop/src-tauri/src/commands_list.rs deleted file mode 100644 index ce2e1fc..0000000 --- a/apps/desktop/src-tauri/src/commands_list.rs +++ /dev/null @@ -1,25 +0,0 @@ -/// Every app command the webview can call. build.rs turns each into an `allow-` permission (underscores become -/// dashes); capabilities/main.json grants them to the `main` window only; tests/security.rs checks both lists match. -pub const COMMANDS: &[&str] = &[ - "app_info", - "sidecar_status", - "sidecar_call", - "cache_get", - "cache_clear", - "job_cancel", - "write_with_confirm", - "projects_list", - "project_add", - "project_init", - "project_remove", - "project_set_accounts", - "accounts_list", - "account_add_play", - "account_add_apple", - "account_remove", - "portfolio_status", - "open_external", - "tools_info", - "tools_install", - "tools_uninstall", -]; diff --git a/apps/desktop/src-tauri/src/confirm.rs b/apps/desktop/src-tauri/src/confirm.rs deleted file mode 100644 index fca5cc8..0000000 --- a/apps/desktop/src-tauri/src/confirm.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! Confirm tokens, host side. The same scheme as cli/lib/confirm-token.ts (the sidecar verifies): -//! -//! token = "sbt1." + expiry_ms + "." + nonce + "." + base64url(HMAC-SHA256(secret, payload)) -//! payload = "storebound-confirm-v1\n" + method + "\n" + canonical(params) + "\n" + expiry_ms + "\n" + nonce -//! -//! The secret is 32 random bytes made per sidecar start and handed over in `session.hello` on the sidecar's stdin; it -//! never leaves this process otherwise. Tokens are minted only by `commands::write_with_confirm`, right after the owner -//! pressed the confirm button of the native dialog that showed the dry run. -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use hmac::{Hmac, Mac}; -use serde_json::Value; -use sha2::Sha256; - -pub const PREFIX: &str = "sbt1"; -pub const DOMAIN: &str = "storebound-confirm-v1"; -/// Lifetime of a minted token: the real call follows the dialog right away. -pub const TTL_MS: u64 = 60_000; -const MAX_SAFE_INTEGER: i64 = 9_007_199_254_740_991; - -/// Canonical JSON, byte for byte what the TypeScript side builds: keys sorted by UTF-16 code units, no whitespace, -/// JSON string escaping, numbers only as safe integers, null members kept (JSON has no undefined). -pub fn canonical(v: &Value) -> Result { - let mut out = String::new(); - write_canonical(v, &mut out)?; - Ok(out) -} - -fn write_canonical(v: &Value, out: &mut String) -> Result<(), String> { - match v { - Value::Null => out.push_str("null"), - Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }), - Value::Number(n) => { - let i = n - .as_i64() - .filter(|i| (-MAX_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(i)) - .ok_or("canonical: only safe integers are allowed as numbers")?; - out.push_str(&i.to_string()); - } - Value::String(s) => out.push_str(&serde_json::to_string(s).map_err(|e| e.to_string())?), - Value::Array(a) => { - out.push('['); - for (i, x) in a.iter().enumerate() { - if i > 0 { - out.push(','); - } - write_canonical(x, out)?; - } - out.push(']'); - } - Value::Object(o) => { - let mut keys: Vec<&String> = o.keys().collect(); - keys.sort_by(|a, b| a.encode_utf16().cmp(b.encode_utf16())); - out.push('{'); - for (i, k) in keys.iter().enumerate() { - if i > 0 { - out.push(','); - } - out.push_str(&serde_json::to_string(k).map_err(|e| e.to_string())?); - out.push(':'); - write_canonical(&o[k.as_str()], out)?; - } - out.push('}'); - } - } - Ok(()) -} - -pub fn payload(method: &str, params: &Value, expiry: u64, nonce: &str) -> Result { - Ok(format!("{DOMAIN}\n{method}\n{}\n{expiry}\n{nonce}", canonical(params)?)) -} - -pub fn mac(secret: &[u8], method: &str, params: &Value, expiry: u64, nonce: &str) -> Result { - let mut m = Hmac::::new_from_slice(secret).map_err(|e| e.to_string())?; - m.update(payload(method, params, expiry, nonce)?.as_bytes()); - Ok(URL_SAFE_NO_PAD.encode(m.finalize().into_bytes())) -} - -/// A token for exactly (method, params), valid for TTL_MS from `now_ms`. -pub fn mint(secret: &[u8], method: &str, params: &Value, now_ms: u64, nonce: &str) -> Result { - let expiry = now_ms + TTL_MS; - Ok(format!("{PREFIX}.{expiry}.{nonce}.{}", mac(secret, method, params, expiry, nonce)?)) -} - -/// 16 random bytes, base64url (22 characters). -pub fn new_nonce() -> Result { - let mut b = [0u8; 16]; - getrandom::fill(&mut b).map_err(|e| e.to_string())?; - Ok(URL_SAFE_NO_PAD.encode(b)) -} - -/// The per-session secret. -pub fn new_secret() -> Result<[u8; 32], String> { - let mut b = [0u8; 32]; - getrandom::fill(&mut b).map_err(|e| e.to_string())?; - Ok(b) -} - -pub fn hex(b: &[u8]) -> String { - b.iter().map(|x| format!("{x:02x}")).collect() -} diff --git a/apps/desktop/src-tauri/src/downloads.rs b/apps/desktop/src-tauri/src/downloads.rs deleted file mode 100644 index c640af4..0000000 --- a/apps/desktop/src-tauri/src/downloads.rs +++ /dev/null @@ -1,454 +0,0 @@ -//! One-click downloads of the optional local tools (Dependencies screen) into the app data folder, never system-wide -//! and never bundled with the installer (docs/desktop.md): -//! -//! - **ffmpeg + ffprobe**: the exact archives of `tools-manifest.json` (URL, SHA-256, size; written by -//! tools/tools-manifest.ts per desktop release). The SHA-256 is checked while downloading; a mismatch deletes the file. -//! - **Chrome for Testing**: Google's official JSON endpoint (Stable channel), archive from storage.googleapis.com. No -//! hashes are published, so the unpacked browser must carry Google's code signature (macOS team EQHXZ8M8AV, -//! Windows Authenticode by Google LLC); on Linux the version banner is checked. -//! -//! Everything happens in a staging folder; only a verified result replaces the installed copy. Progress goes to the -//! window as `tool-download` events with the job id; cancel removes the staging folder. Redirects may only lead to -//! the hosts in DOWNLOAD_HOSTS, over HTTPS. -use std::collections::HashMap; -use std::fs; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; - -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use url::Url; - -pub const MANIFEST_JSON: &str = include_str!("../tools-manifest.json"); - -/// Hosts a download (or one of its redirects) may touch. -pub const DOWNLOAD_HOSTS: &[&str] = &[ - "ffmpeg.martin-riedl.de", - "github.com", - "objects.githubusercontent.com", - "release-assets.githubusercontent.com", - "googlechromelabs.github.io", - "storage.googleapis.com", -]; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct FileMap { - pub from: String, - pub to: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Archive { - pub url: String, - pub sha256: String, - pub size: u64, - pub kind: String, - pub files: Vec, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Ffmpeg { - pub version: String, - pub license: String, - pub license_url: String, - pub sources: HashMap, - pub targets: HashMap>, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Chrome { - pub endpoint: String, - pub channel: String, - pub platforms: HashMap, - pub mac_team_id: String, - pub windows_signer: String, - pub license: String, - pub license_url: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Manifest { - pub generated: String, - pub ffmpeg: Ffmpeg, - pub chrome: Chrome, -} - -pub fn manifest() -> Result { - serde_json::from_str(MANIFEST_JSON).map_err(|e| format!("tools manifest: {e}")) -} - -/// Rust target triple of this build (the manifest's keys). -pub fn target_triple() -> &'static str { - if cfg!(all(target_os = "macos", target_arch = "aarch64")) { - "aarch64-apple-darwin" - } else if cfg!(target_os = "macos") { - "x86_64-apple-darwin" - } else if cfg!(all(target_os = "linux", target_arch = "aarch64")) { - "aarch64-unknown-linux-gnu" - } else if cfg!(target_os = "linux") { - "x86_64-unknown-linux-gnu" - } else { - "x86_64-pc-windows-msvc" - } -} - -pub fn download_allowed(u: &Url) -> bool { - u.scheme() == "https" && u.username().is_empty() && u.password().is_none() && u.port().is_none() && u.host_str().is_some_and(|h| DOWNLOAD_HOSTS.contains(&h)) -} - -pub fn hex(b: &[u8]) -> String { - crate::confirm::hex(b) -} - -/// What the Dependencies screen shows next to the Download buttons. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct DownloadInfo { - pub tool: &'static str, - pub available: bool, - pub version: Option, - pub size: Option, - pub license: String, - pub license_url: String, - pub source: String, - pub installed: bool, - pub folder: String, -} - -pub fn info(data_dir: &Path) -> Result, String> { - let m = manifest()?; - let t = target_triple(); - let ff = m.ffmpeg.targets.get(t); - let os = if t.contains("apple") { "macos" } else if t.contains("windows") { "windows" } else { "linux" }; - let exe = if cfg!(windows) { ".exe" } else { "" }; - Ok(vec![ - DownloadInfo { - tool: "ffmpeg", - available: ff.is_some(), - version: Some(m.ffmpeg.version.clone()), - size: ff.map(|a| a.iter().map(|x| x.size).sum()), - license: m.ffmpeg.license.clone(), - license_url: m.ffmpeg.license_url.clone(), - source: m.ffmpeg.sources.get(os).cloned().unwrap_or_default(), - installed: data_dir.join("tools").join("bin").join(format!("ffmpeg{exe}")).is_file(), - folder: "tools/bin".into(), - }, - DownloadInfo { - tool: "chrome", - available: m.chrome.platforms.contains_key(t), - version: None, - size: None, - license: m.chrome.license.clone(), - license_url: m.chrome.license_url.clone(), - source: "https://googlechromelabs.github.io/chrome-for-testing/".into(), - installed: data_dir.join("tools").join("chrome").join(crate::policy::chrome_executable()).is_file(), - folder: "tools/chrome".into(), - }, - ]) -} - -/// Progress of a download job, sent to the window as `tool-download`. -#[derive(Debug, Clone, Serialize)] -pub struct Event { - pub job: String, - pub tool: String, - /// resolve, download, verify, unpack, install, done, error, cancelled - pub stage: &'static str, - pub received: u64, - pub total: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, -} - -/// Running download jobs and their cancel flags. -#[derive(Default)] -pub struct Jobs { - flags: Mutex>>, -} - -impl Jobs { - pub fn start(&self, job: &str) -> Result, String> { - let mut m = self.flags.lock().map_err(|_| "jobs poisoned")?; - if m.contains_key(job) { - return Err("this download is already running".into()); - } - let f = Arc::new(AtomicBool::new(false)); - m.insert(job.to_string(), f.clone()); - Ok(f) - } - pub fn finish(&self, job: &str) { - if let Ok(mut m) = self.flags.lock() { - m.remove(job); - } - } - pub fn cancel(&self, job: &str) -> bool { - self.flags.lock().ok().and_then(|m| m.get(job).map(|f| f.store(true, Ordering::SeqCst))).is_some() - } -} - -/// The Chrome for Testing download of a platform from the endpoint JSON: (version, url). -pub fn chrome_download(endpoint_json: &serde_json::Value, channel: &str, platform: &str) -> Result<(String, Url), String> { - let ch = &endpoint_json["channels"][channel]; - let version = ch["version"].as_str().ok_or("Chrome for Testing: no version in the endpoint answer")?.to_string(); - let url = ch["downloads"]["chrome"] - .as_array() - .and_then(|a| a.iter().find(|d| d["platform"].as_str() == Some(platform))) - .and_then(|d| d["url"].as_str()) - .ok_or_else(|| format!("Chrome for Testing: no download for {platform}"))?; - let u = Url::parse(url).map_err(|e| e.to_string())?; - if !download_allowed(&u) { - return Err(format!("Chrome for Testing: unexpected download host {}", u.host_str().unwrap_or("?"))); - } - Ok((version, u)) -} - -fn client() -> Result { - let _ = rustls::crypto::ring::default_provider().install_default(); - reqwest::Client::builder() - .user_agent(concat!("storebound-desktop/", env!("CARGO_PKG_VERSION"))) - .redirect(reqwest::redirect::Policy::custom(|a| { - if a.previous().len() > 8 { - a.error("too many redirects") - } else if download_allowed(a.url()) { - a.follow() - } else { - a.error("redirect to a host outside the download list") - } - })) - .connect_timeout(std::time::Duration::from_secs(20)) - .build() - .map_err(|e| e.to_string()) -} - -/// Streams `url` into `dest`, returning its SHA-256 (hex). `progress(received_in_this_file)` after each chunk. -async fn fetch(c: &reqwest::Client, url: &Url, dest: &Path, cancel: &AtomicBool, mut progress: impl FnMut(u64)) -> Result { - if !download_allowed(url) { - return Err(format!("not an allowed download host: {}", url.host_str().unwrap_or("?"))); - } - let mut resp = c.get(url.clone()).send().await.map_err(|e| format!("download: {e}"))?; - if !resp.status().is_success() { - return Err(format!("download: {} answered {}", url.host_str().unwrap_or("?"), resp.status())); - } - let mut f = fs::File::create(dest).map_err(|e| e.to_string())?; - let mut h = Sha256::new(); - let mut got: u64 = 0; - while let Some(chunk) = resp.chunk().await.map_err(|e| format!("download: {e}"))? { - if cancel.load(Ordering::SeqCst) { - return Err("cancelled".into()); - } - h.update(&chunk); - f.write_all(&chunk).map_err(|e| e.to_string())?; - got += chunk.len() as u64; - progress(got); - } - f.flush().map_err(|e| e.to_string())?; - Ok(hex(&h.finalize())) -} - -/// Unpacks an archive into `dir`. macOS: ditto (keeps the app bundles' symlinks and signatures intact); tar.xz: the -/// system tar; other zips: the zip crate, which refuses entries outside `dir`. -pub fn unpack(archive: &Path, kind: &str, dir: &Path) -> Result<(), String> { - fs::create_dir_all(dir).map_err(|e| e.to_string())?; - let run = |cmd: &mut std::process::Command| -> Result<(), String> { - let out = cmd.output().map_err(|e| e.to_string())?; - if out.status.success() { - Ok(()) - } else { - Err(String::from_utf8_lossy(&out.stderr).trim().chars().take(300).collect()) - } - }; - match kind { - "tar.xz" => run(std::process::Command::new("tar").arg("-xJf").arg(archive).arg("-C").arg(dir)), - "zip" if cfg!(target_os = "macos") => run(std::process::Command::new("/usr/bin/ditto").arg("-x").arg("-k").arg(archive).arg(dir)), - "zip" => { - let f = fs::File::open(archive).map_err(|e| e.to_string())?; - let mut z = zip::ZipArchive::new(f).map_err(|e| e.to_string())?; - z.extract(dir).map_err(|e| e.to_string()) - } - k => Err(format!("unknown archive kind {k}")), - } -} - -/// `rel` inside `base`, refusing absolute paths and `..`. -pub fn inside(base: &Path, rel: &str) -> Result { - let p = Path::new(rel); - if p.is_absolute() || p.components().any(|c| !matches!(c, std::path::Component::Normal(_))) { - return Err(format!("unsafe path in the manifest: {rel}")); - } - Ok(base.join(p)) -} - -fn first_line(exe: &Path, arg: &str) -> Option { - let out = std::process::Command::new(exe).arg(arg).output().ok()?; - let text = if out.stdout.is_empty() { out.stderr } else { out.stdout }; - String::from_utf8_lossy(&text).lines().next().map(|s| s.trim().to_string()) -} - -#[cfg(unix)] -fn make_executable(p: &Path) -> Result<(), String> { - use std::os::unix::fs::PermissionsExt; - fs::set_permissions(p, fs::Permissions::from_mode(0o755)).map_err(|e| e.to_string()) -} -#[cfg(not(unix))] -fn make_executable(_: &Path) -> Result<(), String> { - Ok(()) -} - -/// Google's code signature on the unpacked Chrome for Testing. -fn verify_chrome(root: &Path, exe: &Path, c: &Chrome) -> Result<(), String> { - if cfg!(target_os = "macos") { - let app = root.join("Google Chrome for Testing.app"); - let v = std::process::Command::new("/usr/bin/codesign").args(["--verify", "--deep", "--strict"]).arg(&app).output().map_err(|e| e.to_string())?; - if !v.status.success() { - return Err(format!("code signature invalid: {}", String::from_utf8_lossy(&v.stderr).trim())); - } - let d = std::process::Command::new("/usr/bin/codesign").args(["-dv", "--verbose=2"]).arg(&app).output().map_err(|e| e.to_string())?; - let text = String::from_utf8_lossy(&d.stderr); - if !text.lines().any(|l| l.trim() == format!("TeamIdentifier={}", c.mac_team_id)) { - return Err("not signed by Google (team identifier differs)".into()); - } - Ok(()) - } else if cfg!(windows) { - let script = format!( - "$s = Get-AuthenticodeSignature -LiteralPath '{}'; if ($s.Status -eq 'Valid' -and $s.SignerCertificate.Subject -like '*O={}*') {{ exit 0 }} else {{ exit 1 }}", - exe.display().to_string().replace('\'', "''"), - c.windows_signer - ); - let st = std::process::Command::new("powershell").args(["-NoProfile", "-NonInteractive", "-Command", &script]).status().map_err(|e| e.to_string())?; - if st.success() { - Ok(()) - } else { - Err(format!("Authenticode signature by {} not found", c.windows_signer)) - } - } else { - match first_line(exe, "--version") { - Some(l) if l.starts_with("Google Chrome for Testing") => Ok(()), - other => Err(format!("unexpected version banner: {}", other.unwrap_or_default())), - } - } -} - -/// Installs `tool` ("ffmpeg" or "chrome") into `/tools`. `emit` gets every progress event. -pub async fn install(data_dir: &Path, tool: &str, job: &str, cancel: Arc, emit: impl Fn(Event) + Send + Sync) -> Result { - let m = manifest()?; - let tools = data_dir.join("tools"); - let staging = tools.join(format!(".staging-{job}")); - let _ = fs::remove_dir_all(&staging); - fs::create_dir_all(&staging).map_err(|e| e.to_string())?; - let ev = |stage: &'static str, received: u64, total: Option, message: Option| emit(Event { job: job.into(), tool: tool.into(), stage, received, total, message }); - let res = async { - let c = client()?; - match tool { - "ffmpeg" => { - let archives = m.ffmpeg.targets.get(target_triple()).ok_or("no ffmpeg build for this platform in the manifest")?; - let total: u64 = archives.iter().map(|a| a.size).sum(); - let mut done: u64 = 0; - let bin = staging.join("bin"); - fs::create_dir_all(&bin).map_err(|e| e.to_string())?; - for (i, a) in archives.iter().enumerate() { - let url = Url::parse(&a.url).map_err(|e| e.to_string())?; - let file = staging.join(format!("a{i}.{}", a.kind)); - let sum = fetch(&c, &url, &file, &cancel, |n| ev("download", done + n, Some(total), None)).await?; - ev("verify", done + a.size, Some(total), None); - if !sum.eq_ignore_ascii_case(&a.sha256) { - let _ = fs::remove_file(&file); - return Err(format!("checksum mismatch for {} (deleted)", url.path())); - } - done += a.size; - ev("unpack", done, Some(total), None); - let out = staging.join(format!("u{i}")); - unpack(&file, &a.kind, &out)?; - for f in &a.files { - let src = inside(&out, &f.from)?; - let dst = inside(&bin, &f.to)?; - if !src.is_file() { - return Err(format!("{} missing in the archive", f.from)); - } - fs::rename(&src, &dst).or_else(|_| fs::copy(&src, &dst).map(|_| ())).map_err(|e| e.to_string())?; - make_executable(&dst)?; - } - let _ = fs::remove_file(&file); - } - let exe = if cfg!(windows) { "ffmpeg.exe" } else { "ffmpeg" }; - let banner = first_line(&bin.join(exe), "-version").unwrap_or_default(); - if !banner.starts_with("ffmpeg version") { - return Err(format!("the downloaded ffmpeg does not run: {banner}")); - } - ev("install", total, Some(total), None); - let dest = tools.join("bin"); - fs::create_dir_all(&dest).map_err(|e| e.to_string())?; - for e in fs::read_dir(&bin).map_err(|e| e.to_string())? { - let e = e.map_err(|e| e.to_string())?; - let target = dest.join(e.file_name()); - let _ = fs::remove_file(&target); - fs::rename(e.path(), &target).map_err(|e| e.to_string())?; - } - Ok(banner) - } - "chrome" => { - let platform = m.chrome.platforms.get(target_triple()).ok_or("Chrome for Testing has no build for this platform")?; - ev("resolve", 0, None, None); - let endpoint = Url::parse(&m.chrome.endpoint).map_err(|e| e.to_string())?; - if !download_allowed(&endpoint) { - return Err("endpoint host not allowed".into()); - } - let json: serde_json::Value = c.get(endpoint).send().await.map_err(|e| e.to_string())?.json().await.map_err(|e| e.to_string())?; - let (version, url) = chrome_download(&json, &m.chrome.channel, platform)?; - let head = c.head(url.clone()).send().await.ok(); - let total = head.and_then(|h| h.content_length()); - let file = staging.join("chrome.zip"); - fetch(&c, &url, &file, &cancel, |n| ev("download", n, total, Some(version.clone()))).await?; - ev("unpack", total.unwrap_or(0), total, None); - let out = staging.join("u"); - unpack(&file, "zip", &out)?; - let _ = fs::remove_file(&file); - let root = out.join(format!("chrome-{platform}")); - let exe = root.join(crate::policy::chrome_executable()); - if !exe.is_file() { - return Err("Chrome for Testing archive has an unexpected layout".into()); - } - ev("verify", total.unwrap_or(0), total, None); - verify_chrome(&root, &exe, &m.chrome)?; - ev("install", total.unwrap_or(0), total, None); - let dest = tools.join("chrome"); - let _ = fs::remove_dir_all(&dest); - fs::rename(&root, &dest).map_err(|e| e.to_string())?; - Ok(format!("Chrome for Testing {version}")) - } - other => Err(format!("unknown tool {other}")), - } - } - .await; - let _ = fs::remove_dir_all(&staging); - match &res { - Ok(v) => ev("done", 0, None, Some(v.clone())), - Err(e) if e == "cancelled" => ev("cancelled", 0, None, None), - Err(e) => ev("error", 0, None, Some(e.clone())), - } - res -} - -/// Removes an app-managed tool. -pub fn uninstall(data_dir: &Path, tool: &str) -> Result<(), String> { - let tools = data_dir.join("tools"); - match tool { - "ffmpeg" => { - for n in ["ffmpeg", "ffprobe", "ffmpeg.exe", "ffprobe.exe"] { - let _ = fs::remove_file(tools.join("bin").join(n)); - } - Ok(()) - } - "chrome" => { - let p = tools.join("chrome"); - if p.exists() { - fs::remove_dir_all(p).map_err(|e| e.to_string())?; - } - Ok(()) - } - other => Err(format!("unknown tool {other}")), - } -} diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs deleted file mode 100644 index 23d79ce..0000000 --- a/apps/desktop/src-tauri/src/lib.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! storebound desktop: a Tauri shell around the storebound CLI, which runs as a sidecar (`storebound serve --stdio`). -//! The GUI never talks to a store itself; every store call is a storebound command. -pub mod accounts; -pub mod cache; -pub mod commands; -pub mod confirm; -pub mod downloads; -pub mod policy; -pub mod sidecar; -pub mod store; - -include!("commands_list.rs"); - -use std::sync::Mutex; - -use tauri::menu::{Menu, MenuItem, PredefinedMenuItem, Submenu, WINDOW_SUBMENU_ID}; -use tauri::{AppHandle, Emitter, Manager, RunEvent, Runtime, WindowEvent}; -use tauri_plugin_deep_link::DeepLinkExt; - -/// WebKitGTK's DMABUF renderer leaves the window blank on some Linux GPU / driver / VM combinations. Turn it off unless -/// the user set the variable (WEBKIT_DISABLE_DMABUF_RENDERER=0 keeps DMABUF). Must run before any webview exists. -pub fn linux_webkit_env() { - #[cfg(target_os = "linux")] - if std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none() { - // SAFETY: called first thing in main, before any thread is started. - unsafe { std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1") }; - } -} - -fn show_main(app: &AppHandle) { - if let Some(w) = app.get_webview_window(policy::MAIN) { - let _ = w.unminimize(); - let _ = w.show(); - let _ = w.set_focus(); - } -} - -/// macOS menu: app menu with Quit (Cmd+Q really quits), Edit (copy / paste in text fields), and a Window menu that -/// keeps a permanent entry for the main window, since the automatic list drops a hidden window. -fn menu(app: &AppHandle) -> tauri::Result> { - let name = "storebound"; - let app_menu = Submenu::with_items( - app, - name, - true, - &[ - &PredefinedMenuItem::about(app, Some("About storebound"), None)?, - &PredefinedMenuItem::separator(app)?, - &PredefinedMenuItem::services(app, None)?, - &PredefinedMenuItem::separator(app)?, - &PredefinedMenuItem::hide(app, None)?, - &PredefinedMenuItem::hide_others(app, None)?, - &PredefinedMenuItem::show_all(app, None)?, - &PredefinedMenuItem::separator(app)?, - &PredefinedMenuItem::quit(app, Some("Quit storebound"))?, - ], - )?; - let edit = Submenu::with_items( - app, - "Edit", - true, - &[ - &PredefinedMenuItem::undo(app, None)?, - &PredefinedMenuItem::redo(app, None)?, - &PredefinedMenuItem::separator(app)?, - &PredefinedMenuItem::cut(app, None)?, - &PredefinedMenuItem::copy(app, None)?, - &PredefinedMenuItem::paste(app, None)?, - &PredefinedMenuItem::select_all(app, None)?, - ], - )?; - let window = Submenu::with_id_and_items( - app, - WINDOW_SUBMENU_ID, - "Window", - true, - &[ - &PredefinedMenuItem::minimize(app, None)?, - &PredefinedMenuItem::maximize(app, Some("Zoom"))?, - &PredefinedMenuItem::separator(app)?, - &MenuItem::with_id(app, "show-main", "storebound", true, Some("CmdOrCtrl+0"))?, - &PredefinedMenuItem::separator(app)?, - &PredefinedMenuItem::close_window(app, None)?, - ], - )?; - Menu::with_items(app, &[&app_menu, &edit, &window]) -} - -fn handle_deep_link(app: &AppHandle, urls: &[url::Url]) { - if let Some(screen) = urls.iter().find_map(policy::deep_link_screen) { - show_main(app); - let _ = app.emit_to(policy::MAIN, "deep-link", screen); - } -} - -/// Debug builds only: STOREBOUND_DEV_PROJECTS (folders separated by the OS path separator) seeds the app folder list -/// once, for screenshots and manual checks with the demo apps. Release builds ignore it. -fn dev_seed(data_dir: &std::path::Path) { - if !cfg!(debug_assertions) { - return; - } - let Some(v) = std::env::var_os("STOREBOUND_DEV_PROJECTS") else { return }; - let mut st = store::load(data_dir); - let mut changed = false; - for dir in std::env::split_paths(&v) { - let dir = std::fs::canonicalize(&dir).unwrap_or(dir); - let path = dir.to_string_lossy().to_string(); - if !commands::has_config(&dir) || st.projects.iter().any(|p| p.path == path) { - continue; - } - let name = dir.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default(); - if let Ok(id) = store::new_id() { - st.projects.push(store::Project { id, path, name, play_account: None, apple_account: None, added: store::now_iso() }); - changed = true; - } - } - if changed { - let _ = store::save(data_dir, &st); - } -} - -pub fn run() { - linux_webkit_env(); - let app = tauri::Builder::default() - // single-instance first: a second start (or a storebound:// link) focuses the running app instead - .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| show_main(app))) - .plugin(tauri_plugin_deep_link::init()) - .plugin(tauri_plugin_window_state::Builder::new().build()) - .plugin(tauri_plugin_shell::init()) - .plugin(tauri_plugin_opener::init()) - .plugin(tauri_plugin_dialog::init()) - .plugin(tauri_plugin_notification::init()) - .plugin(tauri_plugin_updater::Builder::new().build()) - .plugin(tauri_plugin_process::init()) - .setup(|app| { - let data_dir = app.path().app_data_dir()?; - dev_seed(&data_dir); - let cache = cache::Cache::open(&data_dir).or_else(|_| cache::Cache::in_memory()).map_err(std::io::Error::other)?; - app.manage(commands::AppState { - sidecar: sidecar::Sidecar::default(), - saved: Mutex::new(store::load(&data_dir)), - data_dir, - secrets: Box::new(accounts::Keychain), - cache, - downloads: downloads::Jobs::default(), - }); - #[cfg(target_os = "macos")] - app.set_menu(menu(app.handle())?)?; - #[cfg(not(target_os = "macos"))] - let _ = menu::; - app.on_menu_event(|app, e| { - if e.id() == "show-main" { - show_main(app); - } - }); - #[cfg(any(target_os = "linux", all(debug_assertions, windows)))] - app.deep_link().register_all()?; - let handle = app.handle().clone(); - app.deep_link().on_open_url(move |e| handle_deep_link(&handle, &e.urls())); - if let Ok(Some(urls)) = app.deep_link().get_current() { - handle_deep_link(app.handle(), &urls); - } - Ok(()) - }) - .on_window_event(|w, e| { - // macOS: the close button hides the window (the app stays in the Dock and the Window menu); - // Cmd+Q and the app menu's Quit still quit. Elsewhere closing the window quits. - if cfg!(target_os = "macos") && w.label() == policy::MAIN { - if let WindowEvent::CloseRequested { api, .. } = e { - api.prevent_close(); - let _ = w.hide(); - } - } - }) - .invoke_handler(tauri::generate_handler![ - commands::app_info, - commands::sidecar_status, - commands::sidecar_call, - commands::cache_get, - commands::cache_clear, - commands::job_cancel, - commands::write_with_confirm, - commands::projects_list, - commands::project_add, - commands::project_init, - commands::project_remove, - commands::project_set_accounts, - commands::accounts_list, - commands::account_add_play, - commands::account_add_apple, - commands::account_remove, - commands::portfolio_status, - commands::open_external, - commands::tools_info, - commands::tools_install, - commands::tools_uninstall, - ]) - .build(tauri::generate_context!()) - .expect("storebound desktop failed to start"); - - app.run(|app, e| match e { - #[cfg(target_os = "macos")] - RunEvent::Reopen { .. } => show_main(app), - RunEvent::Exit => { - if let Some(s) = app.try_state::() { - s.sidecar.shutdown_now(); - } - } - _ => {} - }); -} diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs deleted file mode 100644 index cdf4943..0000000 --- a/apps/desktop/src-tauri/src/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -// No console window on Windows release builds. -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -fn main() { - storebound_desktop::run(); -} diff --git a/apps/desktop/src-tauri/src/policy.rs b/apps/desktop/src-tauri/src/policy.rs deleted file mode 100644 index 7ed2c71..0000000 --- a/apps/desktop/src-tauri/src/policy.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Who may call what. Pure functions, so tests/security.rs can pin every rule. -//! -//! The capability file already limits the app commands to the `main` window with local content; every command checks -//! again here (window label and the exact origin of the page that called), so a future second window, a stray -//! navigation or a remote page can never reach them, whatever the capability files say. -use serde_json::{Map, Value}; -use url::Url; - -/// The one window that may call app commands. -pub const MAIN: &str = "main"; - -/// The only arguments the sidecar is ever started with. -pub const SIDECAR_ARGS: [&str; 2] = ["serve", "--stdio"]; -/// Name of the sidecar binary (`bundle.externalBin` = binaries/storebound). -pub const SIDECAR: &str = "storebound"; - -/// Dev server origin (`build.devUrl`); accepted only in debug builds. -pub const DEV_HOST: &str = "127.0.0.1"; -pub const DEV_PORT: u16 = 1420; - -/// The app's own bundled pages: `tauri://localhost` (macOS, Linux) or `http(s)://tauri.localhost` (Windows). -/// No port, no user info, nothing that only looks similar. In debug builds also the Vite dev server. -pub fn is_local_origin(url: &Url, dev: bool) -> bool { - if !url.username().is_empty() || url.password().is_some() { - return false; - } - let host = url.host_str().unwrap_or_default(); - match url.scheme() { - "tauri" => host == "localhost" && url.port().is_none(), - "http" | "https" if host == "tauri.localhost" => url.port().is_none(), - "http" if dev => host == DEV_HOST && url.port() == Some(DEV_PORT), - _ => false, - } -} - -pub fn require_main(label: &str, url: &Url, dev: bool) -> Result<(), String> { - if label != MAIN { - return Err(format!("not allowed from window '{label}'")); - } - if !is_local_origin(url, dev) { - return Err("not allowed from this page".into()); - } - Ok(()) -} - -/// Method kinds the sidecar announces in its `session.hello` descriptors. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Kind { - Read, - Write, - System, - Portfolio, - Session, -} - -impl Kind { - pub fn parse(s: &str) -> Option { - Some(match s { - "read" => Kind::Read, - "write" => Kind::Write, - "system" => Kind::System, - "portfolio" => Kind::Portfolio, - "session" => Kind::Session, - _ => return None, - }) - } -} - -/// Params keys the webview may send for a sidecar method. The project folder comes from the host's project list (the -/// webview names a project id), and `confirmToken`, `credentials` and `dryRun` are the host's: tokens are minted only -/// after the native dialog, credentials come from the keychain. -const JS_PARAM_KEYS: &[&str] = &["args", "live"]; - -/// Checks a webview call to the sidecar and returns the params to send. Writes from the webview are always dry runs; -/// the real run goes through `write_with_confirm` (native dialog, then a host-minted token). -pub fn sanitize_js_call(method: &str, kind: Option, params: &Value) -> Result { - let kind = kind.ok_or_else(|| format!("unknown method: {method}"))?; - match kind { - Kind::Session => return Err(format!("{method} is the host's")), - Kind::Portfolio => return Err(format!("{method}: use portfolio_status")), - _ => {} - } - let empty = Map::new(); - let obj = match params { - Value::Object(o) => o, - Value::Null => &empty, - _ => return Err("params must be an object".into()), - }; - for k in obj.keys() { - if !JS_PARAM_KEYS.contains(&k.as_str()) { - return Err(format!("param '{k}' is not allowed from the window")); - } - } - let mut out = obj.clone(); - if kind == Kind::Write { - out.insert("dryRun".into(), Value::Bool(true)); - } - Ok(Value::Object(out)) -} - -/// Hosts `open_external` may open in the browser: the two store consoles and storebound's own site. -pub const EXTERNAL_HOSTS: &[&str] = &[ - "play.google.com", - "appstoreconnect.apple.com", - "developer.apple.com", - "storebound.dev", - // Dependencies screen link-outs - "developer.android.com", - "ffmpeg.org", - "googlechromelabs.github.io", - "git-scm.com", -]; - -/// Executable of a Chrome for Testing download, relative to its unpacked folder (app data `tools/chrome/`). -pub fn chrome_executable() -> &'static str { - if cfg!(target_os = "macos") { - "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" - } else if cfg!(windows) { - "chrome.exe" - } else { - "chrome" - } -} - -pub fn external_allowed(url: &Url) -> bool { - url.scheme() == "https" - && url.username().is_empty() - && url.password().is_none() - && url.port().is_none() - && url.host_str().is_some_and(|h| EXTERNAL_HOSTS.contains(&h)) -} - -/// Deep links the app reacts to: `storebound://` for a known screen, nothing else. -pub const DEEP_LINK_SCREENS: &[&str] = &["portfolio", "apps", "accounts", "dependencies", "settings"]; - -pub fn deep_link_screen(url: &Url) -> Option<&'static str> { - if url.scheme() != "storebound" { - return None; - } - let host = url.host_str().unwrap_or_default(); - DEEP_LINK_SCREENS.iter().copied().find(|s| *s == host) -} diff --git a/apps/desktop/src-tauri/src/sidecar.rs b/apps/desktop/src-tauri/src/sidecar.rs deleted file mode 100644 index ae3c93f..0000000 --- a/apps/desktop/src-tauri/src/sidecar.rs +++ /dev/null @@ -1,344 +0,0 @@ -//! The storebound sidecar: `storebound serve --stdio`, started from Rust only (no capability grants the shell -//! plugin to the webview), with the fixed arguments in policy::SIDECAR_ARGS. One process per app run, started on -//! first use and again after it stops. Its first request is `session.hello` with a fresh 32-byte secret, so only this -//! process can mint the confirm tokens the sidecar accepts for real writes. -//! -//! Jobs: the webview may tag a call with a short job id. Progress notifications of that request then carry the job id -//! (so the right toast or screen shows them), and `cancel_job` sends `$/cancelRequest` for every request of the job. -use std::collections::HashMap; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -use serde::Serialize; -use serde_json::{json, Value}; -use tauri::{AppHandle, Emitter, Manager, Runtime}; -use tauri_plugin_shell::process::{CommandChild, CommandEvent}; -use tauri_plugin_shell::ShellExt; -use tokio::sync::oneshot; - -use crate::confirm; -use crate::policy::{self, Kind}; - -/// A failed call, as the webview sees it. -#[derive(Debug, Clone, Serialize)] -pub struct Fail { - pub code: i64, - pub message: String, - #[serde(skip_serializing_if = "Value::is_null")] - pub data: Value, -} - -impl Fail { - pub fn new(code: i64, message: impl Into) -> Fail { - Fail { code, message: message.into(), data: Value::Null } - } - /// The host refused (window, origin, params, state). - pub fn host(message: impl Into) -> Fail { - Fail::new(-1, message) - } -} - -impl From for Fail { - fn from(s: String) -> Fail { - Fail::host(s) - } -} - -#[derive(Debug, Clone, Serialize)] -pub struct Descriptor { - pub method: String, - pub title: String, - pub description: String, - pub kind: String, - pub network: bool, - pub destructive: bool, - pub json: bool, -} - -type Pending = Arc>>>>; -/// Request id -> the webview's job id. -type Jobs = Arc>>; - -/// Job ids come from the webview and are echoed back in events: short and plain only. -pub fn valid_job(job: &str) -> bool { - !job.is_empty() && job.len() <= 64 && job.bytes().all(|b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b':' | b'.')) -} - -pub struct Running { - child: Mutex>, - secret: [u8; 32], - pending: Pending, - jobs: Jobs, - next: AtomicU64, - alive: Arc, - pub version: String, - pub protocol: String, - pub methods: Vec, -} - -impl Running { - pub fn kind(&self, method: &str) -> Option { - self.methods.iter().find(|d| d.method == method).and_then(|d| Kind::parse(&d.kind)) - } - pub fn descriptor(&self, method: &str) -> Option<&Descriptor> { - self.methods.iter().find(|d| d.method == method) - } - pub fn mint(&self, method: &str, params: &Value) -> Result { - let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_err(|e| e.to_string())?.as_millis() as u64; - confirm::mint(&self.secret, method, params, now, &confirm::new_nonce()?) - } - - fn send(&self, id: u64, method: &str, params: &Value) -> Result>, Fail> { - if !self.alive.load(Ordering::SeqCst) { - return Err(Fail::host("the storebound sidecar stopped")); - } - let (tx, rx) = oneshot::channel(); - self.pending.lock().map_err(|_| Fail::host("sidecar state poisoned"))?.insert(id, tx); - let mut line = serde_json::to_vec(&json!({ "jsonrpc": "2.0", "id": id, "method": method, "params": params })).map_err(|e| Fail::host(e.to_string()))?; - line.push(b'\n'); - let mut child = self.child.lock().map_err(|_| Fail::host("sidecar state poisoned"))?; - match child.as_mut() { - Some(c) => c.write(&line).map_err(|e| Fail::host(format!("sidecar write: {e}")))?, - None => return Err(Fail::host("the storebound sidecar stopped")), - } - Ok(rx) - } - - pub async fn call(&self, method: &str, params: Value) -> Result { - self.call_job(method, params, None).await - } - - /// A call whose progress events carry `job`, and which `cancel_job` can stop. - pub async fn call_job(&self, method: &str, params: Value, job: Option<&str>) -> Result { - let id = self.next.fetch_add(1, Ordering::SeqCst); - if let Some(j) = job { - if let Ok(mut m) = self.jobs.lock() { - m.insert(id, j.to_string()); - } - } - let res = match self.send(id, method, ¶ms) { - Ok(rx) => rx.await.map_err(|_| Fail::host("the storebound sidecar stopped")).and_then(|r| r), - Err(e) => Err(e), - }; - if job.is_some() { - if let Ok(mut m) = self.jobs.lock() { - m.remove(&id); - } - } - res - } - - /// Cancels every running request of a job (`$/cancelRequest`; they answer -32800). Returns how many. - pub fn cancel_job(&self, job: &str) -> usize { - let ids: Vec = self.jobs.lock().map(|m| m.iter().filter(|(_, j)| j.as_str() == job).map(|(id, _)| *id).collect()).unwrap_or_default(); - for id in &ids { - self.notify("$/cancelRequest", json!({ "id": id })); - } - ids.len() - } - - pub fn notify(&self, method: &str, params: Value) { - let mut line = match serde_json::to_vec(&json!({ "jsonrpc": "2.0", "method": method, "params": params })) { - Ok(l) => l, - Err(_) => return, - }; - line.push(b'\n'); - if let Ok(mut c) = self.child.lock() { - if let Some(c) = c.as_mut() { - let _ = c.write(&line); - } - } - } - - pub fn alive(&self) -> bool { - self.alive.load(Ordering::SeqCst) - } - - pub fn kill(&self) { - self.alive.store(false, Ordering::SeqCst); - if let Ok(mut c) = self.child.lock() { - if let Some(c) = c.take() { - let _ = c.kill(); - } - } - } -} - -#[derive(Default)] -pub struct Sidecar { - current: tokio::sync::Mutex>>, -} - -/// Progress of a running request, forwarded to the main window as `sidecar-progress`. -#[derive(Clone, Serialize)] -pub struct Progress { - pub params: Value, - #[serde(skip_serializing_if = "Option::is_none")] - pub job: Option, -} - -impl Sidecar { - /// The running sidecar, started (and greeted) if needed. - pub async fn get(&self, app: &AppHandle) -> Result, Fail> { - let mut cur = self.current.lock().await; - if let Some(r) = cur.as_ref() { - if r.alive() { - return Ok(r.clone()); - } - } - let r = Arc::new(start(app).await?); - *cur = Some(r.clone()); - Ok(r) - } - - /// The running sidecar, if there is one (never starts it). - pub async fn current(&self) -> Option> { - self.current.lock().await.as_ref().filter(|r| r.alive()).cloned() - } - - /// Stops the sidecar; the next call starts a fresh one (after a tool install, so it sees the new tool paths). - pub async fn shutdown(&self) { - if let Some(r) = self.current.lock().await.take() { - r.kill(); - } - } - - /// Non-async variant for the exit path (the event loop is ending). - pub fn shutdown_now(&self) { - if let Ok(mut cur) = self.current.try_lock() { - if let Some(r) = cur.take() { - r.kill(); - } - } - } -} - -/// Tools the app manages in its data folder (Dependencies screen): `tools/bin` (ffmpeg, ffprobe) goes in front of -/// PATH for every storebound command, `tools/chrome/` is the Chrome for Testing it renders with. Only -/// paths that exist are passed. -pub fn managed_tool_env(data_dir: &std::path::Path) -> Vec<(String, String)> { - let mut env = Vec::new(); - let bin = data_dir.join("tools").join("bin"); - if bin.is_dir() { - env.push(("STOREBOUND_TOOL_PATH".to_string(), bin.to_string_lossy().to_string())); - } - let chrome = data_dir.join("tools").join("chrome").join(policy::chrome_executable()); - if chrome.is_file() { - env.push(("STOREBOUND_CHROME".to_string(), chrome.to_string_lossy().to_string())); - } - env -} - -async fn start(app: &AppHandle) -> Result { - let tool_env = app.path().app_data_dir().map(|d| managed_tool_env(&d)).unwrap_or_default(); - let cmd = app - .shell() - .sidecar(policy::SIDECAR) - .map_err(|e| Fail::host(format!("sidecar missing: {e} (bun tools/build-sidecars.ts --host)")))? - .args(policy::SIDECAR_ARGS) - .envs(tool_env) - .set_raw_out(true) - .set_process_group(true); - let (mut rx, child) = cmd.spawn().map_err(|e| Fail::host(format!("sidecar did not start: {e}")))?; - let pending: Pending = Arc::new(Mutex::new(HashMap::new())); - let jobs: Jobs = Arc::new(Mutex::new(HashMap::new())); - let alive = Arc::new(AtomicBool::new(true)); - - let (p, a, handle, jb) = (pending.clone(), alive.clone(), app.clone(), jobs.clone()); - tauri::async_runtime::spawn(async move { - let mut out: Vec = Vec::new(); - let mut err: Vec = Vec::new(); - while let Some(ev) = rx.recv().await { - match ev { - CommandEvent::Stdout(bytes) => { - out.extend_from_slice(&bytes); - while let Some(nl) = out.iter().position(|b| *b == b'\n') { - let line: Vec = out.drain(..=nl).collect(); - route(&handle, &p, &jb, &line); - } - } - CommandEvent::Stderr(bytes) => { - err.extend_from_slice(&bytes); - while let Some(nl) = err.iter().position(|b| *b == b'\n') { - let line: Vec = err.drain(..=nl).collect(); - if cfg!(debug_assertions) { - eprint!("[sidecar] {}", String::from_utf8_lossy(&line)); - } - } - } - CommandEvent::Terminated(_) | CommandEvent::Error(_) => break, - _ => {} - } - } - a.store(false, Ordering::SeqCst); - if let Ok(mut map) = p.lock() { - for (_, tx) in map.drain() { - let _ = tx.send(Err(Fail::host("the storebound sidecar stopped"))); - } - } - }); - - let secret = confirm::new_secret()?; - let mut running = Running { - child: Mutex::new(Some(child)), - secret, - pending, - jobs, - next: AtomicU64::new(1), - alive, - version: String::new(), - protocol: String::new(), - methods: Vec::new(), - }; - let hello = tokio::time::timeout( - Duration::from_secs(20), - running.call("session.hello", json!({ "secret": confirm::hex(&secret), "client": { "name": "storebound-desktop", "version": env!("CARGO_PKG_VERSION") } })), - ) - .await - .map_err(|_| Fail::host("the storebound sidecar did not answer session.hello"))??; - running.protocol = hello["protocol"].as_str().unwrap_or_default().to_string(); - running.version = hello["server"]["version"].as_str().unwrap_or_default().to_string(); - if running.protocol != "storebound-desktop/1" { - running.kill(); - return Err(Fail::host(format!("unsupported sidecar protocol '{}'", running.protocol))); - } - running.methods = hello["methods"] - .as_array() - .map(|a| { - a.iter() - .map(|m| Descriptor { - method: m["method"].as_str().unwrap_or_default().into(), - title: m["title"].as_str().unwrap_or_default().into(), - description: m["description"].as_str().unwrap_or_default().into(), - kind: m["kind"].as_str().unwrap_or_default().into(), - network: m["network"].as_bool().unwrap_or(true), - destructive: m["destructive"].as_bool().unwrap_or(true), - json: m["json"].as_bool().unwrap_or(false), - }) - .collect() - }) - .unwrap_or_default(); - Ok(running) -} - -fn route(app: &AppHandle, pending: &Pending, jobs: &Jobs, line: &[u8]) { - let Ok(msg) = serde_json::from_slice::(line) else { return }; - if let Some(id) = msg.get("id").and_then(Value::as_u64) { - let tx = pending.lock().ok().and_then(|mut m| m.remove(&id)); - if let Some(tx) = tx { - let res = match msg.get("error") { - Some(e) => Err(Fail { - code: e["code"].as_i64().unwrap_or(-32603), - message: e["message"].as_str().unwrap_or("error").to_string(), - data: e.get("data").cloned().unwrap_or(Value::Null), - }), - None => Ok(msg.get("result").cloned().unwrap_or(Value::Null)), - }; - let _ = tx.send(res); - } - } else if msg.get("method").and_then(Value::as_str) == Some("progress") { - let job = msg["params"]["requestId"].as_u64().and_then(|id| jobs.lock().ok().and_then(|m| m.get(&id).cloned())); - let _ = app.emit_to(policy::MAIN, "sidecar-progress", Progress { params: msg["params"].clone(), job }); - } -} diff --git a/apps/desktop/src-tauri/src/store.rs b/apps/desktop/src-tauri/src/store.rs deleted file mode 100644 index be386b0..0000000 --- a/apps/desktop/src-tauri/src/store.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! The app's own small state in its data folder: the project list and account labels (never secrets). Written -//! atomically (temp file, then rename) so a crash never leaves half a file. -use std::fs; -use std::path::{Path, PathBuf}; - -use serde::{Deserialize, Serialize}; - -use crate::accounts::Account; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Project { - pub id: String, - /// Absolute folder that holds storebound.config.ts. - pub path: String, - pub name: String, - #[serde(default)] - pub play_account: Option, - #[serde(default)] - pub apple_account: Option, - pub added: String, -} - -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct State { - #[serde(default)] - pub projects: Vec, - #[serde(default)] - pub accounts: Vec, -} - -pub const FILE: &str = "state.json"; - -pub fn load(dir: &Path) -> State { - fs::read_to_string(dir.join(FILE)).ok().and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default() -} - -pub fn save(dir: &Path, state: &State) -> Result<(), String> { - fs::create_dir_all(dir).map_err(|e| e.to_string())?; - let tmp: PathBuf = dir.join(format!("{FILE}.tmp")); - let json = serde_json::to_string_pretty(state).map_err(|e| e.to_string())?; - fs::write(&tmp, json + "\n").map_err(|e| e.to_string())?; - fs::rename(&tmp, dir.join(FILE)).map_err(|e| e.to_string()) -} - -/// Random id (hex) for projects and accounts. -pub fn new_id() -> Result { - let mut b = [0u8; 8]; - getrandom::fill(&mut b).map_err(|e| e.to_string())?; - Ok(crate::confirm::hex(&b)) -} - -/// Current time as an RFC 3339 UTC string, without a date crate. -pub fn now_iso() -> String { - let secs = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0); - iso_from_unix(secs) -} - -pub fn iso_from_unix(secs: u64) -> String { - let days = (secs / 86_400) as i64; - let rem = secs % 86_400; - // civil-from-days (Howard Hinnant) - let z = days + 719_468; - let era = z.div_euclid(146_097); - let doe = z - era * 146_097; - let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; - let y = yoe + era * 400; - let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); - let mp = (5 * doy + 2) / 153; - let d = doy - (153 * mp + 2) / 5 + 1; - let m = if mp < 10 { mp + 3 } else { mp - 9 }; - let y = if m <= 2 { y + 1 } else { y }; - format!("{y:04}-{m:02}-{d:02}T{:02}:{:02}:{:02}Z", rem / 3600, rem % 3600 / 60, rem % 60) -} diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json deleted file mode 100644 index aaf7859..0000000 --- a/apps/desktop/src-tauri/tauri.conf.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "storebound", - "mainBinaryName": "storebound-desktop", - "version": "../package.json", - "identifier": "dev.storebound.desktop", - "build": { - "frontendDist": "../dist", - "devUrl": "http://127.0.0.1:1420", - "beforeDevCommand": "bun run dev", - "beforeBuildCommand": "bun run build" - }, - "app": { - "windows": [ - { - "label": "main", - "title": "storebound", - "width": 1200, - "height": 780, - "minWidth": 880, - "minHeight": 560, - "resizable": true, - "center": true - } - ], - "security": { - "csp": { - "default-src": "'self'", - "script-src": "'self'", - "style-src": "'self'", - "img-src": "'self' data:", - "font-src": "'self'", - "connect-src": "ipc: http://ipc.localhost", - "object-src": "'none'", - "base-uri": "'none'", - "frame-src": "'none'", - "form-action": "'none'" - }, - "devCsp": { - "default-src": "'self'", - "script-src": "'self'", - "style-src": "'self' 'unsafe-inline'", - "img-src": "'self' data:", - "font-src": "'self'", - "connect-src": "ipc: http://ipc.localhost ws://127.0.0.1:1420 http://127.0.0.1:1420", - "object-src": "'none'", - "base-uri": "'none'", - "frame-src": "'none'", - "form-action": "'none'" - }, - "freezePrototype": true, - "capabilities": ["main-window"] - } - }, - "bundle": { - "active": true, - "targets": "all", - "category": "DeveloperTool", - "shortDescription": "Every game's releases on both stores in one window", - "copyright": "MIT license, storebound contributors", - "externalBin": ["binaries/storebound"], - "createUpdaterArtifacts": false, - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "macOS": { - "minimumSystemVersion": "11.0" - } - }, - "plugins": { - "deep-link": { - "desktop": { - "schemes": ["storebound"] - } - }, - "updater": { - "endpoints": ["https://storebound.dev/desktop/latest.json"], - "pubkey": "PLACEHOLDER-minisign-public-key-set-in-D5" - } - } -} diff --git a/apps/desktop/src-tauri/tests/accounts.rs b/apps/desktop/src-tauri/tests/accounts.rs deleted file mode 100644 index 1e8d1e4..0000000 --- a/apps/desktop/src-tauri/tests/accounts.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Account handling: what is stored where, what is shown, and the key checks. No real keys anywhere. -use serde_json::{json, Map}; -use storebound_desktop::accounts::*; -use storebound_desktop::commands::{dialog_excerpt, dialog_summary, has_config, tilde}; -use storebound_desktop::sidecar::managed_tool_env; -use storebound_desktop::store::{self, Project, State}; - -const FAKE_PEM: &str = "-----BEGIN PRIVATE KEY-----\nnot-a-real-key\n-----END PRIVATE KEY-----"; - -#[test] -fn shown_metadata_carries_no_secret() { - let a = Account { id: "abc".into(), label: "Studio Play".into(), kind: AccountKind::Play, created: "2026-10-10T00:00:00Z".into() }; - let v = serde_json::to_value(&a).unwrap(); - assert_eq!(v.as_object().unwrap().keys().cloned().collect::>(), vec!["created", "id", "kind", "label"]); - assert_eq!(v["kind"], "play"); -} - -#[test] -fn secrets_map_to_the_cli_env_names() { - let mut m = Map::new(); - Secret::Apple { key_id: "ABCDEFGH12".into(), issuer_id: "00000000-0000-0000-0000-000000000000".into(), p8: FAKE_PEM.into() }.to_credentials(&mut m); - Secret::Play { service_account: "{}".into() }.to_credentials(&mut m); - assert_eq!(m.keys().cloned().collect::>(), vec!["ASC_ISSUER_ID", "ASC_KEY_ID", "ASC_P8", "PLAY_SERVICE_ACCOUNT"]); - let s = Secret::Play { service_account: "{}".into() }; - let back: Secret = serde_json::from_str(&serde_json::to_string(&s).unwrap()).unwrap(); - assert_eq!(back, s); -} - -#[test] -fn key_checks() { - let sa = json!({ "type": "service_account", "client_email": "bot@example.com", "private_key": FAKE_PEM }).to_string(); - assert!(check_service_account(&sa).is_ok()); - assert!(check_service_account("{\"client_email\":\"x@example.com\"}").is_err()); - assert!(check_service_account("not json").is_err()); - assert_eq!(check_key_id(" ABCDEFGH12 ").unwrap(), "ABCDEFGH12"); - assert!(check_key_id("abc").is_err()); - assert!(check_key_id("abcdefgh12").is_err()); - let upper = "00000000-0000-0000-0000-00000000000A"; - assert_eq!(check_issuer_id(upper).unwrap(), upper.to_ascii_lowercase()); - assert!(check_issuer_id("0000-0000").is_err()); - assert!(check_p8(FAKE_PEM).is_ok()); - assert!(check_p8("hello").is_err()); - assert!(check_label("").is_err()); - assert!(check_label("a\u{7}").is_err()); - assert!(check_label(&"x".repeat(61)).is_err()); - assert_eq!(check_label(" Studio ").unwrap(), "Studio"); -} - -#[test] -fn state_file_round_trip_is_atomic_and_tolerant() { - let dir = tempfile::tempdir().unwrap(); - assert_eq!(store::load(dir.path()), State::default()); - let st = State { - projects: vec![Project { id: "1".into(), path: "/games/a".into(), name: "A".into(), play_account: Some("p".into()), apple_account: None, added: "2026-10-10T00:00:00Z".into() }], - accounts: vec![], - }; - store::save(dir.path(), &st).unwrap(); - assert_eq!(store::load(dir.path()), st); - assert!(!dir.path().join("state.json.tmp").exists()); - let text = std::fs::read_to_string(dir.path().join("state.json")).unwrap(); - assert!(text.contains("\"playAccount\": \"p\"")); - std::fs::write(dir.path().join("state.json"), "{broken").unwrap(); - assert_eq!(store::load(dir.path()), State::default()); - assert_eq!(store::iso_from_unix(0), "1970-01-01T00:00:00Z"); - assert_eq!(store::iso_from_unix(1_700_000_000), "2023-11-14T22:13:20Z"); - assert_eq!(store::new_id().unwrap().len(), 16); -} - -#[test] -fn managed_tools_are_passed_only_when_present() { - let dir = tempfile::tempdir().unwrap(); - assert!(managed_tool_env(dir.path()).is_empty()); - std::fs::create_dir_all(dir.path().join("tools/bin")).unwrap(); - let env = managed_tool_env(dir.path()); - assert_eq!(env.len(), 1); - assert_eq!(env[0].0, "STOREBOUND_TOOL_PATH"); - assert!(env[0].1.ends_with("bin")); -} - -#[test] -fn confirm_dialog_text() { - let s = dialog_summary("Push Play listing text", "Title and descriptions.", "Prism Puddle", true, true); - assert!(s.starts_with("Push Play listing text · Prism Puddle")); - assert!(s.contains("not public until you submit")); - assert!(s.contains("replaces")); - let out: String = (1..=40).map(|i| format!("line {i}\n")).collect(); - let ex = dialog_excerpt(&out); - assert!(ex.starts_with("(12 earlier lines")); - assert!(ex.ends_with("line 40")); - assert!(dialog_excerpt(&"x".repeat(5000)).chars().count() < 2_500); - assert_eq!(tilde("would write /home/you/a/index.html", Some("/home/you/")), "would write ~/a/index.html"); - assert_eq!(tilde("/x", None), "/x"); - assert_eq!(tilde("/x", Some("/")), "/x"); - let dir = tempfile::tempdir().unwrap(); - assert!(!has_config(dir.path())); - std::fs::write(dir.path().join("storebound.config.ts"), "export default {}").unwrap(); - assert!(has_config(dir.path())); -} diff --git a/apps/desktop/src-tauri/tests/confirm.rs b/apps/desktop/src-tauri/tests/confirm.rs deleted file mode 100644 index c26c06b..0000000 --- a/apps/desktop/src-tauri/tests/confirm.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! The host mints exactly the tokens the sidecar verifies: same canonical JSON, same HMAC, same shape as -//! cli/lib/confirm-token.ts. The vector is shared with tests/serve.test.ts in the repository root. -use serde_json::json; -use storebound_desktop::confirm::*; - -fn secret() -> Vec { - (0u8..32).collect() -} - -#[test] -fn shared_vector() { - let p = json!({ "project": "/tmp/game", "args": { "only": "en-US,tr", "b": [1, true, null, "ü\"\n"] } }); - assert_eq!(canonical(&p).unwrap(), "{\"args\":{\"b\":[1,true,null,\"ü\\\"\\n\"],\"only\":\"en-US,tr\"},\"project\":\"/tmp/game\"}"); - assert_eq!(mac(&secret(), "play.listing", &p, 1_700_000_060_000, "AAAAAAAAAAAAAAAAAAAAAA").unwrap(), "JhSxUL2ej4cm4yrmlcA23rTCPq3YMKJthbB7n6aixxw"); -} - -#[test] -fn canonical_rules() { - assert_eq!(canonical(&json!({ "b": 1, "a": [true, null, "x"], "c": { "z": "" } })).unwrap(), "{\"a\":[true,null,\"x\"],\"b\":1,\"c\":{\"z\":\"\"}}"); - assert!(canonical(&json!({ "f": 0.5 })).is_err()); - assert!(canonical(&json!({ "f": 9_007_199_254_740_992u64 })).is_err()); - assert_eq!(canonical(&json!(-9_007_199_254_740_991i64)).unwrap(), "-9007199254740991"); - // keys sort by UTF-16 code units like JavaScript: "\u{e9}" (U+00E9) after "z", astral after BMP private use - assert_eq!(canonical(&json!({ "é": 1, "z": 2, "A": 3 })).unwrap(), "{\"A\":3,\"z\":2,\"é\":1}"); - assert_eq!(canonical(&json!("\u{1}\t = t.split('.').collect(); - assert_eq!(parts.len(), 4); - assert_eq!(parts[0], "sbt1"); - assert_eq!(parts[1], (1_000 + TTL_MS).to_string()); - assert_eq!(parts[3].len(), 43); - assert_eq!(parts[3], mac(&secret(), "play.listing", &p, 1_000 + TTL_MS, "AAAAAAAAAAAAAAAAAAAAAA").unwrap()); - let n = new_nonce().unwrap(); - assert_eq!(n.len(), 22); - assert_ne!(n, new_nonce().unwrap()); - assert_ne!(new_secret().unwrap(), new_secret().unwrap()); - assert_eq!(hex(&[0, 15, 255]), "000fff"); -} diff --git a/apps/desktop/src-tauri/tests/d2.rs b/apps/desktop/src-tauri/tests/d2.rs deleted file mode 100644 index c5b886f..0000000 --- a/apps/desktop/src-tauri/tests/d2.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! Desktop D2: offline cache, tool download manifest and rules, job ids. -use std::io::Write; - -use serde_json::json; -use storebound_desktop::cache::{key, Cache}; -use storebound_desktop::downloads::{self, chrome_download, download_allowed, inside, manifest, unpack}; -use storebound_desktop::sidecar::valid_job; -use url::Url; - -#[test] -fn cache_keeps_the_last_read_per_project_method_and_args() { - let c = Cache::in_memory().unwrap(); - assert_eq!(c.get("p1", "health", &json!({})).unwrap(), None); - c.put("p1", "health", &json!({}), &json!({ "ok": true, "n": 1 }), "2026-10-10T10:00:00Z").unwrap(); - c.put("p1", "health", &json!({}), &json!({ "ok": true, "n": 2 }), "2026-10-10T11:00:00Z").unwrap(); - c.put("p1", "reviews.list", &json!({ "days": 14, "store": "play" }), &json!([1]), "2026-10-10T09:00:00Z").unwrap(); - c.put("p2", "health", &json!({}), &json!({ "n": 9 }), "2026-10-10T08:00:00Z").unwrap(); - let e = c.get("p1", "health", &json!({})).unwrap().unwrap(); - assert_eq!((e.value["n"].clone(), e.at.as_str()), (json!(2), "2026-10-10T11:00:00Z")); - // argument order does not matter (canonical JSON), values do - assert!(c.get("p1", "reviews.list", &json!({ "store": "play", "days": 14 })).unwrap().is_some()); - assert!(c.get("p1", "reviews.list", &json!({ "store": "apple", "days": 14 })).unwrap().is_none()); - assert_eq!(key("a", "m", &json!({ "b": 1, "a": 2 })), key("a", "m", &json!({ "a": 2, "b": 1 }))); - assert_eq!(c.stamps("p1").unwrap().len(), 2); - c.forget_project("p1").unwrap(); - assert!(c.get("p1", "health", &json!({})).unwrap().is_none()); - assert!(c.get("p2", "health", &json!({})).unwrap().is_some()); - c.clear().unwrap(); - assert!(c.get("p2", "health", &json!({})).unwrap().is_none()); -} - -#[test] -fn cache_survives_a_reopen() { - let dir = tempfile::tempdir().unwrap(); - Cache::open(dir.path()).unwrap().put("p", "m", &json!({}), &json!({ "x": 1 }), "t").unwrap(); - assert_eq!(Cache::open(dir.path()).unwrap().get("p", "m", &json!({})).unwrap().unwrap().value, json!({ "x": 1 })); -} - -#[test] -fn manifest_pins_every_desktop_target_with_hashes() { - let m = manifest().unwrap(); - for t in ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] { - let list = m.ffmpeg.targets.get(t).unwrap_or_else(|| panic!("no ffmpeg for {t}")); - let mut outs = vec![]; - for a in list { - let u = Url::parse(&a.url).unwrap(); - assert!(download_allowed(&u), "{}", a.url); - assert!(!a.url.contains("/latest/"), "pinned URLs only: {}", a.url); - assert_eq!(a.sha256.len(), 64); - assert!(a.sha256.chars().all(|c| c.is_ascii_hexdigit())); - assert!(a.size > 1_000_000); - assert!(a.kind == "zip" || a.kind == "tar.xz"); - for f in &a.files { - inside(std::path::Path::new("/x"), &f.from).unwrap(); - outs.push(f.to.clone()); - } - } - outs.sort(); - let exe = if t.contains("windows") { ".exe" } else { "" }; - assert_eq!(outs, vec![format!("ffmpeg{exe}"), format!("ffprobe{exe}")], "{t}"); - } - assert!(download_allowed(&Url::parse(&m.chrome.endpoint).unwrap())); - assert_eq!(m.chrome.mac_team_id, "EQHXZ8M8AV"); - assert!(m.ffmpeg.license.contains("GPL")); -} - -#[test] -fn downloads_only_over_https_from_the_listed_hosts() { - for ok in ["https://ffmpeg.martin-riedl.de/download/x.zip", "https://github.com/BtbN/FFmpeg-Builds/releases/download/a/b.zip", "https://storage.googleapis.com/chrome-for-testing-public/1/mac-arm64/chrome-mac-arm64.zip"] { - assert!(download_allowed(&Url::parse(ok).unwrap()), "{ok}"); - } - for bad in ["http://ffmpeg.martin-riedl.de/x.zip", "https://evil.test/x.zip", "https://github.com.evil.test/x", "https://storage.googleapis.com:8443/x", "file:///tmp/x.zip"] { - assert!(!download_allowed(&Url::parse(bad).unwrap()), "{bad}"); - } - assert!(!download_allowed(&Url::parse(&format!("https://{}@github.com/x", "u")).unwrap())); -} - -#[test] -fn chrome_for_testing_endpoint_answer() { - let j = json!({ "channels": { "Stable": { "version": "141.0.7390.54", "downloads": { "chrome": [ - { "platform": "linux64", "url": "https://storage.googleapis.com/chrome-for-testing-public/141.0.7390.54/linux64/chrome-linux64.zip" }, - { "platform": "mac-arm64", "url": "https://storage.googleapis.com/chrome-for-testing-public/141.0.7390.54/mac-arm64/chrome-mac-arm64.zip" } - ] } } } }); - let (v, u) = chrome_download(&j, "Stable", "mac-arm64").unwrap(); - assert_eq!(v, "141.0.7390.54"); - assert!(u.as_str().ends_with("chrome-mac-arm64.zip")); - assert!(chrome_download(&j, "Stable", "win64").is_err()); - let evil = json!({ "channels": { "Stable": { "version": "1", "downloads": { "chrome": [{ "platform": "win64", "url": "https://evil.test/chrome.zip" }] } } } }); - assert!(chrome_download(&evil, "Stable", "win64").is_err()); -} - -#[test] -fn archive_paths_stay_inside() { - let base = std::path::Path::new("/data/tools/.staging"); - assert!(inside(base, "ffmpeg-n9/bin/ffmpeg").is_ok()); - for bad in ["../ffmpeg", "/usr/bin/ffmpeg", "a/../../b", "./x"] { - assert!(inside(base, bad).is_err(), "{bad}"); - } -} - -#[test] -fn zip_unpack_refuses_entries_outside_the_folder() { - if cfg!(target_os = "macos") { - return; // macOS unpacks with ditto, which never writes outside the target folder - } - let dir = tempfile::tempdir().unwrap(); - let zip_path = dir.path().join("evil.zip"); - { - let f = std::fs::File::create(&zip_path).unwrap(); - let mut z = zip::ZipWriter::new(f); - z.start_file("../escape.txt", zip::write::SimpleFileOptions::default()).unwrap(); - z.write_all(b"x").unwrap(); - z.finish().unwrap(); - } - let out = dir.path().join("out"); - let _ = unpack(&zip_path, "zip", &out); - assert!(!dir.path().join("escape.txt").exists()); -} - -#[test] -fn download_info_lists_both_tools() { - let dir = tempfile::tempdir().unwrap(); - let info = downloads::info(dir.path()).unwrap(); - assert_eq!(info.iter().map(|i| i.tool).collect::>(), vec!["ffmpeg", "chrome"]); - assert!(info.iter().all(|i| !i.installed)); - assert!(info[0].available && info[0].size.unwrap_or(0) > 0); -} - -#[test] -fn job_ids_are_short_and_plain() { - for ok in ["portfolio-1", "dl:ffmpeg:3", "a.b_c"] { - assert!(valid_job(ok), "{ok}"); - } - for bad in ["", "a b", "x\"y", "