From e0773ba2994fee38ad36cf11b4e9a31e28587864 Mon Sep 17 00:00:00 2001 From: Davito Date: Fri, 30 Oct 2026 15:58:58 +0100 Subject: [PATCH] =?UTF-8?q?Erste=20Version:=20Tauri-Grundger=C3=BCst,=20Wi?= =?UTF-8?q?nGet-Anbindung,=20Selbst-Updater=20und=20Release-Skript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitattributes | 5 + .github/copilot-instructions.md | 64 + .gitignore | 22 + AGENTS.md | 64 + README.md | 100 + build.ps1 | 47 + index.html | 12 + package-lock.json | 2159 +++++++ package.json | 35 + scripts/cdp.mjs | 35 + scripts/release.ps1 | 403 ++ src-tauri/Cargo.lock | 5409 +++++++++++++++++ src-tauri/Cargo.toml | 39 + src-tauri/build.rs | 3 + src-tauri/capabilities/default.json | 7 + src-tauri/icons/128x128.png | Bin 0 -> 4582 bytes src-tauri/icons/128x128@2x.png | Bin 0 -> 9132 bytes src-tauri/icons/32x32.png | Bin 0 -> 1287 bytes src-tauri/icons/64x64.png | Bin 0 -> 2608 bytes src-tauri/icons/Square107x107Logo.png | Bin 0 -> 3918 bytes src-tauri/icons/Square142x142Logo.png | Bin 0 -> 5087 bytes src-tauri/icons/Square150x150Logo.png | Bin 0 -> 5162 bytes src-tauri/icons/Square284x284Logo.png | Bin 0 -> 10271 bytes src-tauri/icons/Square30x30Logo.png | Bin 0 -> 1207 bytes src-tauri/icons/Square310x310Logo.png | Bin 0 -> 11038 bytes src-tauri/icons/Square44x44Logo.png | Bin 0 -> 1825 bytes src-tauri/icons/Square71x71Logo.png | Bin 0 -> 2549 bytes src-tauri/icons/Square89x89Logo.png | Bin 0 -> 3239 bytes src-tauri/icons/StoreLogo.png | Bin 0 -> 1982 bytes src-tauri/icons/icon.icns | Bin 0 -> 105600 bytes src-tauri/icons/icon.ico | Bin 0 -> 19716 bytes src-tauri/icons/icon.png | Bin 0 -> 20016 bytes src-tauri/icons/source.png | Bin 0 -> 90107 bytes src-tauri/src/commands/mod.rs | 126 + src-tauri/src/error.rs | 42 + src-tauri/src/lib.rs | 146 + src-tauri/src/logging.rs | 37 + src-tauri/src/main.rs | 5 + src-tauri/src/managers/mod.rs | 36 + src-tauri/src/managers/winget/mod.rs | 190 + src-tauri/src/managers/winget/parser.rs | 349 ++ src-tauri/src/managers/winget/ps.rs | 128 + src-tauri/src/models.rs | 139 + src-tauri/src/process.rs | 68 + src-tauri/src/settings/mod.rs | 73 + src-tauri/src/updater/mod.rs | 430 ++ src-tauri/src/updater/policy.rs | 190 + src-tauri/tauri.conf.json | 48 + src-tauri/tests/fixtures/winget_list_de.txt | 133 + src-tauri/tests/fixtures/winget_search_de.txt | 12 + src-tauri/tests/fixtures/winget_show_de.txt | 35 + .../tests/fixtures/winget_upgrade_de.txt | 7 + src/App.svelte | 48 + src/app.css | 128 + src/components/DetailsPanel.svelte | 145 + src/components/Markdown.svelte | 23 + src/components/Modal.svelte | 53 + src/components/PackageView.svelte | 162 + src/components/SetupBanner.svelte | 50 + src/components/Sidebar.svelte | 59 + src/components/UpdateDialog.svelte | 100 + src/components/UpdatedNotice.svelte | 29 + src/components/VirtualTable.svelte | 216 + src/i18n/de.json | 190 + src/i18n/t.ts | 68 + src/lib/api.ts | 49 + src/lib/columns.ts | 25 + src/lib/markdown.ts | 14 + src/lib/table.ts | 19 + src/lib/types.ts | 93 + src/main.ts | 10 + src/stores/list.svelte.ts | 41 + src/stores/managers.svelte.ts | 53 + src/stores/packages.svelte.ts | 14 + src/stores/theme.svelte.ts | 45 + src/stores/updater.svelte.ts | 137 + src/views/Bundles.svelte | 13 + src/views/Discover.svelte | 27 + src/views/Installed.svelte | 18 + src/views/Settings.svelte | 201 + src/views/Updates.svelte | 18 + svelte.config.js | 5 + tsconfig.json | 19 + vite.config.ts | 18 + 84 files changed, 12688 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/copilot-instructions.md create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 README.md create mode 100644 build.ps1 create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/cdp.mjs create mode 100644 scripts/release.ps1 create mode 100644 src-tauri/Cargo.lock create mode 100644 src-tauri/Cargo.toml create mode 100644 src-tauri/build.rs create mode 100644 src-tauri/capabilities/default.json create mode 100644 src-tauri/icons/128x128.png create mode 100644 src-tauri/icons/128x128@2x.png create mode 100644 src-tauri/icons/32x32.png create mode 100644 src-tauri/icons/64x64.png create mode 100644 src-tauri/icons/Square107x107Logo.png create mode 100644 src-tauri/icons/Square142x142Logo.png create mode 100644 src-tauri/icons/Square150x150Logo.png create mode 100644 src-tauri/icons/Square284x284Logo.png create mode 100644 src-tauri/icons/Square30x30Logo.png create mode 100644 src-tauri/icons/Square310x310Logo.png create mode 100644 src-tauri/icons/Square44x44Logo.png create mode 100644 src-tauri/icons/Square71x71Logo.png create mode 100644 src-tauri/icons/Square89x89Logo.png create mode 100644 src-tauri/icons/StoreLogo.png create mode 100644 src-tauri/icons/icon.icns create mode 100644 src-tauri/icons/icon.ico create mode 100644 src-tauri/icons/icon.png create mode 100644 src-tauri/icons/source.png create mode 100644 src-tauri/src/commands/mod.rs create mode 100644 src-tauri/src/error.rs create mode 100644 src-tauri/src/lib.rs create mode 100644 src-tauri/src/logging.rs create mode 100644 src-tauri/src/main.rs create mode 100644 src-tauri/src/managers/mod.rs create mode 100644 src-tauri/src/managers/winget/mod.rs create mode 100644 src-tauri/src/managers/winget/parser.rs create mode 100644 src-tauri/src/managers/winget/ps.rs create mode 100644 src-tauri/src/models.rs create mode 100644 src-tauri/src/process.rs create mode 100644 src-tauri/src/settings/mod.rs create mode 100644 src-tauri/src/updater/mod.rs create mode 100644 src-tauri/src/updater/policy.rs create mode 100644 src-tauri/tauri.conf.json create mode 100644 src-tauri/tests/fixtures/winget_list_de.txt create mode 100644 src-tauri/tests/fixtures/winget_search_de.txt create mode 100644 src-tauri/tests/fixtures/winget_show_de.txt create mode 100644 src-tauri/tests/fixtures/winget_upgrade_de.txt create mode 100644 src/App.svelte create mode 100644 src/app.css create mode 100644 src/components/DetailsPanel.svelte create mode 100644 src/components/Markdown.svelte create mode 100644 src/components/Modal.svelte create mode 100644 src/components/PackageView.svelte create mode 100644 src/components/SetupBanner.svelte create mode 100644 src/components/Sidebar.svelte create mode 100644 src/components/UpdateDialog.svelte create mode 100644 src/components/UpdatedNotice.svelte create mode 100644 src/components/VirtualTable.svelte create mode 100644 src/i18n/de.json create mode 100644 src/i18n/t.ts create mode 100644 src/lib/api.ts create mode 100644 src/lib/columns.ts create mode 100644 src/lib/markdown.ts create mode 100644 src/lib/table.ts create mode 100644 src/lib/types.ts create mode 100644 src/main.ts create mode 100644 src/stores/list.svelte.ts create mode 100644 src/stores/managers.svelte.ts create mode 100644 src/stores/packages.svelte.ts create mode 100644 src/stores/theme.svelte.ts create mode 100644 src/stores/updater.svelte.ts create mode 100644 src/views/Bundles.svelte create mode 100644 src/views/Discover.svelte create mode 100644 src/views/Installed.svelte create mode 100644 src/views/Settings.svelte create mode 100644 src/views/Updates.svelte create mode 100644 svelte.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8275441 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto eol=lf +*.ps1 text working-tree-encoding=UTF-8 +*.png binary +*.ico binary +*.icns binary diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..27a99d8 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,64 @@ +# VitosUpdater – Projektanweisungen + +## Dauerhafte Regel: Release auf Gitea + +Wenn der Nutzer schreibt 'Bau den Installer neu und lade es auf Gitea hoch' (oder sinngemäß: Installer neu bauen / auf Gitea hochladen / Release erstellen), führe IMMER scripts/release.ps1 aus: Version erhöhen, Installer bauen und signieren, latest.json erzeugen, committen, taggen, pushen, Gitea-Release inklusive Assets erstellen und das rollende Release 'latest' aktualisieren. Danach auf Deutsch zusammenfassen (Version, Links, Ergebnis der Prüfung). Antworte immer auf Deutsch, Code und Kommentare auf Englisch. + +Hinweise zum Aufruf (aus dem Projektordner, `pwsh` oder `powershell`): + +- Standard (Patch-Version): `powershell -ExecutionPolicy Bypass -File scripts\release.ps1` +- Größere Sprünge: `-Bump minor`, `-Bump major` oder `-Version x.y.z` +- Nur bauen, nichts veröffentlichen: `-NoPublish` (Ergebnis in `release-out\`) +- Das Skript bricht bei jedem Fehler ab und nimmt eine Versionsänderung vor dem Commit wieder zurück. +- Fehlt `GITEA_TOKEN`, dem Nutzer auf Deutsch erklären, wie er es anlegt (Gitea → Einstellungen → Anwendungen → Zugriffstoken, Berechtigung `write:repository`), und warten. Das Token nie ausgeben oder speichern. + +## Projektüberblick + +VitosUpdater ist ein Klon von UniGetUI für Windows 10/11: eine einheitliche Oberfläche zum Suchen, Installieren, Aktualisieren und Deinstallieren von Software über mehrere Paketmanager (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg). Technik: Rust (Edition 2021) + Tauri 2, Frontend mit Svelte 5 + TypeScript + Tailwind CSS 4. Der Selbst-Updater nutzt `tauri-plugin-updater` mit `latest.json` auf dem eigenen Gitea (`https://gitea.davito.synology.me`, Repo `Davito/VitosUpdater`). + +## Ordnerstruktur + +``` +build.ps1 Komfortskript für Installer-Build +scripts\release.ps1 Vollständiger Release-Ablauf (siehe oben) +scripts\cdp.mjs Entwicklungshilfe: JavaScript in der laufenden WebView2 auswerten +src\ Frontend (Svelte) + i18n\de.json, t.ts ALLE sichtbaren Texte (nur Deutsch) und Formatierer + views\ Ansichten (Entdecken, Updates, Installiert, Bundles, Einstellungen) + components\ UI-Bausteine (Sidebar, VirtualTable, DetailsPanel, UpdateDialog, ...) + stores\ Zustand (Svelte-5-Runes) + lib\ api.ts (typisierte Tauri-Befehle), types.ts, Hilfsfunktionen +src-tauri\ Backend (Rust) + src\managers\ Trait PackageManager + ein Modul je Manager (winget\ ...) + src\updater\ Selbst-Update (mod.rs: Zustand, Scheduler, Befehle; policy.rs: reine Logik) + src\settings\, commands\, process.rs, logging.rs, models.rs, error.rs + tests\fixtures\ Echte Beispielausgaben der Paketmanager für Parser-Tests +``` + +Konfiguration und Zustand der App liegen in `%APPDATA%\VitosUpdater` (Logs unter `logs\`). + +## Bauen und Testen + +Voraussetzungen: Rust (MSVC), Node.js LTS, WebView2, VS Build Tools (C++), Tauri CLI (`cargo install tauri-cli --version "^2"`). + +- Entwickeln: `cargo tauri dev` (aus dem Projektordner) +- Rust prüfen: in `src-tauri` → `cargo check`, `cargo clippy --all-targets -- -D warnings`, `cargo test` +- Frontend prüfen: `npm run check` (svelte-check) und `npm run build` +- Das Projekt muss nach jeder Änderung ohne Fehler und Warnungen kompilieren. +- Parser immer mit echter Beispielausgabe testen (Fixtures in `src-tauri\tests\fixtures`). Keine CLI-Flags raten: im Zweifel lokal ausprobieren. + +## Sprache und Texte + +- Die Oberfläche ist ausschließlich Deutsch: UI-Texte, Schaltflächen, Menüs, Tooltips, Dialoge, Tray, Benachrichtigungen, Fehlermeldungen, Einstellungen, Installer, README. +- Korrektes Deutsch mit echten Umlauten und ß, korrekte Grammatik und Zeichensetzung, deutsche Formate (dd.MM.yyyy, 24 Stunden, Dezimalkomma, „1.234,5 MB“). +- Neutrale Ansprache ohne „du/Sie“ (z. B. „Updates suchen“, „Alle aktualisieren“); einheitliche Begriffe (Paket, Quelle, Aktualisierung, Deinstallieren, Warteschlange, Einstellungen). +- Alle Texte stehen zentral in `src/i18n/de.json` und werden über `t()`/`tp()` verwendet. Nie Texte in Komponenten hartcodieren. Ausnahme: Eigennamen (WinGet, Scoop, Chocolatey). +- Texte, die im Backend entstehen (Fehlermeldungen, Benachrichtigungen), sind ebenfalls Deutsch. +- Code, Bezeichner und Code-Kommentare sind Englisch. Commit-Nachrichten sind Deutsch (sie erscheinen als Release-Notizen im Update-Popup). + +## Geheimnisse + +- Niemals Geheimnisse einchecken oder ausgeben: Gitea-Token, Signierschlüssel (`updater.key`), Passwörter, `.env`-Dateien. +- Der private Signierschlüssel liegt außerhalb des Repos (`%USERPROFILE%\.vitosupdater\updater.key`); nur der öffentliche Schlüssel steht in `tauri.conf.json`. +- Das Gitea-Token kommt aus der Umgebungsvariable `GITEA_TOKEN` oder der git-ignorierten Datei `.env.local`. Git-Pushes laufen über den Windows Git Credential Manager. +- TLS-Prüfung nie abschalten (keine `dangerous_*`-Optionen im Updater). diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d13916 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +node_modules/ +dist/ +target/ +src-tauri/gen/ +release-out/ + +# Geheimnisse und lokale Konfiguration – niemals einchecken +*.key +*.key.pub.bak +.env +.env.* +!.env.example +*.pem +*.pfx +*.sig.key + +# Editor und Betriebssystem +.vscode/ +.idea/ +*.log +Thumbs.db +.DS_Store diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..27a99d8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,64 @@ +# VitosUpdater – Projektanweisungen + +## Dauerhafte Regel: Release auf Gitea + +Wenn der Nutzer schreibt 'Bau den Installer neu und lade es auf Gitea hoch' (oder sinngemäß: Installer neu bauen / auf Gitea hochladen / Release erstellen), führe IMMER scripts/release.ps1 aus: Version erhöhen, Installer bauen und signieren, latest.json erzeugen, committen, taggen, pushen, Gitea-Release inklusive Assets erstellen und das rollende Release 'latest' aktualisieren. Danach auf Deutsch zusammenfassen (Version, Links, Ergebnis der Prüfung). Antworte immer auf Deutsch, Code und Kommentare auf Englisch. + +Hinweise zum Aufruf (aus dem Projektordner, `pwsh` oder `powershell`): + +- Standard (Patch-Version): `powershell -ExecutionPolicy Bypass -File scripts\release.ps1` +- Größere Sprünge: `-Bump minor`, `-Bump major` oder `-Version x.y.z` +- Nur bauen, nichts veröffentlichen: `-NoPublish` (Ergebnis in `release-out\`) +- Das Skript bricht bei jedem Fehler ab und nimmt eine Versionsänderung vor dem Commit wieder zurück. +- Fehlt `GITEA_TOKEN`, dem Nutzer auf Deutsch erklären, wie er es anlegt (Gitea → Einstellungen → Anwendungen → Zugriffstoken, Berechtigung `write:repository`), und warten. Das Token nie ausgeben oder speichern. + +## Projektüberblick + +VitosUpdater ist ein Klon von UniGetUI für Windows 10/11: eine einheitliche Oberfläche zum Suchen, Installieren, Aktualisieren und Deinstallieren von Software über mehrere Paketmanager (WinGet, Scoop, Chocolatey, Pip, Npm, .NET Tool, PowerShell Gallery, Cargo, Vcpkg). Technik: Rust (Edition 2021) + Tauri 2, Frontend mit Svelte 5 + TypeScript + Tailwind CSS 4. Der Selbst-Updater nutzt `tauri-plugin-updater` mit `latest.json` auf dem eigenen Gitea (`https://gitea.davito.synology.me`, Repo `Davito/VitosUpdater`). + +## Ordnerstruktur + +``` +build.ps1 Komfortskript für Installer-Build +scripts\release.ps1 Vollständiger Release-Ablauf (siehe oben) +scripts\cdp.mjs Entwicklungshilfe: JavaScript in der laufenden WebView2 auswerten +src\ Frontend (Svelte) + i18n\de.json, t.ts ALLE sichtbaren Texte (nur Deutsch) und Formatierer + views\ Ansichten (Entdecken, Updates, Installiert, Bundles, Einstellungen) + components\ UI-Bausteine (Sidebar, VirtualTable, DetailsPanel, UpdateDialog, ...) + stores\ Zustand (Svelte-5-Runes) + lib\ api.ts (typisierte Tauri-Befehle), types.ts, Hilfsfunktionen +src-tauri\ Backend (Rust) + src\managers\ Trait PackageManager + ein Modul je Manager (winget\ ...) + src\updater\ Selbst-Update (mod.rs: Zustand, Scheduler, Befehle; policy.rs: reine Logik) + src\settings\, commands\, process.rs, logging.rs, models.rs, error.rs + tests\fixtures\ Echte Beispielausgaben der Paketmanager für Parser-Tests +``` + +Konfiguration und Zustand der App liegen in `%APPDATA%\VitosUpdater` (Logs unter `logs\`). + +## Bauen und Testen + +Voraussetzungen: Rust (MSVC), Node.js LTS, WebView2, VS Build Tools (C++), Tauri CLI (`cargo install tauri-cli --version "^2"`). + +- Entwickeln: `cargo tauri dev` (aus dem Projektordner) +- Rust prüfen: in `src-tauri` → `cargo check`, `cargo clippy --all-targets -- -D warnings`, `cargo test` +- Frontend prüfen: `npm run check` (svelte-check) und `npm run build` +- Das Projekt muss nach jeder Änderung ohne Fehler und Warnungen kompilieren. +- Parser immer mit echter Beispielausgabe testen (Fixtures in `src-tauri\tests\fixtures`). Keine CLI-Flags raten: im Zweifel lokal ausprobieren. + +## Sprache und Texte + +- Die Oberfläche ist ausschließlich Deutsch: UI-Texte, Schaltflächen, Menüs, Tooltips, Dialoge, Tray, Benachrichtigungen, Fehlermeldungen, Einstellungen, Installer, README. +- Korrektes Deutsch mit echten Umlauten und ß, korrekte Grammatik und Zeichensetzung, deutsche Formate (dd.MM.yyyy, 24 Stunden, Dezimalkomma, „1.234,5 MB“). +- Neutrale Ansprache ohne „du/Sie“ (z. B. „Updates suchen“, „Alle aktualisieren“); einheitliche Begriffe (Paket, Quelle, Aktualisierung, Deinstallieren, Warteschlange, Einstellungen). +- Alle Texte stehen zentral in `src/i18n/de.json` und werden über `t()`/`tp()` verwendet. Nie Texte in Komponenten hartcodieren. Ausnahme: Eigennamen (WinGet, Scoop, Chocolatey). +- Texte, die im Backend entstehen (Fehlermeldungen, Benachrichtigungen), sind ebenfalls Deutsch. +- Code, Bezeichner und Code-Kommentare sind Englisch. Commit-Nachrichten sind Deutsch (sie erscheinen als Release-Notizen im Update-Popup). + +## Geheimnisse + +- Niemals Geheimnisse einchecken oder ausgeben: Gitea-Token, Signierschlüssel (`updater.key`), Passwörter, `.env`-Dateien. +- Der private Signierschlüssel liegt außerhalb des Repos (`%USERPROFILE%\.vitosupdater\updater.key`); nur der öffentliche Schlüssel steht in `tauri.conf.json`. +- Das Gitea-Token kommt aus der Umgebungsvariable `GITEA_TOKEN` oder der git-ignorierten Datei `.env.local`. Git-Pushes laufen über den Windows Git Credential Manager. +- TLS-Prüfung nie abschalten (keine `dangerous_*`-Optionen im Updater). diff --git a/README.md b/README.md new file mode 100644 index 0000000..5041207 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# VitosUpdater + +VitosUpdater ist eine einheitliche Oberfläche zum Suchen, Installieren, Aktualisieren und Deinstallieren von Software über mehrere Paketmanager unter Windows 10/11. Die Oberfläche ist vollständig deutsch. + +**Stand:** Phase 1 (WinGet, Entdecken/Installiert/Updates) und der Selbst-Updater sind umgesetzt. Warteschlange, weitere Paketmanager, Bundles, Tray und Backup folgen in den nächsten Phasen. + +## Funktionen (aktueller Stand) + +- WinGet-Erkennung beim Start (PATH, danach `%LOCALAPPDATA%\Microsoft\WindowsApps`) mit Versionsanzeige; Hinweis und Ein-Klick-Installation des „App-Installers“, falls WinGet fehlt +- Optional: PowerShell-Modul `Microsoft.WinGet.Client` (strukturierte JSON-Daten); ohne Modul wird die lokalisierte Tabellenausgabe von `winget.exe` ausgewertet +- Ansichten: Pakete entdecken (Suche), Software-Updates (mit Zähler in der Seitenleiste), Installierte Pakete – mit Details-Panel und virtualisierter Tabelle (flüssig auch mit 5000+ Zeilen) +- Dunkles Design (Standard), helles Design, „System folgen“, Akzentfarbe wählbar +- Fensterposition und -größe werden gemerkt +- **Selbst-Update** von VitosUpdater (siehe unten) + +## Voraussetzungen + +| Komponente | Hinweis | +|---|---| +| Rust (rustup, MSVC-Toolchain) | `rustup default stable-x86_64-pc-windows-msvc` | +| Node.js LTS | inkl. npm | +| WebView2-Laufzeit | unter Windows 11 vorinstalliert | +| Visual Studio Build Tools | Workload „Desktopentwicklung mit C++“ | +| Tauri CLI | `cargo install tauri-cli --version "^2"` | +| WinGet | Teil des „App-Installers“ (Microsoft Store) | + +## Entwickeln und Bauen + +```powershell +npm install # einmalig +cargo tauri dev # Entwicklungsmodus mit Hot Reload + +npm run check # Frontend-Typprüfung (svelte-check) +cd src-tauri +cargo check +cargo clippy --all-targets -- -D warnings +cargo test # inkl. Parser-Tests mit echter WinGet-Ausgabe + +.\build.ps1 # Prüfungen + Installer (NSIS) + portable EXE +``` + +Der portable Build liegt unter `src-tauri\target\release\vitos-updater.exe`, der Installer unter `src-tauri\target\release\bundle\nsis\`. + +Konfiguration, Zustand und Logs liegen in `%APPDATA%\VitosUpdater` (Logs unter `logs\`). + +## Selbst-Update über Gitea + +VitosUpdater prüft nach dem Start und danach alle 30 Minuten (einstellbar unter *Einstellungen → Programm-Updates*, auch bei ausgeblendetem Fenster), ob eine neue Version vorliegt. Quelle: + +``` +https://gitea.davito.synology.me/Davito/VitosUpdater/releases/download/latest/latest.json +``` + +- Updates werden mit einem Minisign-Schlüsselpaar signiert und vor der Installation geprüft. Zusätzlich ist `requireSignedVersion` aktiv: Die Signatur ist an die Versionsnummer gebunden, ein Downgrade durch eine manipulierte `latest.json` ist nicht möglich. +- Die Versionen werden nach SemVer verglichen; es werden nie ältere Versionen angeboten. +- Bei einem neuen Update erscheint ein Popup mit Versionswechsel, Datum, Größe und den Release-Notizen. Möglich sind „Jetzt aktualisieren“, „Später erinnern“ (4 Stunden) und „Diese Version überspringen“. Eine übersprungene Version unter *Einstellungen* wieder freigeben. +- Ist das Fenster ausgeblendet oder minimiert, erscheint zuerst eine Windows-Benachrichtigung. Das Popup wird gezeigt, sobald das Fenster wieder geöffnet wird. Nach der Installation startet VitosUpdater neu und zeigt „Aktualisiert auf Version x.y.z“ mit den Änderungen. + +### Zertifikat (Synology / Gitea) + +Der Updater nutzt den Windows-Zertifikatsspeicher (Schannel) und prüft das TLS-Zertifikat immer. Die Prüfung wird **nicht** abgeschaltet. Der Server `gitea.davito.synology.me` verwendet derzeit ein öffentlich vertrauenswürdiges Let's-Encrypt-Zertifikat. + +Erscheint in der App die Meldung „… weil Windows dem Zertifikat nicht vertraut“ (z. B. bei einem selbst signierten Synology-Zertifikat), die Zertifizierungsstelle in Windows als vertrauenswürdig eintragen: + +1. DSM: *Systemsteuerung → Sicherheit → Zertifikat → Aktion → Zertifikat exportieren*; aus dem Archiv die Datei `syno-ca-cert.crt` (die CA, nicht den privaten Schlüssel) entnehmen. +2. In PowerShell (ohne Administratorrechte, gilt für den aktuellen Benutzer): + ```powershell + Import-Certificate -FilePath "$env:USERPROFILE\Downloads\syno-ca-cert.crt" -CertStoreLocation Cert:\CurrentUser\Root + ``` + Für alle Benutzer (Administrator): `-CertStoreLocation Cert:\LocalMachine\Root`. +3. VitosUpdater neu starten. + +Prüfen lässt sich die Verbindung mit `curl.exe -I https://gitea.davito.synology.me`. + +## Release veröffentlichen + +Alles erledigt `scripts\release.ps1` (Version erhöhen, bauen, signieren, `latest.json`, Commit, Tag, Push, Gitea-Release, rollendes Release `latest`, Prüfung): + +```powershell +powershell -ExecutionPolicy Bypass -File scripts\release.ps1 # Patch-Version +powershell -ExecutionPolicy Bypass -File scripts\release.ps1 -Bump minor +powershell -ExecutionPolicy Bypass -File scripts\release.ps1 -Version 1.2.0 +powershell -ExecutionPolicy Bypass -File scripts\release.ps1 -NoPublish # nur bauen, Ergebnis in release-out\ +``` + +Commit-Nachrichten auf Deutsch schreiben: Sie bilden die Release-Notizen im Update-Popup. + +### Einmalige Einrichtung + +1. **Zugriffstoken:** Gitea → *Einstellungen → Anwendungen → Zugriffstoken*, Berechtigung *Repository: Lesen und Schreiben* (`write:repository`). Danach entweder dauerhaft `setx GITEA_TOKEN ""` (neues Terminal öffnen) oder die Zeile `GITEA_TOKEN=` in die Datei `.env.local` im Projektordner schreiben. Beides ist von Git ausgeschlossen. +2. **Git-Push:** läuft über den Windows Git Credential Manager (beim ersten Push anmelden). +3. **Signierschlüssel:** liegt unter `%USERPROFILE%\.vitosupdater\updater.key` (außerhalb des Repos, nur für den Benutzer lesbar). **Unbedingt sichern** (z. B. Passwortmanager oder verschlüsseltes Backup): Ohne diesen Schlüssel können keine Updates für bereits installierte Versionen signiert werden. Neu erzeugen: `cargo tauri signer generate -w "%USERPROFILE%\.vitosupdater\updater.key"` – danach den öffentlichen Schlüssel in `src-tauri\tauri.conf.json` (`plugins.updater.pubkey`) eintragen; alle bereits installierten Versionen müssten dann manuell neu installiert werden. + +Optional kann der Schlüssel über `TAURI_SIGNING_PRIVATE_KEY` bzw. `TAURI_SIGNING_PRIVATE_KEY_PATH` (und `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`) bereitgestellt werden. + +## Bekannte Einschränkungen + +- Das PowerShell-Modul `Microsoft.WinGet.Client` wird beim Start erkannt und genutzt, wenn vorhanden. Diese Strecke ist mangels installiertem Modul auf dem Entwicklungsrechner noch nicht mit echten Daten getestet; bei jedem Fehler wird automatisch auf die Kommandozeile zurückgegriffen. +- Die Beschreibung in den Paketdetails übernimmt die Zeilenumbrüche von WinGet. +- Ein Klick auf die Windows-Benachrichtigung öffnet das Fenster nicht zuverlässig; das Popup erscheint, sobald das Fenster geöffnet oder fokussiert wird. diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..b32e392 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,47 @@ +<# +.SYNOPSIS + Baut VitosUpdater lokal (Installer + portable EXE) ohne Veröffentlichung. + +.DESCRIPTION + Führt Frontend-Prüfung und Rust-Prüfungen aus und baut danach mit `cargo tauri build`. + Ergebnis: src-tauri\target\release\bundle\nsis\*-setup.exe sowie src-tauri\target\release\vitos-updater.exe (portabel). + Für signierte Updater-Artefakte muss der Signierschlüssel gesetzt sein (siehe README, Abschnitt "Release"). + Für eine Veröffentlichung auf Gitea stattdessen scripts\release.ps1 verwenden. +#> +[CmdletBinding()] +param([switch]$SkipChecks) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +Set-Location $PSScriptRoot + +function Invoke-Native([string]$Description, [scriptblock]$Command) { + & $Command + if ($LASTEXITCODE -ne 0) { throw "$Description ist fehlgeschlagen (Exit-Code $LASTEXITCODE)." } +} + +$defaultKey = Join-Path $env:USERPROFILE '.vitosupdater\updater.key' +if (-not $env:TAURI_SIGNING_PRIVATE_KEY -and -not $env:TAURI_SIGNING_PRIVATE_KEY_PATH -and (Test-Path $defaultKey)) { + $env:TAURI_SIGNING_PRIVATE_KEY_PATH = $defaultKey +} +$env:CI = 'true' + +if (-not (Test-Path 'node_modules')) { Invoke-Native 'npm install' { npm install } } + +if (-not $SkipChecks) { + Write-Host '==> Prüfungen' -ForegroundColor Cyan + Invoke-Native 'svelte-check' { npm run check } + Push-Location 'src-tauri' + try { + Invoke-Native 'cargo clippy' { cargo clippy --all-targets -- -D warnings } + Invoke-Native 'cargo test' { cargo test } + } finally { Pop-Location } +} + +Write-Host '==> Bauen (cargo tauri build)' -ForegroundColor Cyan +Invoke-Native 'cargo tauri build' { cargo tauri build } + +Write-Host '' +Write-Host 'Fertig. Ergebnisse:' -ForegroundColor Green +Get-ChildItem 'src-tauri\target\release\bundle\nsis' -Filter '*-setup.exe' | ForEach-Object { Write-Host " Installer: $($_.FullName)" } +Write-Host " Portabel: $(Resolve-Path 'src-tauri\target\release\vitos-updater.exe')" diff --git a/index.html b/index.html new file mode 100644 index 0000000..55214ad --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + VitosUpdater + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..141bd93 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2159 @@ +{ + "name": "vitos-updater", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vitos-updater", + "version": "0.1.0", + "dependencies": { + "@fontsource-variable/inter": "^5.3.0", + "@lucide/svelte": "^1.65.0", + "@tauri-apps/api": "^2.12.4", + "@tauri-apps/plugin-autostart": "^2.7.0", + "@tauri-apps/plugin-notification": "^2.8.0", + "@tauri-apps/plugin-opener": "^2.7.2", + "@tauri-apps/plugin-process": "^2.4.0", + "@tauri-apps/plugin-updater": "^2.14.1", + "dompurify": "^3.4.17", + "marked": "^18.1.2" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.3.1", + "@tailwindcss/vite": "^4.3.3", + "@tauri-apps/cli": "^2.12.4", + "svelte": "^5.57.3", + "svelte-check": "^4.7.7", + "tailwindcss": "^4.3.3", + "typescript": "^6.0.3", + "vite": "^8.3.5" + } + }, + "node_modules/@fontsource-variable/inter": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz", + "integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lucide/svelte": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.65.0.tgz", + "integrity": "sha512-VfM4Ej7JFYz33WXm/JdqtOsOqRtfca5H9eFcyfcbFthWvbOnvvCerY/YkEk0ZiAvOvqo5hpK3u+opC0zY7Caxw==", + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz", + "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz", + "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz", + "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz", + "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz", + "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz", + "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz", + "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz", + "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz", + "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz", + "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz", + "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz", + "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz", + "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz", + "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz", + "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.13.tgz", + "integrity": "sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/load-config": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.3.tgz", + "integrity": "sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.3.1.tgz", + "integrity": "sha512-ZPsLN8B1e/En+Ak5s4V7srFDT532oS0qieLsQwu63NGKsS+iAjoO2Js1BochlHlglcU+Pt7WAO3C5Ee+4f6gVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "magic-string": "^1.0.0", + "obug": "^2.1.0", + "vitefu": "^1.1.2" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.46.4", + "vite": "^8.0.0-beta.7 || ^8.0.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/node/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.12.4.tgz", + "integrity": "sha512-pVmfis+4zT8H0Qhp3xPZx9Ju7kpzvA6rr/PSJPyuwfvucTQ8sVt61NYU9KMkK703UFlYadFmjKA4HhybOtN0gg==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.12.4.tgz", + "integrity": "sha512-JI9bHfudSRJikGOrjbXEm6RjzzvmKuLc+JixB1NE8hNzQkNlCCIC3pTES3uypkR9lxHrsbxzcnSwhI2IxBQphw==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.12.4", + "@tauri-apps/cli-darwin-x64": "2.12.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.12.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.12.4", + "@tauri-apps/cli-linux-arm64-musl": "2.12.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.12.4", + "@tauri-apps/cli-linux-x64-gnu": "2.12.4", + "@tauri-apps/cli-linux-x64-musl": "2.12.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.12.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.12.4", + "@tauri-apps/cli-win32-x64-msvc": "2.12.4" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.12.4.tgz", + "integrity": "sha512-OxqKSckIkoJ6vX2AjifIJpTasFC0Hbdoo7gEiCmAjauABO3U8m2MRsLWe+QTiBwDx1mIrvgA+rvI7tvDnbnHVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.12.4.tgz", + "integrity": "sha512-fz76/77wMMJOaaUN6n3CybRG0P/MTEjjqYk81mGPyhi9m6Ue5T+LWBnJP/AkPooNatBPI2vtulGqomyMJ44lCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.12.4.tgz", + "integrity": "sha512-K0w8Bof6Lt8QKLAYhRy0yy36ZH5iA/35VuSOvnxyjvCUZ+A3JKsUTlALviH/sVgAlisaPzzmeJSH8XOn4Q2HBA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.12.4.tgz", + "integrity": "sha512-j5rOnjmRUwWrm1oosf0zBYl4D+fqydgUROFCk0hvKZxvbTT8SOERes0QB7rBV/XWNy+kM6pCKzb3oSm2sRglzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.12.4.tgz", + "integrity": "sha512-aOue3Fy6Jb7+oWcfAVUsVHfeTybSCt2CZoU06aO4jv4d2Ks/KnaTPwMAK3CLEduGOvBLY1lbBBrIyomqLLH5Sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.12.4.tgz", + "integrity": "sha512-Fx6GShY/RvST+t6LMlTHAwd66+1tEaAAGrnU7cysVfaiRP1BKng5Vl6rVmO6rSLOA1Sp1dRzNYM4f0OWQGb7qA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.12.4.tgz", + "integrity": "sha512-tNzZSa79Z1nRpHxEpQ38lsAFpKlFlYvoXzjTV+lRRn/zSf3wrdUC3wnLcq2XEadchPu4ttK0zivhYzr+XGD19Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.12.4.tgz", + "integrity": "sha512-jmZ1pr0xEqUhuEusWtt9Yt+GF6lCnGHUmIL+KIknpbjK/XQ2qkhDBhaxK/JMHM2A8dAacVXCxXJk2EXpwhQUfA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.12.4.tgz", + "integrity": "sha512-fGo8pgEHZxdSYgZ/J98m1usJ7NHKmKbq7k6uT0x1rpOq6Bg2YVzzpaAblWk710f5LHYBJIL5QIFS5IrD+4gmTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.12.4.tgz", + "integrity": "sha512-ezl8sQIcjYsye3TUnTH5VcWmWzccZASlQm7hlZt0dYyj1XD0qpVW47u2xYVrIKui0F8uQv/7QGZixdWiClBsbg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.12.4.tgz", + "integrity": "sha512-DJhDeCqjLDNMWYFy7ZS8cfeAHX2RK0RbQMLiA3bMVVkXRDJ4NBYiHriGqsUhsr9iG41aTb+dJGkFPmPw1gWwVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-autostart": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-autostart/-/plugin-autostart-2.7.0.tgz", + "integrity": "sha512-4uRq+rqKSAh6MWAxgk6FnUPigc8VMG8eDVYTLk3A7OfKs9sLVwfeM8kbUvzjpabRQvyl4zd9tfzXBTrrtuprsA==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@tauri-apps/plugin-notification": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.8.0.tgz", + "integrity": "sha512-BB9Kk2emiNHyMjIQr3k/ye8RKD2RJeOPCjM5OZLF+4/pO6Kfjl9RtkX4hVCe1vqgbAx5HjHXjf0O8rtdW2F2dw==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.7.2.tgz", + "integrity": "sha512-LyybP1VSzTMQiaFYcsbAse47KpivlyXdLZMQzGLEnkgQacGESnpjAfFoQ6h5sqWgAdZDH9szYjPzHMpv0Sz+TQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@tauri-apps/plugin-process": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.4.0.tgz", + "integrity": "sha512-GddrmsSLAYrLa/yTXlXK/YRDzUgYeeOnUXTbiyvJWkmI424RmDPD3FVLeqcon7rv6O6ZfhBilcrHWnSo7o6hIQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@tauri-apps/plugin-updater": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.14.1.tgz", + "integrity": "sha512-Hr7nlBm1wT6xbwzjZ/S36YULazu8MNlj5vvOwMl47fXI7HTa5Bjl1PbDEwVHaZTAKzE1VZsUy35UqnbrQxmYQw==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.9.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.4.tgz", + "integrity": "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==", + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz", + "integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.4.0.tgz", + "integrity": "sha512-CQPcneEHw/czj32uwQC52nYnax8H4SXt25FCM+LIwGZ49cMIxpOj3bP8wmzcI+Z0HIy5KkClaPhRLfCySzwHPA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.5.tgz", + "integrity": "sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/marked": { + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.1.2.tgz", + "integrity": "sha512-DfOnvLAQ6WYeWxWO1u4RyFOeqyHup6dks5PPqnoFiooU7ipRvidO/2j8plhAHIiZg574E9ZlzSjUiPMLAzFkgg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rolldown": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz", + "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.156.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.16", + "@rolldown/binding-android-arm64": "1.2.16", + "@rolldown/binding-darwin-arm64": "1.2.16", + "@rolldown/binding-darwin-x64": "1.2.16", + "@rolldown/binding-freebsd-x64": "1.2.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", + "@rolldown/binding-linux-arm64-gnu": "1.2.16", + "@rolldown/binding-linux-arm64-musl": "1.2.16", + "@rolldown/binding-linux-ppc64-gnu": "1.2.16", + "@rolldown/binding-linux-s390x-gnu": "1.2.16", + "@rolldown/binding-linux-x64-gnu": "1.2.16", + "@rolldown/binding-linux-x64-musl": "1.2.16", + "@rolldown/binding-openharmony-arm64": "1.2.16", + "@rolldown/binding-win32-arm64-msvc": "1.2.16", + "@rolldown/binding-win32-x64-msvc": "1.2.16" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.57.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.57.3.tgz", + "integrity": "sha512-5mAftM3SoDV9X0zdF386F9p2pv8iKOwi/KfBS1XnBAkVCKFzVOLHUoa8iGWAt4buoicLmyWVRpuuD5wplXOQsg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "@jridgewell/sourcemap-codec": "^1.6.0", + "@sveltejs/acorn-typescript": "^1.0.13", + "@types/estree": "^1.0.9", + "acorn": "^8.18.0", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.9.2", + "esm-env": "^1.2.1", + "esrap": "^2.3.6", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.7.tgz", + "integrity": "sha512-Ge2IMdeTpbJuzNFGRiiMg0RWgbScysKQu7xwcyFJ22+DU9F5nPX51+Q3gHmGqRxYQMx2knreVWt5oCtfe0vf2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "^0.2.3", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.0.0 || ^6.0.0" + } + }, + "node_modules/svelte/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.5.tgz", + "integrity": "sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..bb1ce6b --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "vitos-updater", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json", + "tauri": "tauri" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.3.1", + "@tailwindcss/vite": "^4.3.3", + "@tauri-apps/cli": "^2.12.4", + "svelte": "^5.57.3", + "svelte-check": "^4.7.7", + "tailwindcss": "^4.3.3", + "typescript": "^6.0.3", + "vite": "^8.3.5" + }, + "dependencies": { + "@fontsource-variable/inter": "^5.3.0", + "@lucide/svelte": "^1.65.0", + "@tauri-apps/api": "^2.12.4", + "@tauri-apps/plugin-autostart": "^2.7.0", + "@tauri-apps/plugin-notification": "^2.8.0", + "@tauri-apps/plugin-opener": "^2.7.2", + "@tauri-apps/plugin-process": "^2.4.0", + "@tauri-apps/plugin-updater": "^2.14.1", + "dompurify": "^3.4.17", + "marked": "^18.1.2" + } +} diff --git a/scripts/cdp.mjs b/scripts/cdp.mjs new file mode 100644 index 0000000..fb15f9b --- /dev/null +++ b/scripts/cdp.mjs @@ -0,0 +1,35 @@ +// Dev helper: evaluates JavaScript in the running WebView2 page via the DevTools protocol. +// Usage: node scripts/cdp.mjs "" [--shot out.png] +import { writeFileSync } from "node:fs"; + +const args = process.argv.slice(2); +const shotIdx = args.indexOf("--shot"); +const shot = shotIdx >= 0 ? args.splice(shotIdx, 2)[1] : null; +const expression = args[0] ?? "document.title"; + +const targets = await (await fetch("http://127.0.0.1:9222/json")).json(); +const page = targets.find((t) => t.type === "page"); +if (!page) throw new Error("Keine Seite gefunden"); + +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; +const pending = new Map(); +ws.onmessage = (event) => { + const msg = JSON.parse(event.data); + if (msg.id && pending.has(msg.id)) pending.get(msg.id)(msg); +}; +const send = (method, params = {}) => + new Promise((resolve) => { + const myId = ++id; + pending.set(myId, resolve); + ws.send(JSON.stringify({ id: myId, method, params })); + }); +await new Promise((resolve) => (ws.onopen = resolve)); + +const res = await send("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true }); +console.log(JSON.stringify(res.result?.result?.value ?? res.result, null, 2)); +if (shot) { + const img = await send("Page.captureScreenshot", { format: "png" }); + writeFileSync(shot, Buffer.from(img.result.data, "base64")); +} +ws.close(); diff --git a/scripts/release.ps1 b/scripts/release.ps1 new file mode 100644 index 0000000..f43d55b --- /dev/null +++ b/scripts/release.ps1 @@ -0,0 +1,403 @@ +<# +.SYNOPSIS + Baut, signiert und veröffentlicht eine neue VitosUpdater-Version auf Gitea. + +.DESCRIPTION + 1. Prüft Git-Zustand, cargo check, cargo clippy, cargo test und svelte-check + 2. Erhöht die Version in tauri.conf.json, Cargo.toml und package.json + 3. Baut den NSIS-Installer samt signiertem Updater-Artefakt (.sig) + 4. Erzeugt latest.json + 5. Committet die Version, setzt den Tag vX.Y.Z und pusht beides + 6. Erstellt das Gitea-Release vX.Y.Z und lädt Installer, .sig und latest.json hoch + 7. Aktualisiert das rollende Release "latest" + 8. Prüft die öffentliche latest.json und die Installer-URL + + Voraussetzungen: Umgebungsvariable GITEA_TOKEN (oder Datei .env.local mit GITEA_TOKEN=...), + Signierschlüssel unter %USERPROFILE%\.vitosupdater\updater.key (oder TAURI_SIGNING_PRIVATE_KEY[_PATH]). + +.PARAMETER Bump + Art der Versionserhöhung: patch (Standard), minor oder major. + +.PARAMETER Version + Explizite Version x.y.z (überschreibt -Bump). + +.PARAMETER NoPublish + Nur bauen und latest.json erzeugen. Es wird nichts committet, getaggt, gepusht oder hochgeladen; + die Versionsänderung wird danach zurückgenommen. Die Ergebnisse liegen in release-out\. + +.PARAMETER SkipChecks + Überspringt cargo check/clippy/test und svelte-check (nur für schnelle Wiederholungen). +#> +[CmdletBinding()] +param( + [ValidateSet('patch', 'minor', 'major')] + [string]$Bump = 'patch', + [string]$Version, + [switch]$NoPublish, + [switch]$SkipChecks +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +$OutputEncoding = [Text.Encoding]::UTF8 +try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { } + +$GiteaBase = 'https://gitea.davito.synology.me' +$Owner = 'Davito' +$Repo = 'VitosUpdater' +$Branch = 'main' +$Root = Split-Path -Parent $PSScriptRoot +$OutDir = Join-Path $Root 'release-out' +$Utf8NoBom = New-Object Text.UTF8Encoding($false) +$VersionFiles = @('src-tauri/tauri.conf.json', 'src-tauri/Cargo.toml', 'src-tauri/Cargo.lock', 'package.json', 'package-lock.json') +$script:Token = $null +$script:Committed = $false +$script:Bumped = $false + +function Write-Step([string]$Text) { Write-Host ''; Write-Host "==> $Text" -ForegroundColor Cyan } +function Write-Ok([string]$Text) { Write-Host " [OK] $Text" -ForegroundColor Green } +function Write-Info([string]$Text) { Write-Host " $Text" -ForegroundColor Gray } + +function Invoke-Native([string]$Description, [scriptblock]$Command) { + & $Command + if ($LASTEXITCODE -ne 0) { throw "$Description ist fehlgeschlagen (Exit-Code $LASTEXITCODE)." } +} + +function Get-GitOutput([string[]]$GitArgs) { + $result = & git @GitArgs + if ($LASTEXITCODE -ne 0) { throw "git $($GitArgs -join ' ') ist fehlgeschlagen (Exit-Code $LASTEXITCODE)." } + return $result +} + +function Read-Token { + if ($env:GITEA_TOKEN) { return $env:GITEA_TOKEN.Trim() } + $file = Join-Path $Root '.env.local' + if (Test-Path $file) { + foreach ($line in Get-Content $file -Encoding UTF8) { + if ($line -match '^\s*GITEA_TOKEN\s*=\s*(.+?)\s*$') { return $Matches[1].Trim('"', "'") } + } + } + return $null +} + +function Show-TokenHelp { + Write-Host '' + Write-Host 'Es wurde kein Gitea-Zugriffstoken gefunden.' -ForegroundColor Yellow + Write-Host ' 1. In Gitea anmelden: Einstellungen -> Anwendungen -> Zugriffstoken' + Write-Host ' 2. Neues Token erzeugen, Berechtigung: Repository = Lesen und Schreiben (write:repository)' + Write-Host ' 3. Token hinterlegen, entweder' + Write-Host ' dauerhaft: setx GITEA_TOKEN "" (danach neues Terminal öffnen)' + Write-Host " oder lokal: Datei .env.local im Projektordner mit der Zeile GITEA_TOKEN=" + Write-Host ' (.env.local ist in .gitignore eingetragen und wird nie eingecheckt.)' +} + +function Get-CurrentVersion { + $conf = Get-Content (Join-Path $Root 'src-tauri/tauri.conf.json') -Raw -Encoding UTF8 | ConvertFrom-Json + return [string]$conf.version +} + +function Get-NextVersion([string]$Current) { + if ($Version) { + if ($Version -notmatch '^\d+\.\d+\.\d+$') { throw "Ungültige Version '$Version'. Erwartet wird x.y.z." } + return $Version + } + if ($Current -notmatch '^(\d+)\.(\d+)\.(\d+)$') { throw "Aktuelle Version '$Current' ist keine einfache SemVer-Version." } + $major = [int]$Matches[1]; $minor = [int]$Matches[2]; $patch = [int]$Matches[3] + switch ($Bump) { + 'major' { return '{0}.0.0' -f ($major + 1) } + 'minor' { return '{0}.{1}.0' -f $major, ($minor + 1) } + default { return '{0}.{1}.{2}' -f $major, $minor, ($patch + 1) } + } +} + +function Set-FirstMatch([string]$Path, [string]$Pattern, [string]$Replacement) { + $full = Join-Path $Root $Path + $text = [IO.File]::ReadAllText($full, $Utf8NoBom) + $regex = New-Object Text.RegularExpressions.Regex($Pattern, [Text.RegularExpressions.RegexOptions]::Multiline) + if (-not $regex.IsMatch($text)) { throw "Versionszeile in $Path nicht gefunden." } + [IO.File]::WriteAllText($full, $regex.Replace($text, $Replacement, 1), $Utf8NoBom) +} + +function Set-ProjectVersion([string]$New) { + Set-FirstMatch 'src-tauri/tauri.conf.json' '^( "version": ")[^"]+(")' ('${1}' + $New + '${2}') + Set-FirstMatch 'src-tauri/Cargo.toml' '^(version = ")[^"]+(")' ('${1}' + $New + '${2}') + Invoke-Native 'npm version' { npm version $New --no-git-tag-version --allow-same-version | Out-Null } +} + +function Undo-VersionChange { + if ($script:Bumped -and -not $script:Committed) { + Write-Info 'Versionsänderung wird zurückgenommen ...' + & git checkout -- @VersionFiles 2>$null | Out-Null + $script:Bumped = $false + } +} + +function Get-ReleaseNotes([string]$NewVersion) { + $lastTag = $null + $found = & git describe --tags --abbrev=0 --match 'v*' 2>$null + if ($LASTEXITCODE -eq 0 -and $found) { $lastTag = [string]$found } + $range = 'HEAD' + if ($lastTag) { $range = "$lastTag..HEAD" } + $lines = & git -c core.quotepath=off log $range --no-merges --pretty=format:'- %s (%h)' --grep='^Release v' --invert-grep + if ($LASTEXITCODE -ne 0) { throw 'git log ist fehlgeschlagen.' } + $text = ($lines | Where-Object { $_ }) -join "`n" + if (-not $text) { $text = '- Interne Verbesserungen und Fehlerbehebungen' } + return $text +} + +# ---------- Gitea-API ---------- +function Invoke-Gitea([string]$Method, [string]$Path, $Body = $null) { + $uri = "$GiteaBase/api/v1/repos/$Owner/$Repo$Path" + $headers = @{ Authorization = "token $script:Token"; Accept = 'application/json' } + if ($null -ne $Body) { + $bytes = [Text.Encoding]::UTF8.GetBytes(($Body | ConvertTo-Json -Depth 6)) + return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -ContentType 'application/json; charset=utf-8' -Body $bytes + } + return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers +} + +function Get-ReleaseByTag([string]$Tag) { + try { + return Invoke-Gitea 'GET' "/releases/tags/$Tag" + } catch { + $status = $null + if ($_.Exception.PSObject.Properties.Name -contains 'Response' -and $_.Exception.Response) { $status = [int]$_.Exception.Response.StatusCode } + if ($status -eq 404) { return $null } + throw + } +} + +function Send-Asset([int]$ReleaseId, [string]$File, [string]$Name) { + $url = "$GiteaBase/api/v1/repos/$Owner/$Repo/releases/$ReleaseId/assets?name=" + [uri]::EscapeDataString($Name) + # Das Token wird über stdin an curl übergeben, damit es nicht in der Prozessliste erscheint. + $config = 'header = "Authorization: token ' + $script:Token + '"' + $response = $config | & curl.exe --silent --show-error --fail --config - -X POST -F "attachment=@$File" $url + if ($LASTEXITCODE -ne 0) { throw "Upload von '$Name' ist fehlgeschlagen (curl-Exit-Code $LASTEXITCODE)." } + Write-Ok "Hochgeladen: $Name ($([math]::Round((Get-Item $File).Length / 1MB, 1)) MB)" +} + +function Clear-ReleaseAssets([int]$ReleaseId) { + $release = Invoke-Gitea 'GET' "/releases/$ReleaseId" + foreach ($asset in @($release.assets)) { + Invoke-Gitea 'DELETE' "/releases/$ReleaseId/assets/$($asset.id)" | Out-Null + Write-Info "Altes Asset entfernt: $($asset.name)" + } +} + +function Get-HttpStatus([string]$Url) { + $code = & curl.exe --silent --location --head --output NUL --write-out '%{http_code}' $Url + if ($code -eq '405' -or $code -eq '000') { + $code = & curl.exe --silent --location --range 0-0 --output NUL --write-out '%{http_code}' $Url + } + return [string]$code +} + +# ---------- Ablauf ---------- +Set-Location $Root +$started = Get-Date +try { + Write-Host 'VitosUpdater – Release-Skript' -ForegroundColor White + + # --- 1. Voraussetzungen und Prüfungen --- + Write-Step '1/8 Voraussetzungen und Qualitätsprüfungen' + if (-not $NoPublish) { + $script:Token = Read-Token + if (-not $script:Token) { Show-TokenHelp; throw 'GITEA_TOKEN fehlt.' } + Write-Ok 'Gitea-Token gefunden (wird nicht angezeigt)' + } + + $keyPath = Join-Path $env:USERPROFILE '.vitosupdater\updater.key' + if (-not $env:TAURI_SIGNING_PRIVATE_KEY -and -not $env:TAURI_SIGNING_PRIVATE_KEY_PATH) { + if (-not (Test-Path $keyPath)) { + throw "Signierschlüssel nicht gefunden: $keyPath. Erzeugen mit: cargo tauri signer generate -w `"$keyPath`" (öffentlichen Schlüssel danach in tauri.conf.json eintragen)." + } + $env:TAURI_SIGNING_PRIVATE_KEY_PATH = $keyPath + } + Write-Ok 'Signierschlüssel gefunden' + + $dirty = & git status --porcelain + if ($LASTEXITCODE -ne 0) { throw 'git status ist fehlgeschlagen. Ist dies ein Git-Repository?' } + if ($dirty) { + Write-Host ($dirty -join "`n") -ForegroundColor Yellow + throw 'Das Arbeitsverzeichnis ist nicht sauber. Bitte zuerst committen oder verwerfen.' + } + $branch = (Get-GitOutput @('rev-parse', '--abbrev-ref', 'HEAD') | Select-Object -First 1).Trim() + if ($branch -ne $Branch) { throw "Es muss der Branch '$Branch' ausgecheckt sein (aktuell: '$branch')." } + if (-not $NoPublish) { + Invoke-Native 'git fetch' { git fetch origin --tags --force --quiet } + $remoteMain = & git rev-parse --verify --quiet "refs/remotes/origin/$Branch" + if ($LASTEXITCODE -eq 0 -and $remoteMain) { + $behind = [int](Get-GitOutput @('rev-list', '--count', "HEAD..origin/$Branch") | Select-Object -First 1) + if ($behind -gt 0) { throw "Der lokale Branch liegt $behind Commit(s) hinter origin/$Branch. Bitte zuerst git pull ausführen." } + } + } + Write-Ok 'Git-Zustand ist sauber' + + if ($SkipChecks) { + Write-Info 'Prüfungen werden übersprungen (-SkipChecks).' + } else { + Push-Location (Join-Path $Root 'src-tauri') + try { + Invoke-Native 'cargo check' { cargo check --all-targets } + Write-Ok 'cargo check' + Invoke-Native 'cargo clippy' { cargo clippy --all-targets -- -D warnings } + Write-Ok 'cargo clippy (ohne Warnungen)' + Invoke-Native 'cargo test' { cargo test } + Write-Ok 'cargo test' + } finally { Pop-Location } + Invoke-Native 'svelte-check' { npm run check } + Write-Ok 'svelte-check' + } + + # --- 2. Version erhöhen --- + Write-Step '2/8 Version erhöhen' + $current = Get-CurrentVersion + $new = Get-NextVersion $current + if (-not $NoPublish) { + $existingTag = & git tag --list "v$new" + if ($existingTag) { throw "Der Tag v$new existiert bereits. Bitte eine höhere Version wählen." } + if ([version]$new -le [version]$current) { throw "Die neue Version $new ist nicht größer als die aktuelle Version $current." } + } + $script:Bumped = $true + Set-ProjectVersion $new + Write-Ok "Version $current -> $new (tauri.conf.json, Cargo.toml, package.json)" + + # --- 3. Bauen --- + Write-Step '3/8 Installer bauen und signieren (cargo tauri build)' + $env:CI = 'true' + Invoke-Native 'cargo tauri build' { cargo tauri build } + $nsisDir = Join-Path $Root 'src-tauri/target/release/bundle/nsis' + $installer = Get-ChildItem $nsisDir -Filter "*_${new}_*-setup.exe" | Select-Object -First 1 + if (-not $installer) { throw "Installer für Version $new wurde nicht gefunden in $nsisDir." } + $sigFile = "$($installer.FullName).sig" + if (-not (Test-Path $sigFile)) { throw "Signaturdatei fehlt: $sigFile. Wurde createUpdaterArtifacts aktiviert und der Schlüssel gefunden?" } + Write-Ok "Installer: $($installer.Name) ($([math]::Round($installer.Length / 1MB, 1)) MB)" + Write-Ok 'Signatur (.sig) erzeugt' + + # --- 4. latest.json --- + Write-Step '4/8 latest.json erzeugen' + $notes = Get-ReleaseNotes $new + $assetUrl = "$GiteaBase/$Owner/$Repo/releases/download/v$new/$($installer.Name)" + $manifest = [ordered]@{ + version = $new + notes = $notes + pub_date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'") + platforms = [ordered]@{ + 'windows-x86_64' = [ordered]@{ + signature = (Get-Content $sigFile -Raw -Encoding UTF8).Trim() + url = $assetUrl + size = [int64]$installer.Length + } + } + } + if (Test-Path $OutDir) { Remove-Item $OutDir -Recurse -Force } + New-Item -ItemType Directory -Path $OutDir | Out-Null + $latestFile = Join-Path $OutDir 'latest.json' + [IO.File]::WriteAllText($latestFile, ($manifest | ConvertTo-Json -Depth 6), $Utf8NoBom) + Copy-Item $installer.FullName $OutDir + Copy-Item $sigFile $OutDir + $outInstaller = Join-Path $OutDir $installer.Name + $outSig = Join-Path $OutDir "$($installer.Name).sig" + Write-Ok "latest.json -> $latestFile" + + if ($NoPublish) { + Undo-VersionChange + Write-Step 'Fertig (-NoPublish)' + Write-Ok "Version $new gebaut. Dateien liegen in: $OutDir" + return + } + + # --- 5. Commit, Tag, Push --- + Write-Step "5/8 Commit, Tag v$new und Push" + Invoke-Native 'git add' { git add -- @VersionFiles } + Invoke-Native 'git commit' { git commit --quiet -m "Release v$new" } + $script:Committed = $true + Invoke-Native 'git tag' { git tag -a "v$new" -m "Release v$new" } + Invoke-Native 'git push (Branch)' { git push origin $Branch } + Invoke-Native 'git push (Tag)' { git push origin "v$new" } + Write-Ok "Commit und Tag v$new gepusht" + + # --- 6. Gitea-Release --- + Write-Step "6/8 Gitea-Release v$new erstellen" + $release = Get-ReleaseByTag "v$new" + if ($release) { + Write-Info 'Release existiert bereits, Assets werden ersetzt.' + Clear-ReleaseAssets $release.id + } else { + $release = Invoke-Gitea 'POST' '/releases' @{ + tag_name = "v$new" + target_commitish = $Branch + name = "VitosUpdater $new" + body = $notes + draft = $false + prerelease = $false + } + } + Write-Ok "Release angelegt: $($release.html_url)" + Send-Asset $release.id $outInstaller $installer.Name + Send-Asset $release.id $outSig "$($installer.Name).sig" + Send-Asset $release.id $latestFile 'latest.json' + + # --- 7. Rollendes Release "latest" --- + Write-Step '7/8 Rollendes Release "latest" aktualisieren' + Invoke-Native 'git tag latest' { git tag -f latest HEAD | Out-Null } + Invoke-Native 'git push latest' { git push --force origin refs/tags/latest } + $rolling = Get-ReleaseByTag 'latest' + $rollingBody = "Aktuelle Version: **$new**`n`n$notes" + if ($rolling) { + Invoke-Gitea 'PATCH' "/releases/$($rolling.id)" @{ name = "Aktuellste Version ($new)"; body = $rollingBody } | Out-Null + Clear-ReleaseAssets $rolling.id + } else { + $rolling = Invoke-Gitea 'POST' '/releases' @{ + tag_name = 'latest' + target_commitish = $Branch + name = "Aktuellste Version ($new)" + body = $rollingBody + draft = $false + prerelease = $false + } + } + Send-Asset $rolling.id $outInstaller $installer.Name + Send-Asset $rolling.id $outSig "$($installer.Name).sig" + Send-Asset $rolling.id $latestFile 'latest.json' + Write-Ok 'Rollendes Release aktualisiert' + + # --- 8. Prüfung --- + Write-Step '8/8 Veröffentlichung prüfen' + $latestUrl = "$GiteaBase/$Owner/$Repo/releases/download/latest/latest.json" + $raw = Invoke-WebRequest -Uri $latestUrl -UseBasicParsing + $content = $raw.Content + if ($content -is [byte[]]) { $content = [Text.Encoding]::UTF8.GetString($content) } + $remote = $content | ConvertFrom-Json + if ($remote.version -ne $new) { throw "Öffentliche latest.json enthält Version $($remote.version), erwartet wurde $new." } + Write-Ok "latest.json öffentlich erreichbar, Version $($remote.version)" + $platform = $remote.platforms.'windows-x86_64' + $status = Get-HttpStatus $platform.url + if ($status -ne '200' -and $status -ne '206') { throw "Installer-URL antwortet mit HTTP ${status}: $($platform.url)" } + Write-Ok "Installer-URL antwortet mit HTTP $status" + $rollingStatus = Get-HttpStatus "$GiteaBase/$Owner/$Repo/releases/download/latest/$($installer.Name)" + if ($rollingStatus -ne '200' -and $rollingStatus -ne '206') { throw "Installer im Release 'latest' antwortet mit HTTP $rollingStatus." } + Write-Ok "Installer im Release 'latest' antwortet mit HTTP $rollingStatus" + + $elapsed = [math]::Round(((Get-Date) - $started).TotalMinutes, 1) + Write-Host '' + Write-Host '==================== Zusammenfassung ====================' -ForegroundColor Green + Write-Host " Version: $new (vorher $current)" + Write-Host " Release: $($release.html_url)" + Write-Host " Rollendes Release: $GiteaBase/$Owner/$Repo/releases/tag/latest" + Write-Host " Update-Endpunkt: $latestUrl" + Write-Host " Installer: $($platform.url)" + Write-Host " Dauer: $elapsed Minuten" + Write-Host '==========================================================' -ForegroundColor Green +} +catch { + Write-Host '' + Write-Host "FEHLER: $($_.Exception.Message)" -ForegroundColor Red + if ($script:Committed) { + Write-Host 'Der Versions-Commit wurde bereits lokal erstellt. Nach der Fehlerbehebung zurücknehmen mit: git tag -d v; git reset --hard HEAD~1 (bereits gepushte Teile ggf. auf Gitea prüfen), danach das Skript erneut starten.' -ForegroundColor Yellow + } else { + Undo-VersionChange + } + exit 1 +} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 0000000..baff5c7 --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,5409 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[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 = "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.9", +] + +[[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.9", +] + +[[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.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 = "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.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "627b7cfb0578f0eafce087a90fcd40add1231900b69e4d4de2d456260a4f5d81" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34c6783f42c21c952ad7112b354f0ba2f10760e9decea5b1b60523d6e270faaf" +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 = "cc" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" +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 = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core", +] + +[[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 = "combine" +version = "4.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" +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 = "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 0.5.0", + "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 = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" +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 = "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.9", +] + +[[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.9", +] + +[[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.9", +] + +[[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 = "defmt" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "defmt-parser" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0" +dependencies = [ + "thiserror 2.0.21", +] + +[[package]] +name = "deranged" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" +dependencies = [ + "serde_core", +] + +[[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", +] + +[[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.9", +] + +[[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 = "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 = "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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "erased-serde" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e59b668157f32d3459d4bd634861a06ffa2503a461ca5f0916b9f0252605e66" +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 = "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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[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.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[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 0.3.1", +] + +[[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.9", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[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.9", +] + +[[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.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", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core", + "wasm-bindgen", +] + +[[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.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[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 = "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-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "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.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75e94d66081e4d8dcd098bd8dc5254800fea14a26389b298410158d834de167" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" + +[[package]] +name = "icu_properties" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" + +[[package]] +name = "icu_provider" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" +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 = "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-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.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a" +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 = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + +[[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.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[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 = "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 = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[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", + "serde", + "thiserror 2.0.21", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[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.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[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", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95" +dependencies = [ + "dunce", + "is-wsl", + "libc", +] + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "openssl-probe" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e036b411754132fd6b74e94b74e9a13c5cf606fa93d1885c35860d3060820b1" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[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 = "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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327a4cb2ad49dc48adfdc9da89e22c64d488b508c67a044d71ba729e4521a285" +dependencies = [ + "base64 0.23.1", + "indexmap 2.14.2", + "quick-xml", + "serde", + "time", +] + +[[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 = "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.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[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.9", +] + +[[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-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[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 = "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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" +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 = "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.9", +] + +[[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.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +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.9", +] + +[[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.3", + "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.9", +] + +[[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 = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[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 = "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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[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 = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_vcruntime" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7589a1859b09232522bb9edf076ca7fcf14ded96685d3fa629db73023ffbb7" + +[[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 = "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 = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +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.9", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5777b06fe6435126ae8d816eba269e050e1e10d302cf5461e9c49eccabc3548" +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", + "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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb0244332cfb9cee7e2a568bf5da29a36ed8fdbd717f6f47a66b6b7b358c072" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adddb0e648752aaac5da9c3c3c6a68e1e87cab55e3b757b18a9441ef8c3ac614" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "static_vcruntime", + "strsim", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb37631cf71dab5e8ab55e1bdd701e18a52653d90b78e71d0e5598b1e0a61316" +dependencies = [ + "base64 0.23.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc38db58854c10fb5f91a887ed8bf82d3239bb92b5d8db86eda09e96d9963d1" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fb21a77e8f94e8beb3325e583102488659365af0a3948b7ea244c5b779c291" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "walkdir", +] + +[[package]] +name = "tauri-plugin-notification" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc6c194a95a535abdfbe3bc1af9a43935b8b9b29c216b8e71af8b59132703cf" +dependencies = [ + "log", + "notify-rust", + "rand", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.21", + "time", + "url", + "zbus", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53810be1b4fed22bb6ac3f4cab5a2b80d9391d1af436bda145607532bc0bb219" +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", +] + +[[package]] +name = "tauri-plugin-single-instance" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726b6da0dd5bd1344f84f4108a622c07d01923baacb5c803a605189672c5352f" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.21", + "tokio", + "tracing", + "windows-sys 0.61.2", + "zbus", +] + +[[package]] +name = "tauri-plugin-updater" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d957abbcb3f9f6c76d22a3a0f012f30880640d0bccb5634d2b12a9937e4c872b" +dependencies = [ + "base64 0.23.1", + "dirs", + "flate2", + "futures-util", + "http", + "infer", + "libc", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest", + "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-runtime" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c55766d99cea69055904d1735b93c2049302b2e09effd462120deb5581d78e9" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1aa9d8b7a120f879f44fdc1053f7b7dbf416bf0bbc6a72b714dec46b782b52f" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "362ab21cd4cc713f2a02b1a9b5688198b1541cfb6907c4281e9190e0f0ef4aa3" +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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52bb6904475a6ca662f15ef787f18ade61d5740a1ee12c45d3b748efdb6f796" +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.9", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[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 = "tinystr" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" +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.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "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-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.21", + "time", + "tracing-subscriber", +] + +[[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", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[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", + "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 = "typed-path" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" + +[[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.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca718471876992059f251e33aafc334346ada08042b7661dd1fe313a9bced557" + +[[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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3bd9d3e571c26fcce9d033b87ea5ab061b8d2ffe9b7a5675d810d68f03edf0" +dependencies = [ + "utf_types", +] + +[[package]] +name = "utf_types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6" + +[[package]] +name = "uuid" +version = "1.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[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 = "vitos-updater" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "semver", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-notification", + "tauri-plugin-opener", + "tauri-plugin-single-instance", + "tauri-plugin-updater", + "thiserror 2.0.21", + "tokio", + "tracing", + "tracing-appender", + "tracing-subscriber", + "unicode-width", + "which", +] + +[[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.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c" +dependencies = [ + "cfg-if", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14e0327cb5368ad60ccb22d04a9827a693c95a926d8d125e9fa39e0baec501" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e" +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.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d179596ff5ec0f6de4db64d54011bde6289cc49603d3a6e4ce3f329207ab88" +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 = "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 = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + +[[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", +] + +[[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", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759c50ddad0ef9913bb5496cb9d509a50433a3b78cb0238d61105526589cf0e9" +dependencies = [ + "base64 0.23.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "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.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963d6fab92d5504b971dc8bd90ca743c2575e590bc8071ea5bc571598667ae08" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" +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.9", + "synstructure", +] + +[[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", + "zbus_names", + "zvariant", +] + +[[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.9", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812a12be4ead7a59e18ea8f047d08add0066b73bc62fc1467066a2472ae088dd" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "939f186d9ecebe044315a5cb6d6f78568bbc21152adc954d5a0c2f58bdbc9920" +dependencies = [ + "serde", +] + +[[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.9", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "zip" +version = "9.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1156b39d142a34b999c2310a50455e0cb3760bf7efad0122f796d813bee26ccb" +dependencies = [ + "crc32fast", + "getrandom 0.4.3", + "indexmap 2.14.2", + "memchr", + "nix", + "typed-path", + "windows-sys 0.61.2", +] + +[[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 = "5.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bf6d6cd6a4b601acb64cfb8284cdc643fcfa75ef26ae3c46ec8aa4e8a1fbb7" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2672456ba1cb54b5eaec136e63135b3c7495fb938e53b28a62e633496d486d" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.9", + "zvariant_utils", +] + +[[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.9", + "winnow 1.0.4", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..1e46136 --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "vitos-updater" +version = "0.1.0" +description = "VitosUpdater – einheitliche Paketverwaltung für Windows" +edition = "2021" +rust-version = "1.82" + +[lib] +name = "vitos_updater_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-single-instance = "2" +tauri-plugin-opener = "2" +tauri-plugin-notification = "2" +tauri-plugin-updater = { version = "2", default-features = false, features = ["native-tls", "system-proxy", "zip"] } +semver = "1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["process", "time", "io-util", "sync", "rt-multi-thread", "macros"] } +async-trait = "0.1" +anyhow = "1" +thiserror = "2" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } +tracing-appender = "0.2" +unicode-width = "0.2" +which = "8" + +[profile.release] +codegen-units = 1 +lto = true +opt-level = "s" +panic = "abort" +strip = true diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 0000000..64d4c84 --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,7 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Standardberechtigungen des Hauptfensters", + "windows": ["main"], + "permissions": ["core:default", "core:window:allow-show", "core:window:allow-set-focus", "opener:allow-open-url", "notification:default", "updater:default"] +} diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..7b1548be60b99edeaee27acf17b4d5ef76df4b7d GIT binary patch literal 4582 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 SW26}X0000 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2b33457054dd2c5b107d31971ce9b1d4c51daada GIT binary patch literal 3918 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 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2910e846639a38aee6a36e938cce3d520b021976 GIT binary patch literal 5087 zcmV<56Cmt~P)AQG$y3Wj6hR1chfhA4$n zMRfD%zPTL-XZ7mc=GfR8+Xtm=9lZ<%T#7=R2LfLuP~_966e&#Gf${`2ztVvga%X8jRO`Y4+8eV#jqjlRIYhv?A#DAWBIrz1ZW2#e&I7##0J!nx|t4zHmeeL0&OI(=%Y1deOj=v@Y@V?u~#Pn>As=7fCW;(pr2+(Q;b z(nN+P=ygxOrr{N?5=7E8Q<*GO$`eJNEFRiixQ|s1e$IpFJ|YY>S6+SO+EgmP1qoE! zDOqZDRzszg!4sW%;$oLH0b!4D?q<_+@ay&u&;$lxOCyys(;1dPckJ9e_z?vUw7vq9 zL8d1knrwE_7TcbrX{qE8Fewd9uRsIdWI*@Kbu4$PRJ61C!n(CL74KwVhKGfWRpR)H z@SlF@@LSTE!lMigvbd3rPo$ygR%m9Q8(F(>(gOw4nw-35$L9P!tt!l-l_fXRc*Tt` zdKsG9hg>10bkVV?oxSTbOAgTNBAYGMW%DTt6d;9(!S%W9 z&`Q>6G&Bi;#yd{XxK=7xFby+xD<9f-t`HF=jwfxdy6WXYFjLPtPNCLm5&}(w zYjYj<4d43o(EespN6xR?j48>2snliZ%p!)y*3cvr8bmvt&4VzE_wdU-P1tBjizbWX zAMhtp=;B{v>f zs*HiID?)(ded&Ggdu51zbOpN+>bt9z=3aNMd|l+9+y zSxF8{GTY4OM$pjoFf=uxgD8v?m}unH>T2Hu!NKu^uq;!uhuhGSCehH;exHSdBbw{J zGDyUVh9;rVh}pkyK@8yJn%rm&P0vGfLQ6qxF~_K(=@V!mzAOg~P5(fHv1U1FXtbn> zk{;72a!*5(9B42=WS7UAG1f87gxJ55Ca@hR>C@9h8R4=uY53#I(@+?g`9;gl zz>Y_!VB0@UfoXV>$;G&@y9=Pfn8BG#ge7HqDo!H2e{BZd{kF6^HhW4+(I^dHnug^k zN%-`a%aBed5E}nI(dNQRNr7g9ptDp^48V1MJKzRpEXNw9#fh77Ano!^*1scBTl(PY+EE90% z>9Qt)=2Y{lO7XHK0?t@zK*gflF8dRjz~CgwxTka(k{T>MGHKD0wcTG$Eg2Fsl;eJb zCJ;Euvs*MToAns4Ym|<9I?)EuS*)# zk|y+cn0+}tMHJK6r_)n#iKC%OyiSAdR*p3^35Et+r$TCIG&Es+Ri=g}oHYV5fzy-d z;O;TK3{A7BzNY8xQ)t53aS{Ygyi-;(puu)W0QRd+(?}QSH)x`TRU|YUq2DEqXkR9$ zhGtqn>OV;nDLqC@nqXP2NrNVaGAI2zJz*b16S2%mze*ZNw4}j~`l@<3Ef=FqRDCFE zqAAA-&*g4uEJXPCyA_%snVd4x-b8&0P19{4dlZ`3%baLQ6CnDpSE1>M%b^RPX;f9B z6VSvpJ=OQWo1noK36eqfvIJH$FNNrSXzCODxbLd}^l!C6ujma) z6VBzZfY5OA8y`DbM3|yz>Y==VlNJe81b~l~r|J^wL;xpkhoIrU*#ydwqIcG%l$*6= zP=JHpG0s<)Vqu~2>{B8>9jCVR#D#%|2fbo@Pvtw`nud!mFk!_qsi5J~Bv1d$fgO)o zaNvI(C=gJt+d4E+D{!^~4NI9~31K;vQ28KL!?@rLhDz$=PK+Nxc;XowzWMJK94GOf z%S`X1(P?OWbTAHZxURYtNa2fL(r~ISmmc}(hqAEh%$d77=CT4VrYaI|y)XrLex(fG z{;s96Gx-U|7M=(Z6`CdwlP?i}<7@+NxiSlbvr4F(K{a>ISdoUeyvc-*|9u%=ID{TN z*lB2jiQwis&cMP!!|(g4w(G0Vu(;lyTFH_o3u)7X#+YZc{eJ%5ubWC~wwx1t)>de$(xz?oaXM+I z;c5~?B)V6eENjczc`rsxE0(uhlY=*(YpSV&ZzMt+9Gr6~!Yn0UeTszJugk(fRy2jC z{@P9=rTG+6qIpUN(Lrbe`hX**$F$cX%sK!+iKY=igvx1nHj-ZP#@87hJZ7W86R|@x zN!wbUx|E^GDU(4f(98yir!H0Wsl(8;r_9L-gNTZP8ILN1ki`1rJx>_%uJWI1%k_r!yKoWS|dpE}T} zsADsD`12X6(st|hIe6`920=*24G}3Vq|tZzTx@_Gq!lRZG^f%ENkJ1pQu<`E=m8W7KDtc z_y9x0ean*VWfM(}tlQWDq4C`&+`js#)mR_v1T*uBtE)bx zgtKw-HAY}))OA*UR4i%k`?o1&BwPq++L!_?7A&5ICB|a1ZS4Av1$TeFazZi3mf$hG z8_hnhTS5ttA=f5r-G|I6_=l=%zsXqU31%?J@7NzVYG$`7# zsuBwxgrzEk5;5PMzx_s20Ua2c>N5S2r)=0v5;UI`GarI3hbCBFRhNW&Dn*yz*-D~4Lt6n|K~>RR z7XnQ={_5;a=NPnf&L{=96@X+F)GObfhS!kP@@-8)nlGKc^Rx|{zFHwqxR@ET(gV;$ zu2!*7F~V3?nC~SKzjMs0*=zzE3F*gABX>p+sl!svhen8S&^-B^0~_zFs6DVmmq8=i znOC*Yf#v=ZmV>soY%jyV?zL(wr=Tk%3}MKL0?m*9%Yl(Em6fD2WS2=AZ1W}{-LQwH z|32G1K0_jZ59tg3$|>jyDa@)W3^dREj{_g0U@h*uS-xBg=Vp4#u! zR!$4u{1w-W<1T#U3ngWAWWA<7rnqbNgb?ix)jNOi zR|xAqU#4h4l8ostg{GaWs&3zxTkULTdMmD5zEFmrke7L(;PH)nzdVL;)7>R_<){~c z?j!`7U=pVXHgo5vIhFDpkAF=!?B_3e@HkHAp*c3`LGu!YNDO3?1x-iFoOF7NWrm0K zh)ME?esoJ2{_kZs)M_gVG#rez?(Q=DH+l67IgvPMV%u7=TcD|jCqq-i5sLb|`Ez9$ zKkP-+1vL@|X>Ju7mUgVu{DhJN*=bn=R`kZ^XzF)U9oSm2+n}j``UeP}mya+!B`+LP zwg(>H<-vN^U)@uJXMXO$qP&>auX+TUAf{s(x~xYRL*oySivA=0f~PBn$KUojPIwp= z-%qxIR&-BMnCs#~4?-h4QIfw0psANUoIcEo^3A04l&3s+TsJ0Ys^`0f+z3zn*zR3u zpaXWCUV*0G5XVl@{VzJOo|IO;u`(h&^?Q_ma$5x+`=PB8>G}bhSj(JrD>VLl9IeNC z$J1mS+(1@b#PIOcJ2~&5c$$Cyz6DDY01ZMroSZ!j4b=IKcq;h;g*ANqi{%;N6+xi! z8%OT#c*24ug=9d}7CTO_LsJhA2ZB9L4$+NYDhCR$@SjMQHuq3Q!q@hA(Dag>W?pEp z(}7$)0!=+UoaylBf7o!_wn|Hx6Ihp#u=U$h>Mv2y1YB7~ND%2wX#8_4csO%{g2nIL zUYU`-&}jAK+iJeH+fw;Yi<1UTU9;N0UsaDnQ@`5c;P)MrcEdic8{UY0!vv?K_?@XzDX3ILni3@Es)A#0M!5{k0TRT%Tfoh>m}SlI0#I z>BPP#z(%!p5&#W$sw97pLj$w2MfW~yE6eXWuaJMSh@Ls%!ViDu)Y6B!@PTqz_WAS_swBTiFf=%;)CfCAh43;f zeSJqm)9xMe5-@l!&XFpjp^3h#O8VgSD>U(MA1yRA{czo)W6N<3OH2pC>MY7{)Xp#p_yt~JYjCj9ipo3ohq6Qt(&?FHW+6NWGgCqT#z7`j) zs|emzRcL6E4UG`!+F-e^+HQW;sw*&r9mjj?q0cTkLLD3z(&X3_)X*dq8bl!tilugs z(~EIsk{N+8-~bK}qftG{vy$qBP_;c*LzB$uiNlYCk>hv1rCg_@-9&z zKGLwW8k)o?!mP&%RTl?$^M6;5xMpaV!`KKoUI}~o&{U~7Nz;{COIy>>BncY+sB|rg zk9EZTd~EqXf#H2sB&wLa?ROLo(yRMYnJk~g*3cvi8Wd<7nE~lo<=u~eBL5_exN`e; z;Y4zBUR2#k&aKFem2$Bp4Ksy^u7)NV&?u#tIAEg-%_ST7$nd`A*1@LWjE+i|rOwXJ zDn>2c&J6ZhK-h|2;LsG?6xeQm}Z1#Xq?ArsAF1 zeEu&glM}S-D4HsdL_^b)lBNnxda5|N>+!$I|DpG;s{~MN;CMf%gcPn_SG+5e%U^Gm zCn*<+EJjk88k*kHX_yX{G*c6$U5}3pT&CV4-3Dlefq1J`5QWvB+<8aghRQ_odV>LfzOw^wE`9Sr2 z8o5vE@z9-w=)8(qAgmEh@n$g_mRhzY&H z8jH7mv~;W2!r!s}=t+(%Pj$h`w^X7#V6$S)MGe;Ms6UVjaeWaR=@(@yT{!gQ$HbxO z8|TX5S@*4@;M4=Ohp<(aW+@SL>&nIwhAqNTfQc2HS@r3~$S888QaO@pJG21hpyj&4b9{|`g1*LZ?%6ITEL002ovPDHLkV1fY= B-+%xB literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3f2d97abce769b1de7523ce5bef69d724ba03f46 GIT binary patch literal 5162 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 Y2Li#Ctuxaag8%>k07*qoM6N<$g5~4kh5!Hn literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4c9dbb839049428bf41b7212904d272760c82d6c GIT binary patch literal 10271 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 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..78f90625d1f0018491ffb1c76ac609d7b4e77999 GIT binary patch literal 1207 zcmV;o1W5adP)6z)O^1Z6+?&_YN*<3QaJzZV%>V5D3y%yj##|4(l)qYQ;1nXn@%*!QyT685m@l3=@#j_q z(Wiv|Ad6!V(9pto@%V0nJLJ!ZfEBhsKkC z4G4+;P!*)k7z4pdpye}!-P+i|$#`_(i_>sX=;5-B4EL&0*Y<}B`^tO8Vev;(iTI(t@EVB zm>(@L2a+@{(5$8bsA&M@`)Fc-qp{odK3)Zip+E>qC8hv&2TQRJ0?i2Vk%tos4S=uC zF91cJAFDxgcj;`7HQ>^ut3VlGGBa@BJrV3Y?Q-akzcu`DIfGt@;?%4MwR?%%FMtb) zSNN3g>{CVB-n>y-hu6GkX-zYzeGZgr)Hw842paAwT{5uiHRd%nz-8bypnyTHVkux5 z@mTD!RWwvJttjTD5TV8fn?X64STk&t-n)S~DdT7ntLCl8PZhWz!V2gt1L#oTv?!LQ zbFh&dGT^n4?za{NPM2uYtQ4|d?dEmhBEY<~tO0{=9TD)}$u4|wst0#&2^dX*ia>cp zlCwtCDYl93!{@tj=EV*iKPC$BdYUzBT$D>MU1nhWsDN8H(Oz#p*Rjw4O-ta>)WEUZ z5#HWu!{I{+w@{QHCz{#9Snh?^P_1wxZ*4_g^|hD%#Z~G2UIyO`GfT|5`WAy9DGhb> zHbI~r*z7s3-(Je#>mM`N=mu$4iJdU2xfZBu{uYvvSx=p#A5S3f z*8Rs4kY0}AzJk|3nb|l1tZBM7xUia5oEFR^rn3IVxfxvCSH*AW3rhaEbWOpl=Tb`_ z$vma7(k=m;fa{khvfvUe!`IHHaG3yD!%Vr0*H?biaPp%RcyNj$=cYy8yhF26^ zys8K^h4GB|#j8O>-g8*dB-XVytsTIGPQOfme1$RE z13-fwFJv$X@Ca(83)W_nR|~#2UP!ZU+6Xem{<%h?6_kU3LcKubr7>pJFi?vHfQDM! zGy=O}%C>4(h#~XArSbFk5B)-`VL~IQ&xA^0htNm3Tw=o-U$6Pj8ffBPR~V!Az8|!I zvcS8$e#l#7PtC^T3p|2?@LhtE*YL&b{Mp~_m?`Rep1yX5XvKUy-9wQ*k-tqWy}~^D zLVA~s=5a>TpjI@PCCk&peBC=u60|Q6TiZQFKvSXTT_@D_1YFH_&;Fa VmF)?HWU2rF002ovPDHLkV1mV*QJnw) literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe420e252705ac4dae4a016d497e4abc1ac36b5 GIT binary patch literal 11038 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 aFF|YB zVLGGOFrZ}&fAJ1$3#DcBHS{hj7vUShm@J6fDQ(hy#ck*>Y6JJ{7Xj(DMAcXW4~YYHoFDpb5-ST$fRl zI@^UbvS399tjK^BS+Gi9fQZ8Li-FrvY@NITi$%oxhK0Ui!*N)^7K=6Ml|uvT1JQ84Wep)tz=by^KydefdB?cStIiF)vJ$i*rjA?0r zDctsBU?f2Y)xcIdH?j8|0{ec`fO>(Af4NR2tfX)3)pf{XD zq9;Gg^su1j2feC6nn!DNO*h69>wj_K7ie*Vy#C^+TKqfaqaOnj$TP3n!gJ1}59q)z!6Mb!|5Kg(!48X!x z;l~i`v@4`XavX*(FEGk>ux%xr_^J%aRuT5@I}BK{%!VH)JvjVL1Byi!IAV={38n4z zs(|H7Ex7kC6ONqb@cpDOp%WjQ*7YnMCRb-CrnVKfj+GYO#^Bvoir`@7l@Hjk@Kyu% ze^G9X3eOSa$N*QQ+s_D;O*U2m_j%UJMuy=vALXsRS()w zb(>~F$=I-VKq9Oy`EEjA`LWHGT9M{DLqqBk3{n02dYHZ8y~xH8eQSg0E=g(6Kq?fi#zCIS<}D)Y2Y)zdN77ki_%Ho zPiAV-GIwmR3n$KVm^auRtjfW&mq_}kr%S%+Cg@=VIC;*4H_?b747~(RcjV=9~EJUq#*;Tj8 z8VQZkJNzw2U3TH+r*jf`_n|s`d7=R$L*1)EONN(PBDEwxqSKB{hBU|!Nv98g$Klj3 z9!4*WaML~v5A+VJ&UE$?shjPi4*4t@SrN0h*&3AznaOqIXLYd9d0&DjlJGgLZbP3- zF3vFFe}D~^SJH{=by)NTtLCsm^e@?n!Bg_J7?^gxg}MnhN7&Y^Yp$@-M!Hp|N&f<40e)iM#Q+)(&Mc6l! z&$zj6S|yo#j(@$4U43Z$h=tBwv>eK;di~l)9BhZ>6z5Ymp0#Px^$|kG62EO*{KFfw zSMi^>j#o;%FwQfAZSlM@^w!IRxYAn6gi+8PdyymOZH~QSn>ITfiCIbkx-RktWMHP$ zS@-nO%E*)ot3O!O9Om7qi`3v~Wd(105*jA4th^TWp?m|6ZqdM0$wt?Iz@eBWt9|=? P00000NkvXXu0mjf+tqYF literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1620879b4927936f55cbe37e9b1a72a2bc06e83e GIT binary patch literal 2549 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 LNkvXXu0mjfgCWJ# literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6c1989579eba7accae04dbc2b4c4dc58ab9e7ecb GIT binary patch literal 3239 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 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..66ad9868dae3f6ad0d2a611b97f9367f4a0bb80f GIT binary patch literal 19716 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 +qE1Apj1?3;m%_!N^!;9cXFS_kYl{{fTKtCI&v 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 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/source.png b/src-tauri/icons/source.png new file mode 100644 index 0000000000000000000000000000000000000000..da05e7887948e5ea7d973192145be462a2d097da GIT binary patch literal 90107 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 aXGqk-2M!$ws|kRAZ_b+cTHdS6KL0<}T#GmW literal 0 HcmV?d00001 diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs new file mode 100644 index 0000000..cd1c875 --- /dev/null +++ b/src-tauri/src/commands/mod.rs @@ -0,0 +1,126 @@ +//! Tauri-Befehle, die die Oberfläche aufruft. + +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; + +use tauri::State; + +use crate::error::{AppError, AppResult}; +use crate::managers::winget::WingetManager; +use crate::managers::PackageManager; +use crate::models::{ManagerId, ManagerInfo, Package, PackageDetails, Source}; +use crate::process; + +/// Registrierte Paketmanager. +pub struct Registry { + managers: HashMap>, +} + +impl Registry { + /// Erstellt die Registry mit allen verfügbaren Implementierungen. + pub fn new() -> Self { + let mut managers: HashMap> = HashMap::new(); + managers.insert(ManagerId::Winget, Arc::new(WingetManager::new())); + Self { managers } + } + + fn get(&self, id: ManagerId) -> AppResult> { + self.managers + .get(&id) + .cloned() + .ok_or_else(|| AppError::Command("Dieser Paketmanager wird noch nicht unterstützt.".to_owned())) + } + + async fn detect_all(&self) -> Vec { + let mut infos = Vec::new(); + for manager in self.managers.values() { + infos.push(manager.detect().await); + } + infos + } +} + +impl Default for Registry { + fn default() -> Self { + Self::new() + } +} + +fn logged(what: &str, result: AppResult) -> AppResult { + if let Err(err) = &result { + tracing::error!(%err, "{what} fehlgeschlagen"); + } + result +} + +/// Erkennt alle Paketmanager (Pfad, Version, Zusatzkomponenten). +#[tauri::command] +pub async fn detect_managers(registry: State<'_, Registry>) -> AppResult> { + Ok(registry.detect_all().await) +} + +/// Sucht Pakete. +#[tauri::command] +pub async fn search_packages(registry: State<'_, Registry>, manager: ManagerId, query: String) -> AppResult> { + let query = query.trim().to_owned(); + if query.is_empty() { + return Ok(Vec::new()); + } + logged("Paketsuche", registry.get(manager)?.search(&query).await) +} + +/// Listet installierte Pakete. +#[tauri::command] +pub async fn list_installed(registry: State<'_, Registry>, manager: ManagerId) -> AppResult> { + logged("Liste der installierten Pakete", registry.get(manager)?.list_installed().await) +} + +/// Listet verfügbare Aktualisierungen. +#[tauri::command] +pub async fn list_updates(registry: State<'_, Registry>, manager: ManagerId) -> AppResult> { + logged("Suche nach Aktualisierungen", registry.get(manager)?.list_updates().await) +} + +/// Lädt Paketdetails. +#[tauri::command] +pub async fn get_package_details(registry: State<'_, Registry>, package: Package) -> AppResult { + logged("Paketdetails", registry.get(package.manager)?.get_details(&package).await) +} + +/// Listet die Quellen eines Managers. +#[tauri::command] +pub async fn list_sources(registry: State<'_, Registry>, manager: ManagerId) -> AppResult> { + logged("Quellenliste", registry.get(manager)?.list_sources().await) +} + +async fn run_powershell(script: &str, timeout: Duration) -> AppResult<()> { + let shell = which::which("powershell") + .map_err(|_| AppError::ManagerMissing("Windows PowerShell".to_owned()))?; + let out = process::run(&shell, &["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], timeout).await?; + if out.code == Some(0) { + Ok(()) + } else { + let detail = out.stderr.lines().find(|l| !l.trim().is_empty()).unwrap_or("unbekannter Fehler"); + Err(AppError::Command(format!("Die Installation ist fehlgeschlagen: {detail}"))) + } +} + +/// Installiert das PowerShell-Modul `Microsoft.WinGet.Client` für den aktuellen Benutzer. +#[tauri::command] +pub async fn install_winget_module(registry: State<'_, Registry>) -> AppResult> { + let script = "$ProgressPreference='SilentlyContinue'; $ErrorActionPreference='Stop'; \ + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force | Out-Null; \ + Install-Module -Name Microsoft.WinGet.Client -Scope CurrentUser -Repository PSGallery -Force"; + logged("Modulinstallation", run_powershell(script, Duration::from_secs(300)).await)?; + Ok(registry.detect_all().await) +} + +/// Registriert bzw. installiert den „App-Installer“ (enthält WinGet). +#[tauri::command] +pub async fn install_app_installer(registry: State<'_, Registry>) -> AppResult> { + let script = "$ProgressPreference='SilentlyContinue'; $ErrorActionPreference='Stop'; \ + Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; + logged("Installation des App-Installers", run_powershell(script, Duration::from_secs(300)).await)?; + Ok(registry.detect_all().await) +} diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs new file mode 100644 index 0000000..f7f713c --- /dev/null +++ b/src-tauri/src/error.rs @@ -0,0 +1,42 @@ +//! Zentrale Fehlertypen. Die Meldungen sind für die Anzeige in der Oberfläche gedacht und daher deutsch. + +use serde::{Serialize, Serializer}; + +/// Fehler, die an die Oberfläche weitergereicht werden. +#[derive(Debug, thiserror::Error)] +pub enum AppError { + /// Der Paketmanager ist nicht installiert oder nicht gefunden worden. + #[error("{0} wurde nicht gefunden.")] + ManagerMissing(String), + /// Ein externer Prozess konnte nicht gestartet werden. + #[error("Der Prozess „{program}“ konnte nicht gestartet werden: {source}")] + Spawn { + program: String, + #[source] + source: std::io::Error, + }, + /// Ein externer Prozess hat das Zeitlimit überschritten. + #[error("Zeitüberschreitung nach {0} Sekunden.")] + Timeout(u64), + /// Ein Paketmanager hat mit einem Fehlercode geendet. + #[error("{0}")] + Command(String), + /// Die Ausgabe eines Paketmanagers konnte nicht gelesen werden. + #[error("Die Ausgabe konnte nicht gelesen werden: {0}")] + Parse(String), + /// Ein-/Ausgabefehler. + #[error("Ein-/Ausgabefehler: {0}")] + Io(#[from] std::io::Error), + /// JSON-Fehler. + #[error("Ungültige JSON-Daten: {0}")] + Json(#[from] serde_json::Error), +} + +impl Serialize for AppError { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } +} + +/// Ergebnistyp der Anwendung. +pub type AppResult = Result; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..8cc9b6c --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,146 @@ +//! VitosUpdater – einheitliche Paketverwaltung für Windows. + +pub mod commands; +pub mod error; +pub mod logging; +pub mod managers; +pub mod models; +pub mod process; +pub mod settings; +pub mod updater; + +use std::path::PathBuf; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use tauri::{Manager, PhysicalPosition, PhysicalSize, WebviewWindow, WindowEvent}; + +use settings::WindowState; + +/// Letzte normale (nicht maximierte) Geometrie und Zähler für das Entprellen des Speicherns. +struct WindowTracker { + dir: PathBuf, + normal: Mutex>, + generation: AtomicU64, +} + +fn restore_window(window: &WebviewWindow, saved: Option) { + if let Some(state) = saved { + let monitors: Vec<(i32, i32, u32, u32)> = window + .available_monitors() + .unwrap_or_default() + .iter() + .map(|m| (m.position().x, m.position().y, m.size().width, m.size().height)) + .collect(); + if settings::is_visible_on(&state, &monitors) { + let _ = window.set_size(PhysicalSize::new(state.width, state.height)); + let _ = window.set_position(PhysicalPosition::new(state.x, state.y)); + } + if state.maximized { + let _ = window.maximize(); + } + } + let _ = window.show(); +} + +fn track_window(window: &WebviewWindow, tracker: &Arc) { + let maximized = window.is_maximized().unwrap_or(false); + let minimized = window.is_minimized().unwrap_or(false); + if minimized { + return; + } + { + let mut normal = tracker.normal.lock().unwrap_or_else(|e| e.into_inner()); + if !maximized { + if let (Ok(pos), Ok(size)) = (window.outer_position(), window.inner_size()) { + *normal = Some(WindowState { x: pos.x, y: pos.y, width: size.width, height: size.height, maximized: false }); + } + } else if let Some(state) = normal.as_mut() { + state.maximized = true; + } + } + let generation = tracker.generation.fetch_add(1, Ordering::SeqCst) + 1; + let tracker = Arc::clone(tracker); + tauri::async_runtime::spawn(async move { + tokio::time::sleep(Duration::from_millis(500)).await; + if tracker.generation.load(Ordering::SeqCst) != generation { + return; + } + let snapshot = *tracker.normal.lock().unwrap_or_else(|e| e.into_inner()); + if let Some(mut state) = snapshot { + state.maximized = maximized; + if let Err(err) = settings::save_window_state(&tracker.dir, &state) { + tracing::warn!(%err, "Fensterzustand konnte nicht gespeichert werden"); + } + } + }); +} + +/// Startet die Anwendung. +pub fn run() { + let dir = logging::data_dir(); + let _guard = logging::init(&dir); + tracing::info!(version = env!("CARGO_PKG_VERSION"), "VitosUpdater startet"); + + let tracker = Arc::new(WindowTracker { + dir: dir.clone(), + normal: Mutex::new(settings::load_window_state(&dir)), + generation: AtomicU64::new(0), + }); + let saved = settings::load_window_state(&dir); + let event_tracker = Arc::clone(&tracker); + + let result = tauri::Builder::default() + .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { + if let Some(window) = app.get_webview_window("main") { + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + } + })) + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_notification::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) + .manage(commands::Registry::new()) + .manage(updater::UpdaterState::new(dir.clone())) + .invoke_handler(tauri::generate_handler![ + commands::detect_managers, + commands::search_packages, + commands::list_installed, + commands::list_updates, + commands::get_package_details, + commands::list_sources, + commands::install_winget_module, + commands::install_app_installer, + updater::updater_ready, + updater::get_updater_status, + updater::get_update_offer, + updater::check_for_update, + updater::snooze_update, + updater::skip_update, + updater::clear_skipped_version, + updater::set_update_interval, + updater::install_update, + updater::take_update_notice, + ]) + .setup(move |app| { + if let Some(window) = app.get_webview_window("main") { + restore_window(&window, saved); + } + Ok(()) + }) + .on_window_event(move |window, event| { + if matches!(event, WindowEvent::Moved(_) | WindowEvent::Resized(_)) { + if let Some(webview) = window.app_handle().get_webview_window(window.label()) { + track_window(&webview, &event_tracker); + } + } + }) + .run(tauri::generate_context!()); + + if let Err(err) = result { + tracing::error!(%err, "Anwendung wurde mit einem Fehler beendet"); + std::process::exit(1); + } +} diff --git a/src-tauri/src/logging.rs b/src-tauri/src/logging.rs new file mode 100644 index 0000000..3b5b437 --- /dev/null +++ b/src-tauri/src/logging.rs @@ -0,0 +1,37 @@ +//! Protokollierung in rotierende Logdateien unter `%APPDATA%\VitosUpdater\logs`. + +use std::path::{Path, PathBuf}; + +use tracing_appender::non_blocking::WorkerGuard; +use tracing_appender::rolling; +use tracing_subscriber::EnvFilter; + +/// Basisverzeichnis für Konfiguration und Zustand (`%APPDATA%\VitosUpdater`). +pub fn data_dir() -> PathBuf { + let base = std::env::var_os("APPDATA") + .map(PathBuf::from) + .unwrap_or_else(std::env::temp_dir); + base.join("VitosUpdater") +} + +/// Initialisiert die Protokollierung. Der Guard muss bis zum Programmende gehalten werden. +pub fn init(dir: &Path) -> Option { + let logs = dir.join("logs"); + std::fs::create_dir_all(&logs).ok()?; + let appender = rolling::Builder::new() + .rotation(rolling::Rotation::DAILY) + .filename_prefix("vitosupdater") + .filename_suffix("log") + .max_log_files(14) + .build(&logs) + .ok()?; + let (writer, guard) = tracing_appender::non_blocking(appender); + let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + tracing_subscriber::fmt() + .with_env_filter(filter) + .with_writer(writer) + .with_ansi(false) + .try_init() + .ok()?; + Some(guard) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..251e50d --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,5 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + vitos_updater_lib::run(); +} diff --git a/src-tauri/src/managers/mod.rs b/src-tauri/src/managers/mod.rs new file mode 100644 index 0000000..0062a62 --- /dev/null +++ b/src-tauri/src/managers/mod.rs @@ -0,0 +1,36 @@ +//! Gemeinsame Schnittstelle aller Paketmanager. + +use async_trait::async_trait; + +use crate::error::AppResult; +use crate::models::{ManagerId, ManagerInfo, Package, PackageDetails, Source}; + +pub mod winget; + +/// Einheitliche Schnittstelle für Paketmanager. +/// +/// Die Operationen `install`, `update` und `uninstall` kommen mit der Warteschlange in Phase 2 hinzu, +/// weil sie den Ausgabestrom und den Prozess-Lebenszyklus der Warteschlange benötigen. +#[async_trait] +pub trait PackageManager: Send + Sync { + /// Kennung des Managers. + fn id(&self) -> ManagerId; + + /// Erkennt Installation, Pfad und Version. + async fn detect(&self) -> ManagerInfo; + + /// Sucht Pakete. + async fn search(&self, query: &str) -> AppResult>; + + /// Listet installierte Pakete. + async fn list_installed(&self) -> AppResult>; + + /// Listet Pakete mit verfügbarer Aktualisierung. + async fn list_updates(&self) -> AppResult>; + + /// Lädt Detailinformationen zu einem Paket. + async fn get_details(&self, package: &Package) -> AppResult; + + /// Listet die konfigurierten Quellen. + async fn list_sources(&self) -> AppResult>; +} diff --git a/src-tauri/src/managers/winget/mod.rs b/src-tauri/src/managers/winget/mod.rs new file mode 100644 index 0000000..af3e62b --- /dev/null +++ b/src-tauri/src/managers/winget/mod.rs @@ -0,0 +1,190 @@ +//! WinGet-Manager: PowerShell-Modul (JSON) wenn vorhanden, sonst Tabellenparser der Kommandozeile. + +use std::path::{Path, PathBuf}; +use std::time::Duration; + +use async_trait::async_trait; +use tokio::sync::RwLock; + +use super::PackageManager; +use crate::error::{AppError, AppResult}; +use crate::models::{ExtraInfo, ManagerId, ManagerInfo, Package, PackageDetails, Source}; +use crate::process::{self, CmdOutput}; + +pub mod parser; +pub mod ps; + +/// Exit-Code von WinGet bei „keine Treffer“ (0x8A150014), per Test mit WinGet 1.29 ermittelt. +const EXIT_NO_MATCH: i32 = 0x8A15_0014_u32 as i32; +/// Gemeinsame Argumente, damit WinGet nie auf Eingaben wartet. +const COMMON_ARGS: [&str; 2] = ["--accept-source-agreements", "--disable-interactivity"]; +const QUERY_TIMEOUT: Duration = Duration::from_secs(180); + +#[derive(Debug, Clone, Default)] +struct Detected { + path: Option, + module: Option, +} + +/// WinGet-Implementierung von [`PackageManager`]. +#[derive(Default)] +pub struct WingetManager { + state: RwLock, +} + +/// Sucht `winget.exe` im PATH und danach in `%LOCALAPPDATA%\Microsoft\WindowsApps`. +pub fn locate_winget() -> Option { + if let Ok(path) = which::which("winget") { + return Some(path); + } + let local = std::env::var_os("LOCALAPPDATA")?; + let candidate = Path::new(&local).join("Microsoft").join("WindowsApps").join("winget.exe"); + candidate.is_file().then_some(candidate) +} + +/// Formatiert einen WinGet-Exit-Code als hexadezimale Zahl, z. B. `0x8A150014`. +pub fn format_exit_code(code: i32) -> String { + format!("0x{:08X}", code as u32) +} + +impl WingetManager { + /// Erstellt einen Manager ohne Erkennung. [`PackageManager::detect`] füllt den Zustand. + pub fn new() -> Self { + Self::default() + } + + async fn exe(&self) -> AppResult { + if let Some(path) = self.state.read().await.path.clone() { + return Ok(path); + } + self.detect().await; + self.state + .read() + .await + .path + .clone() + .ok_or_else(|| AppError::ManagerMissing("WinGet".to_owned())) + } + + async fn module_shell(&self) -> Option { + self.state.read().await.module.as_ref().map(|m| m.shell.clone()) + } + + /// Führt winget aus; „keine Treffer“ ergibt eine leere Ausgabe, andere Fehler einen Fehler. + async fn table(&self, args: &[&str]) -> AppResult { + let exe = self.exe().await?; + let out: CmdOutput = process::run(&exe, args, QUERY_TIMEOUT).await?; + match out.code { + Some(0) => Ok(out.stdout), + Some(EXIT_NO_MATCH) => Ok(String::new()), + // Manche Versionen liefern trotz Teilfehler (z. B. eine nicht erreichbare Quelle) eine Tabelle. + Some(code) if !parser::parse_table(&out.stdout).is_empty() => { + tracing::warn!(code = %format_exit_code(code), "WinGet meldete einen Fehler, Tabelle wird trotzdem verwendet"); + Ok(out.stdout) + } + Some(code) => Err(AppError::Command(format!( + "WinGet wurde mit dem Fehlercode {} beendet.", + format_exit_code(code) + ))), + None => Err(AppError::Command("WinGet wurde unerwartet beendet.".to_owned())), + } + } +} + +#[async_trait] +impl PackageManager for WingetManager { + fn id(&self) -> ManagerId { + ManagerId::Winget + } + + async fn detect(&self) -> ManagerInfo { + let path = locate_winget(); + let version = match &path { + Some(p) => process::run(p, &["--version"], Duration::from_secs(20)) + .await + .ok() + .filter(|o| o.code == Some(0)) + .map(|o| o.stdout.trim().trim_start_matches('v').to_owned()) + .filter(|v| !v.is_empty()), + None => None, + }; + let module = ps::detect_module().await; + let info = ManagerInfo { + id: ManagerId::Winget, + name: "WinGet".to_owned(), + available: path.is_some() && version.is_some(), + path: path.as_ref().map(|p| p.display().to_string()), + version: version.clone(), + extras: vec![ExtraInfo { + name: "Microsoft.WinGet.Client".to_owned(), + available: module.is_some(), + version: module.as_ref().map(|m| m.version.clone()), + }], + }; + *self.state.write().await = Detected { path: path.filter(|_| info.available), module }; + tracing::info!(available = info.available, version = ?info.version, module = info.extras[0].available, "WinGet erkannt"); + info + } + + async fn search(&self, query: &str) -> AppResult> { + if let Some(shell) = self.module_shell().await { + match ps::search(&shell, query).await { + Ok(list) => return Ok(list), + Err(err) => tracing::warn!(%err, "Modulsuche fehlgeschlagen, Kommandozeile wird verwendet"), + } + } + let text = self + .table(&["search", "--query", query, "--count", "200", COMMON_ARGS[0], COMMON_ARGS[1]]) + .await?; + Ok(parser::parse_packages(&text)) + } + + async fn list_installed(&self) -> AppResult> { + if let Some(shell) = self.module_shell().await { + match ps::list_installed(&shell).await { + Ok(list) => return Ok(list), + Err(err) => tracing::warn!(%err, "Modulabfrage fehlgeschlagen, Kommandozeile wird verwendet"), + } + } + let text = self.table(&["list", COMMON_ARGS[0], COMMON_ARGS[1]]).await?; + Ok(parser::parse_packages(&text)) + } + + async fn list_updates(&self) -> AppResult> { + if let Some(shell) = self.module_shell().await { + match ps::list_updates(&shell).await { + Ok(list) => return Ok(list), + Err(err) => tracing::warn!(%err, "Modulabfrage fehlgeschlagen, Kommandozeile wird verwendet"), + } + } + let text = self.table(&["upgrade", COMMON_ARGS[0], COMMON_ARGS[1]]).await?; + Ok(parser::parse_packages(&text)) + } + + async fn get_details(&self, package: &Package) -> AppResult { + let mut base: Vec<&str> = vec!["show", "--id", &package.id, "--exact"]; + if let Some(source) = package.source.as_deref() { + base.extend(["--source", source]); + } + base.extend(COMMON_ARGS); + let mut with_versions = base.clone(); + with_versions.push("--versions"); + + let (show, versions) = tokio::join!(self.table(&base), self.table(&with_versions)); + let mut details = parser::parse_details(&show?); + details.versions = parser::parse_versions(&versions.unwrap_or_default()); + Ok(details) + } + + async fn list_sources(&self) -> AppResult> { + let text = self.table(&["source", "list"]).await?; + Ok(parser::parse_table(&text) + .into_iter() + .map(|cells| Source { + name: cells.first().cloned().unwrap_or_default(), + url: cells.get(1).cloned().unwrap_or_default(), + explicit: cells.get(2).is_some_and(|v| v.eq_ignore_ascii_case("true")), + }) + .collect()) + } +} diff --git a/src-tauri/src/managers/winget/parser.rs b/src-tauri/src/managers/winget/parser.rs new file mode 100644 index 0000000..2081e53 --- /dev/null +++ b/src-tauri/src/managers/winget/parser.rs @@ -0,0 +1,349 @@ +//! Parser für die lokalisierte Textausgabe von `winget`. +//! +//! WinGet richtet Tabellenspalten nach der *Anzeigebreite* aus (breite Zeichen belegen zwei Zellen). +//! Spaltenüberschriften sind lokalisiert; die Spalten werden daher aus den Positionen der Überschrift +//! abgeleitet und nach ihrem Inhalt (nicht nach ihrem Namen) zugeordnet. + +use unicode_width::UnicodeWidthChar; + +use crate::models::{ensure_unique_keys, ManagerId, Package, PackageDetails}; + +/// Eine Zeile als Zellenfolge, bei der breite Zeichen zwei Zellen belegen (zweite Zelle = `'\0'`). +fn to_cells(line: &str) -> Vec { + let mut cells = Vec::with_capacity(line.len()); + for ch in line.chars() { + let width = ch.width().unwrap_or(0); + match width { + 0 => {} + 1 => cells.push(ch), + _ => { + cells.push(ch); + cells.extend(std::iter::repeat_n('\0', width - 1)); + } + } + } + cells +} + +fn slice_cells(cells: &[char], start: usize, end: Option) -> String { + if start >= cells.len() { + return String::new(); + } + let end = end.map_or(cells.len(), |e| e.min(cells.len())); + cells[start..end].iter().filter(|c| **c != '\0').collect::().trim().to_owned() +} + +/// Bereinigt Rohausgabe: entfernt Fortschrittsanzeigen (`\r`-Überschreibungen) und Steuerzeichen. +pub fn clean_lines(text: &str) -> Vec { + text.split('\n') + .map(|line| { + let line = line.trim_end_matches('\r'); + let last = line.rsplit('\r').next().unwrap_or(line); + last.chars().filter(|c| !c.is_control() || *c == '\t').collect::() + }) + .collect() +} + +fn is_separator(line: &str) -> bool { + let t = line.trim(); + t.len() >= 3 && t.chars().all(|c| c == '-') +} + +/// Zerlegt die erste Tabelle der Ausgabe in Zellen (ohne Kopfzeile). +/// +/// Zeilen nach der Tabelle (Zusammenfassungen wie „4 Aktualisierungen verfügbar.“) werden verworfen. +pub fn parse_table(text: &str) -> Vec> { + let lines = clean_lines(text); + let Some(sep_idx) = lines.iter().position(|l| is_separator(l)).filter(|i| *i > 0) else { + return Vec::new(); + }; + let header = to_cells(&lines[sep_idx - 1]); + + // Kandidaten für Zeilen: bis zur ersten Leerzeile. + let body: Vec> = lines[sep_idx + 1..] + .iter() + .take_while(|l| !l.trim().is_empty()) + .map(|l| to_cells(l)) + .collect(); + if body.is_empty() { + return Vec::new(); + } + + // Spaltenanfänge aus der Überschrift (Wortanfänge nach einem Leerzeichen). + let mut starts = vec![0usize]; + for i in 1..header.len() { + if header[i] != ' ' && header[i - 1] == ' ' && header[i] != '\0' { + starts.push(i); + } + } + + // Mehrwörtrige Überschriften: ein Anfang gilt nur, wenn davor in den meisten Zeilen eine Lücke liegt. + let gap_ok = |row: &[char], start: usize| row.len() < start || row.get(start - 1).is_none_or(|c| *c == ' '); + let threshold = (body.len() * 4).div_ceil(5); + starts.retain(|&s| s == 0 || body.iter().filter(|r| gap_ok(r, s)).count() >= threshold); + + let mut rows = Vec::new(); + for row in &body { + // Zeilen, die eine Spaltenlücke verletzen, sind Fließtext (Zusammenfassung) und beenden die Tabelle. + if starts.iter().skip(1).any(|&s| !gap_ok(row, s)) { + break; + } + let mut cells = Vec::with_capacity(starts.len()); + for (i, &s) in starts.iter().enumerate() { + cells.push(slice_cells(row, s, starts.get(i + 1).copied())); + } + if cells.get(1).is_none_or(|id| id.is_empty()) { + break; + } + rows.push(cells); + } + rows +} + +fn looks_like_source(value: &str) -> bool { + let mut chars = value.chars(); + chars.next().is_some_and(|c| c.is_ascii_alphabetic()) + && value.chars().all(|c| c.is_ascii_alphabetic() || c == '-' || c == '_') +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ColumnKind { + Available, + Match, + Source, +} + +fn classify(rows: &[Vec], col: usize) -> ColumnKind { + let values: Vec<&str> = rows.iter().filter_map(|r| r.get(col)).map(String::as_str).filter(|v| !v.is_empty()).collect(); + if values.iter().any(|v| v.contains(':')) { + ColumnKind::Match + } else if !values.is_empty() && values.iter().all(|v| looks_like_source(v)) { + ColumnKind::Source + } else if values.is_empty() { + // Nur leere Zellen: letzte Spalte ist praktisch immer die Quelle. + ColumnKind::Source + } else { + ColumnKind::Available + } +} + +/// Wandelt eine WinGet-Tabelle (`list`, `upgrade`, `search`) in Pakete um. +pub fn parse_packages(text: &str) -> Vec { + let rows = parse_table(text); + if rows.is_empty() { + return Vec::new(); + } + let width = rows.iter().map(Vec::len).max().unwrap_or(0); + let kinds: Vec<(usize, ColumnKind)> = (3..width).map(|c| (c, classify(&rows, c))).collect(); + let available_col = kinds.iter().find(|(_, k)| *k == ColumnKind::Available).map(|(c, _)| *c); + let source_col = kinds.iter().rev().find(|(_, k)| *k == ColumnKind::Source).map(|(c, _)| *c); + + let mut packages: Vec = rows + .iter() + .map(|r| { + let cell = |i: usize| r.get(i).cloned().unwrap_or_default(); + let non_empty = |s: String| if s.is_empty() { None } else { Some(s) }; + let version = cell(2).trim_start_matches(['<', '>', ' ']).to_owned(); + Package::new( + ManagerId::Winget, + cell(1), + cell(0), + version, + available_col.map(cell).and_then(non_empty), + source_col.map(cell).and_then(non_empty), + ) + }) + .collect(); + ensure_unique_keys(&mut packages); + packages +} + +/// Liest die Versionsliste aus `winget show --versions`. +pub fn parse_versions(text: &str) -> Vec { + let lines = clean_lines(text); + let Some(sep) = lines.iter().position(|l| is_separator(l)) else { + return Vec::new(); + }; + lines[sep + 1..] + .iter() + .map(|l| l.trim().to_owned()) + .take_while(|l| !l.is_empty()) + .collect() +} + +fn split_key(line: &str) -> Option<(&str, &str)> { + let idx = line.find(':')?; + let (key, rest) = line.split_at(idx); + let rest = &rest[1..]; + // Schlüssel enthalten keine URL-Teile und sind kurz; URLs wie „https://…“ werden so nicht zerlegt. + if key.is_empty() || key.contains("//") || key.len() > 48 || !(rest.is_empty() || rest.starts_with(' ')) { + return None; + } + Some((key.trim(), rest.trim())) +} + +fn opt(s: &str) -> Option { + if s.is_empty() { None } else { Some(s.to_owned()) } +} + +/// Liest die Ausgabe von `winget show`. Bekannte Bezeichnungen (de/en) werden direkt erkannt; +/// der Installer-Block wird zusätzlich anhand seines Inhalts erkannt. +pub fn parse_details(text: &str) -> PackageDetails { + let mut details = PackageDetails::default(); + let lines = clean_lines(text); + let mut block_key: Option = None; + let mut block_lines: Vec = Vec::new(); + let mut blocks: Vec<(String, Vec)> = Vec::new(); + + let mut flush = |key: &mut Option, lines: &mut Vec| { + if let Some(k) = key.take() { + blocks.push((k, std::mem::take(lines))); + } + }; + + for line in &lines { + if line.trim().is_empty() { + continue; + } + if line.starts_with(' ') || line.starts_with('\t') { + if block_key.is_some() { + block_lines.push(line.trim_end().to_owned()); + } + continue; + } + flush(&mut block_key, &mut block_lines); + let Some((key, value)) = split_key(line) else { continue }; + let k = key.to_lowercase(); + if value.is_empty() { + block_key = Some(k); + continue; + } + match k.as_str() { + "herausgeber" | "publisher" => details.publisher = opt(value), + "startseite" | "homepage" => details.homepage = opt(value), + "lizenz" | "license" => details.license = opt(value), + "lizenz-url" | "license url" => details.license_url = opt(value), + "beschreibung" | "description" => details.description = opt(value), + _ => {} + } + } + flush(&mut block_key, &mut block_lines); + + for (key, body) in blocks { + let is_description = matches!(key.as_str(), "beschreibung" | "description"); + if is_description { + let text = body.iter().map(|l| l.trim()).collect::>().join("\n"); + details.description = opt(text.trim()); + continue; + } + // Installer-Block: Einträge mit URL und Hash erkennen. + let pairs: Vec<(String, String)> = body + .iter() + .filter_map(|l| split_key(l.trim()).map(|(k, v)| (k.to_lowercase(), v.to_owned()))) + .collect(); + for (k, v) in pairs { + if v.is_empty() { + continue; + } + if k.contains("sha256") || k.contains("hash") { + details.hash.get_or_insert(v); + } else if k.contains("url") && v.starts_with("http") { + details.installer_url.get_or_insert(v); + } else if k.contains("typ") || k.contains("type") { + details.installer_type.get_or_insert(v); + } + } + } + details +} + +#[cfg(test)] +mod tests { + use super::*; + + const LIST: &str = include_str!("../../../tests/fixtures/winget_list_de.txt"); + const UPGRADE: &str = include_str!("../../../tests/fixtures/winget_upgrade_de.txt"); + const SEARCH: &str = include_str!("../../../tests/fixtures/winget_search_de.txt"); + const SHOW: &str = include_str!("../../../tests/fixtures/winget_show_de.txt"); + + #[test] + fn upgrade_table_from_real_output() { + let pkgs = parse_packages(UPGRADE); + assert_eq!(pkgs.len(), 4, "Zusammenfassungszeile darf kein Paket ergeben"); + let vlc = pkgs.iter().find(|p| p.id == "VideoLAN.VLC").expect("VLC fehlt"); + assert_eq!(vlc.name, "VLC media player"); + assert_eq!(vlc.version, "3.0.23"); + assert_eq!(vlc.available_version.as_deref(), Some("3.0.24")); + assert_eq!(vlc.source.as_deref(), Some("winget")); + } + + #[test] + fn list_table_from_real_output() { + let pkgs = parse_packages(LIST); + assert!(pkgs.len() > 50, "erwartet viele Pakete, erhalten {}", pkgs.len()); + let arp = pkgs.iter().find(|p| p.id.starts_with("ARP\\")).expect("ARP-Paket fehlt"); + assert!(arp.source.is_none()); + let betterbird = pkgs.iter().filter(|p| p.id == "Betterbird.Betterbird").count(); + assert_eq!(betterbird, 2); + let keys: std::collections::HashSet<_> = pkgs.iter().map(|p| &p.key).collect(); + assert_eq!(keys.len(), pkgs.len(), "Schlüssel müssen eindeutig sein"); + assert!(pkgs.iter().all(|p| !p.id.is_empty() && !p.name.is_empty())); + let anti = pkgs.iter().find(|p| p.id == "Google.Antigravity").expect("Antigravity fehlt"); + assert!(anti.available_version.is_some()); + } + + #[test] + fn search_table_has_match_column() { + let pkgs = parse_packages(SEARCH); + assert!(!pkgs.is_empty()); + let vlc = &pkgs[0]; + assert_eq!(vlc.id, "VideoLAN.VLC"); + assert_eq!(vlc.version, "3.0.24"); + assert_eq!(vlc.available_version, None, "Spalte „Übereinstimmung“ ist keine Version"); + assert_eq!(vlc.source.as_deref(), Some("winget")); + } + + #[test] + fn progress_noise_is_ignored() { + let noisy = format!(" - \r \\ \r | \r\r\n{UPGRADE}"); + assert_eq!(parse_packages(&noisy).len(), 4); + } + + #[test] + fn empty_and_message_only_output() { + assert!(parse_packages("Es wurde kein Paket gefunden, das den Eingabekriterien entspricht.\n").is_empty()); + assert!(parse_packages("").is_empty()); + } + + #[test] + fn wide_characters_keep_columns_aligned() { + // Synthetische Zeile (nicht aus echter Ausgabe): CJK-Zeichen belegen zwei Anzeigezellen. + let text = "Name ID Version Quelle\n\ + ----------------------------------------\n\ + 微信 Tencent.WeChat 3.9.1 winget\n\ + Foo Foo.Bar 1.0 winget\n"; + let pkgs = parse_packages(text); + assert_eq!(pkgs.len(), 2); + assert_eq!(pkgs[0].name, "微信"); + assert_eq!(pkgs[0].id, "Tencent.WeChat"); + assert_eq!(pkgs[1].id, "Foo.Bar"); + } + + #[test] + fn details_from_real_output() { + let d = parse_details(SHOW); + assert_eq!(d.publisher.as_deref(), Some("VideoLAN")); + assert_eq!(d.license.as_deref(), Some("GPL-2.0")); + assert_eq!(d.homepage.as_deref(), Some("https://www.videolan.org/vlc/")); + assert_eq!(d.installer_type.as_deref(), Some("wix")); + assert!(d.installer_url.as_deref().is_some_and(|u| u.ends_with(".msi"))); + assert_eq!(d.hash.as_deref().map(str::len), Some(64)); + assert!(d.description.as_deref().is_some_and(|t| t.starts_with("VLC is a free"))); + } + + #[test] + fn versions_list() { + let text = "Gefunden VLC media player [VideoLAN.VLC]\nVersion\n--------\n3.0.24\n3.0.23\n"; + assert_eq!(parse_versions(text), vec!["3.0.24", "3.0.23"]); + } +} diff --git a/src-tauri/src/managers/winget/ps.rs b/src-tauri/src/managers/winget/ps.rs new file mode 100644 index 0000000..e5add31 --- /dev/null +++ b/src-tauri/src/managers/winget/ps.rs @@ -0,0 +1,128 @@ +//! Zugriff auf das PowerShell-Modul `Microsoft.WinGet.Client` (Ausgabe als JSON). +//! +//! Hinweis: Dieser Pfad ist nur aktiv, wenn das Modul installiert ist. Bei jedem Fehler fällt der +//! WinGet-Manager auf die Kommandozeile zurück. + +use std::path::{Path, PathBuf}; +use std::time::Duration; + +use serde::Deserialize; + +use crate::error::{AppError, AppResult}; +use crate::models::{ManagerId, Package}; +use crate::process; + +const PS_PREAMBLE: &str = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; \ +$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; \ +Import-Module Microsoft.WinGet.Client; "; + +/// Ergebnis der Modulerkennung. +#[derive(Debug, Clone)] +pub struct ModuleInfo { + /// PowerShell-Programm, in dem das Modul gefunden wurde. + pub shell: PathBuf, + pub version: String, +} + +fn shells() -> Vec { + ["pwsh", "powershell"].iter().filter_map(|n| which::which(n).ok()).collect() +} + +/// Sucht das Modul in PowerShell 7 und Windows PowerShell. +pub async fn detect_module() -> Option { + let script = "$m = Get-Module -ListAvailable -Name Microsoft.WinGet.Client | \ + Sort-Object Version -Descending | Select-Object -First 1; \ + if ($m) { $m.Version.ToString() }"; + for shell in shells() { + let Ok(out) = process::run( + &shell, + &["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], + Duration::from_secs(30), + ) + .await + else { + continue; + }; + let version = out.stdout.trim().to_owned(); + if out.code == Some(0) && !version.is_empty() { + return Some(ModuleInfo { shell, version }); + } + } + None +} + +#[derive(Debug, Deserialize)] +struct PsPackage { + id: Option, + name: Option, + version: Option, + available: Option, + update: Option, + source: Option, +} + +async fn run_json(shell: &Path, body: &str, query: &str) -> AppResult> { + let script = format!("{PS_PREAMBLE}{body}"); + let out = process::run_env( + shell, + &["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", &script], + &[("VU_QUERY", query)], + Duration::from_secs(180), + ) + .await?; + if out.code != Some(0) { + return Err(AppError::Command(format!( + "PowerShell-Modul fehlgeschlagen: {}", + out.stderr.lines().next().unwrap_or("unbekannter Fehler") + ))); + } + let text = out.stdout.trim(); + if text.is_empty() { + return Ok(Vec::new()); + } + Ok(serde_json::from_str(text)?) +} + +fn convert(items: Vec, only_updates: bool) -> Vec { + items + .into_iter() + .filter(|p| !only_updates || p.update.unwrap_or(false)) + .filter_map(|p| { + let id = p.id.filter(|s| !s.is_empty())?; + let non_empty = |s: Option| s.filter(|v| !v.is_empty()); + Some(Package::new( + ManagerId::Winget, + id.clone(), + non_empty(p.name).unwrap_or(id), + p.version.unwrap_or_default(), + if only_updates || p.update.unwrap_or(false) { non_empty(p.available) } else { None }, + non_empty(p.source), + )) + }) + .collect() +} + +const INSTALLED_BODY: &str = "$r = @(Get-WinGetPackage | ForEach-Object { [pscustomobject]@{ \ +id = $_.Id; name = $_.Name; version = $_.InstalledVersion; \ +available = ($_.AvailableVersions | Select-Object -First 1); \ +update = [bool]$_.IsUpdateAvailable; source = $_.Source } }); \ +ConvertTo-Json -InputObject $r -Compress -Depth 3"; + +const SEARCH_BODY: &str = "$r = @(Find-WinGetPackage -Query $env:VU_QUERY | ForEach-Object { [pscustomobject]@{ \ +id = $_.Id; name = $_.Name; version = $_.Version; source = $_.Source } }); \ +ConvertTo-Json -InputObject $r -Compress -Depth 3"; + +/// Installierte Pakete über das Modul. +pub async fn list_installed(shell: &Path) -> AppResult> { + Ok(convert(run_json(shell, INSTALLED_BODY, "").await?, false)) +} + +/// Verfügbare Aktualisierungen über das Modul. +pub async fn list_updates(shell: &Path) -> AppResult> { + Ok(convert(run_json(shell, INSTALLED_BODY, "").await?, true)) +} + +/// Suche über das Modul. +pub async fn search(shell: &Path, query: &str) -> AppResult> { + Ok(convert(run_json(shell, SEARCH_BODY, query).await?, false)) +} diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs new file mode 100644 index 0000000..b7c2f64 --- /dev/null +++ b/src-tauri/src/models.rs @@ -0,0 +1,139 @@ +//! Gemeinsame Datentypen, die zwischen Backend und Oberfläche ausgetauscht werden. + +use serde::{Deserialize, Serialize}; + +/// Kennung eines Paketmanagers. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ManagerId { + Winget, + Scoop, + Choco, + Pip, + Npm, + Dotnet, + Psgallery, + Cargo, + Vcpkg, +} + +/// Ein Paket in einer Liste (Suche, installiert, Aktualisierung). +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Package { + /// Stabile Kennung innerhalb der Oberfläche (`manager:id:quelle`). + pub key: String, + /// Paket-ID des Managers. + pub id: String, + /// Anzeigename. + pub name: String, + /// Installierte oder (bei Suche) aktuelle Version. + pub version: String, + /// Verfügbare neue Version, falls vorhanden. + pub available_version: Option, + /// Zugehöriger Manager. + pub manager: ManagerId, + /// Quelle (z. B. `winget`, `msstore`). + pub source: Option, +} + +impl Package { + /// Erzeugt ein Paket und berechnet den eindeutigen Schlüssel. + pub fn new( + manager: ManagerId, + id: String, + name: String, + version: String, + available_version: Option, + source: Option, + ) -> Self { + let key = format!( + "{}:{}:{}:{}", + serde_json::to_value(manager) + .ok() + .and_then(|v| v.as_str().map(str::to_owned)) + .unwrap_or_default(), + id, + source.as_deref().unwrap_or(""), + version + ); + Self { key, id, name, version, available_version, manager, source } + } +} + +/// Macht die Schlüssel einer Liste eindeutig (z. B. bei Paketen mit gleicher ID, aber verschiedenem Namen). +pub fn ensure_unique_keys(packages: &mut [Package]) { + let mut seen = std::collections::HashSet::new(); + for pkg in packages.iter_mut() { + let mut candidate = pkg.key.clone(); + let mut n = 2; + while !seen.insert(candidate.clone()) { + candidate = format!("{}#{n}", pkg.key); + n += 1; + } + pkg.key = candidate; + } +} + +/// Detailinformationen zu einem Paket. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PackageDetails { + pub description: Option, + pub publisher: Option, + pub license: Option, + pub license_url: Option, + pub homepage: Option, + pub installer_url: Option, + pub installer_type: Option, + pub hash: Option, + pub versions: Vec, +} + +/// Ergebnis der Erkennung eines Paketmanagers. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ManagerInfo { + pub id: ManagerId, + pub name: String, + /// Ob die ausführbare Datei gefunden wurde. + pub available: bool, + pub path: Option, + pub version: Option, + /// Zusatzkomponenten, z. B. das PowerShell-Modul bei WinGet. + pub extras: Vec, +} + +/// Eine optionale Zusatzkomponente eines Managers. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExtraInfo { + pub name: String, + pub available: bool, + pub version: Option, +} + +/// Eine Paketquelle. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Source { + pub name: String, + pub url: String, + pub explicit: bool, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn duplicate_keys_are_made_unique() { + let make = |name: &str| { + Package::new(ManagerId::Winget, "A.B".into(), name.into(), "1.0".into(), None, Some("winget".into())) + }; + let mut list = vec![make("x"), make("y"), make("z")]; + ensure_unique_keys(&mut list); + let keys: std::collections::HashSet<_> = list.iter().map(|p| p.key.clone()).collect(); + assert_eq!(keys.len(), 3); + } +} diff --git a/src-tauri/src/process.rs b/src-tauri/src/process.rs new file mode 100644 index 0000000..a6d74ed --- /dev/null +++ b/src-tauri/src/process.rs @@ -0,0 +1,68 @@ +//! Start externer Prozesse ohne Konsolenfenster, mit Zeitlimit und UTF-8-Dekodierung. + +use std::path::Path; +use std::process::Stdio; +use std::time::Duration; + +use tokio::process::Command; + +use crate::error::{AppError, AppResult}; + +/// Ergebnis eines beendeten Prozesses. +#[derive(Debug, Clone)] +pub struct CmdOutput { + /// Exit-Code (`None`, wenn der Prozess beendet wurde). + pub code: Option, + pub stdout: String, + pub stderr: String, +} + +/// Dekodiert Prozessausgabe als UTF-8 und entfernt ein führendes BOM. +pub fn decode(bytes: &[u8]) -> String { + let text = String::from_utf8_lossy(bytes); + text.trim_start_matches('\u{feff}').to_owned() +} + +/// Erzeugt ein Kommando, das unter Windows kein Konsolenfenster öffnet. +pub fn command(program: &Path) -> Command { + let mut cmd = Command::new(program); + #[cfg(windows)] + { + const CREATE_NO_WINDOW: u32 = 0x0800_0000; + cmd.creation_flags(CREATE_NO_WINDOW); + } + cmd.kill_on_drop(true) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + cmd +} + +/// Führt ein Programm aus und wartet höchstens `timeout`. +pub async fn run(program: &Path, args: &[&str], timeout: Duration) -> AppResult { + run_env(program, args, &[], timeout).await +} + +/// Wie [`run`], setzt zusätzlich Umgebungsvariablen (z. B. um Benutzereingaben sicher zu übergeben). +pub async fn run_env( + program: &Path, + args: &[&str], + envs: &[(&str, &str)], + timeout: Duration, +) -> AppResult { + let mut cmd = command(program); + cmd.args(args).envs(envs.iter().copied()); + tracing::debug!(program = %program.display(), ?args, "Prozess wird gestartet"); + let child = cmd.spawn().map_err(|source| AppError::Spawn { + program: program.display().to_string(), + source, + })?; + let output = tokio::time::timeout(timeout, child.wait_with_output()) + .await + .map_err(|_| AppError::Timeout(timeout.as_secs()))??; + Ok(CmdOutput { + code: output.status.code(), + stdout: decode(&output.stdout), + stderr: decode(&output.stderr), + }) +} diff --git a/src-tauri/src/settings/mod.rs b/src-tauri/src/settings/mod.rs new file mode 100644 index 0000000..b59ef7c --- /dev/null +++ b/src-tauri/src/settings/mod.rs @@ -0,0 +1,73 @@ +//! Persistenter Zustand unter `%APPDATA%\VitosUpdater` (Phase 1: Fensterposition und -größe). + +use std::path::{Path, PathBuf}; + +use serde::{Deserialize, Serialize}; + +/// Gespeicherte Fenstergeometrie in physischen Pixeln. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct WindowState { + pub x: i32, + pub y: i32, + pub width: u32, + pub height: u32, + pub maximized: bool, +} + +fn file(dir: &Path) -> PathBuf { + dir.join("window.json") +} + +/// Lädt die gespeicherte Fenstergeometrie. Fehlt die Datei oder ist sie defekt, ist das Ergebnis `None`. +pub fn load_window_state(dir: &Path) -> Option { + let text = std::fs::read_to_string(file(dir)).ok()?; + serde_json::from_str(&text).ok() +} + +/// Speichert die Fenstergeometrie atomar (Schreiben in temporäre Datei, dann Umbenennen). +pub fn save_window_state(dir: &Path, state: &WindowState) -> std::io::Result<()> { + std::fs::create_dir_all(dir)?; + let tmp = dir.join("window.json.tmp"); + std::fs::write(&tmp, serde_json::to_string_pretty(state)?)?; + std::fs::rename(tmp, file(dir)) +} + +/// Prüft, ob ein Fensterrechteck ein Monitorrechteck `(x, y, breite, höhe)` ausreichend überlappt. +pub fn is_visible_on(state: &WindowState, monitors: &[(i32, i32, u32, u32)]) -> bool { + const MIN_VISIBLE: i64 = 120; + monitors.iter().any(|&(mx, my, mw, mh)| { + let left = i64::from(state.x.max(mx)); + let top = i64::from(state.y.max(my)); + let right = (i64::from(state.x) + i64::from(state.width)).min(i64::from(mx) + i64::from(mw)); + let bottom = (i64::from(state.y) + i64::from(state.height)).min(i64::from(my) + i64::from(mh)); + right - left >= MIN_VISIBLE && bottom - top >= MIN_VISIBLE + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn state(x: i32, y: i32) -> WindowState { + WindowState { x, y, width: 1280, height: 800, maximized: false } + } + + #[test] + fn visible_on_primary_monitor() { + assert!(is_visible_on(&state(100, 100), &[(0, 0, 1920, 1080)])); + } + + #[test] + fn offscreen_after_monitor_removed() { + assert!(!is_visible_on(&state(2500, 100), &[(0, 0, 1920, 1080)])); + } + + #[test] + fn roundtrip() { + let dir = std::env::temp_dir().join("vitosupdater-test-window"); + let s = state(10, 20); + save_window_state(&dir, &s).unwrap(); + assert_eq!(load_window_state(&dir), Some(s)); + let _ = std::fs::remove_dir_all(dir); + } +} diff --git a/src-tauri/src/updater/mod.rs b/src-tauri/src/updater/mod.rs new file mode 100644 index 0000000..6da3de9 --- /dev/null +++ b/src-tauri/src/updater/mod.rs @@ -0,0 +1,430 @@ +//! Selbst-Update von VitosUpdater über `tauri-plugin-updater` (statische `latest.json` auf Gitea). + +pub mod policy; + +use std::error::Error as StdError; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Mutex, MutexGuard}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use serde::{Deserialize, Serialize}; +use tauri::ipc::Channel; +use tauri::{AppHandle, Emitter, Manager, State}; +use tauri_plugin_notification::NotificationExt; +use tauri_plugin_updater::{Update, UpdaterExt}; +use tokio::sync::Notify; + +use policy::{Offer, UpdaterSettings}; + +const SETTINGS_FILE: &str = "updater.json"; +const NOTICE_FILE: &str = "update-notice.json"; +const MAIN_WINDOW: &str = "main"; + +/// Öffentliche Angaben zu einem verfügbaren Update. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateInfo { + pub version: String, + pub current_version: String, + /// Veröffentlichungsdatum als Unix-Sekunden. + pub date: Option, + /// Release-Notizen (Markdown). + pub notes: String, + /// Größe des Installers in Bytes, falls in `latest.json` angegeben. + pub size: Option, +} + +/// Ergebnis einer Update-Prüfung. +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "status", rename_all = "camelCase")] +pub enum CheckOutcome { + Available { info: UpdateInfo }, + Skipped { info: UpdateInfo }, + Snoozed { info: UpdateInfo }, + UpToDate, +} + +/// Fortschrittsereignisse der Installation. +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "stage", rename_all = "camelCase", rename_all_fields = "camelCase")] +pub enum InstallEvent { + Started { total: Option }, + Progress { downloaded: u64, total: Option }, + Installing, +} + +/// Zustand für die Oberfläche. +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdaterStatus { + pub current_version: String, + pub settings: UpdaterSettings, + pub last_check: Option, + pub last_error: Option, + /// Aktuell anzubietendes Update (nicht übersprungen/zurückgestellt). + pub offer: Option, +} + +/// Hinweis, der nach dem Neustart den erfolgreichen Wechsel anzeigt. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateNotice { + pub version: String, + pub notes: String, +} + +struct Inner { + settings: UpdaterSettings, + pending: Option, + last_check: Option, + last_error: Option, + notified_version: Option, +} + +/// Gemeinsamer Zustand des Updaters. +pub struct UpdaterState { + dir: PathBuf, + inner: Mutex, + check_lock: tokio::sync::Mutex<()>, + wake: Notify, + started: AtomicBool, +} + +fn now_secs() -> i64 { + SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |d| i64::try_from(d.as_secs()).unwrap_or(i64::MAX)) +} + +fn write_atomic(path: &Path, text: &str) -> std::io::Result<()> { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + let tmp = path.with_extension("json.tmp"); + std::fs::write(&tmp, text)?; + std::fs::rename(tmp, path) +} + +/// Verkettet eine Fehlerkette zu einem Text. +fn error_chain(err: &dyn StdError) -> String { + let mut text = err.to_string(); + let mut source = err.source(); + while let Some(s) = source { + text.push_str(": "); + text.push_str(&s.to_string()); + source = s.source(); + } + text +} + +fn info_of(update: &Update) -> UpdateInfo { + // Die Größe steht als zusätzliches Feld am Plattformeintrag, dessen URL zum Download passt. + let size = update + .raw_json + .get("platforms") + .and_then(|p| p.as_object()) + .and_then(|platforms| { + platforms + .values() + .find(|p| p.get("url").and_then(|u| u.as_str()) == Some(update.download_url.as_str())) + }) + .and_then(|p| p.get("size")) + .and_then(serde_json::Value::as_u64); + UpdateInfo { + version: update.version.clone(), + current_version: update.current_version.clone(), + date: update.date.map(|d| d.unix_timestamp()), + notes: update.body.clone().unwrap_or_default(), + size, + } +} + +impl UpdaterState { + /// Lädt gespeicherte Einstellungen aus dem Datenverzeichnis. + pub fn new(dir: PathBuf) -> Self { + let settings = std::fs::read_to_string(dir.join(SETTINGS_FILE)) + .ok() + .and_then(|t| serde_json::from_str::(&t).ok()) + .unwrap_or_default() + .sanitized(); + Self { + dir, + inner: Mutex::new(Inner { settings, pending: None, last_check: None, last_error: None, notified_version: None }), + check_lock: tokio::sync::Mutex::new(()), + wake: Notify::new(), + started: AtomicBool::new(false), + } + } + + fn lock(&self) -> MutexGuard<'_, Inner> { + self.inner.lock().unwrap_or_else(|e| e.into_inner()) + } + + fn persist(&self, settings: &UpdaterSettings) { + match serde_json::to_string_pretty(settings) { + Ok(text) => { + if let Err(err) = write_atomic(&self.dir.join(SETTINGS_FILE), &text) { + tracing::warn!(%err, "Updater-Einstellungen konnten nicht gespeichert werden"); + } + } + Err(err) => tracing::warn!(%err, "Updater-Einstellungen konnten nicht serialisiert werden"), + } + } + + fn status(&self, current_version: &str) -> UpdaterStatus { + let inner = self.lock(); + UpdaterStatus { + current_version: current_version.to_owned(), + settings: inner.settings.clone(), + last_check: inner.last_check, + last_error: inner.last_error.clone(), + offer: Self::offer_of(&inner), + } + } + + fn offer_of(inner: &Inner) -> Option { + let update = inner.pending.as_ref()?; + (policy::decide(&inner.settings, &update.version, now_secs(), false) == Offer::Show).then(|| info_of(update)) + } +} + +fn main_window_visible(app: &AppHandle) -> bool { + app.get_webview_window(MAIN_WINDOW) + .is_some_and(|w| w.is_visible().unwrap_or(false) && !w.is_minimized().unwrap_or(false)) +} + +/// Meldet ein neues Update: Popup-Ereignis bei sichtbarem Fenster, sonst einmalig eine Windows-Benachrichtigung. +fn announce(app: &AppHandle, info: &UpdateInfo) { + if main_window_visible(app) { + if let Err(err) = app.emit("update-available", info) { + tracing::warn!(%err, "Ereignis „update-available“ konnte nicht gesendet werden"); + } + return; + } + let state = app.state::(); + { + let mut inner = state.lock(); + if inner.notified_version.as_deref() == Some(info.version.as_str()) { + return; + } + inner.notified_version = Some(info.version.clone()); + } + let result = app + .notification() + .builder() + .title("Update verfügbar") + .body(format!("VitosUpdater {} steht bereit. Zum Installieren die Anwendung öffnen.", info.version)) + .show(); + if let Err(err) = result { + tracing::warn!(%err, "Benachrichtigung konnte nicht angezeigt werden"); + } +} + +async fn run_check(app: &AppHandle, manual: bool) -> Result { + let state = app.state::(); + let _guard = state.check_lock.lock().await; + + let restart_handle = app.clone(); + let checked = async { + let updater = app + .updater_builder() + .on_before_exit(move || { + let _ = restart_handle.emit("update-restarting", ()); + }) + .build() + .map_err(|e| error_chain(&e))?; + updater.check().await.map_err(|e| error_chain(&e)) + } + .await; + + let now = now_secs(); + let mut inner = state.lock(); + inner.last_check = Some(now); + match checked { + Ok(Some(update)) if policy::is_newer(&update.version, &update.current_version) => { + inner.last_error = None; + let info = info_of(&update); + let offer = policy::decide(&inner.settings, &update.version, now, manual); + inner.pending = Some(update); + drop(inner); + tracing::info!(version = %info.version, ?offer, manual, "Update gefunden"); + Ok(match offer { + Offer::Show => { + if !manual { + announce(app, &info); + } + CheckOutcome::Available { info } + } + Offer::Skipped => CheckOutcome::Skipped { info }, + Offer::Snoozed => CheckOutcome::Snoozed { info }, + }) + } + Ok(_) => { + inner.last_error = None; + inner.pending = None; + tracing::info!("Kein neueres Update verfügbar"); + Ok(CheckOutcome::UpToDate) + } + Err(raw) => { + let text = policy::friendly_error(&raw); + inner.last_error = Some(text.clone()); + tracing::warn!(error = %raw, "Update-Prüfung fehlgeschlagen"); + Err(text) + } + } +} + +async fn scheduler(app: AppHandle) { + let _ = run_check(&app, false).await; + loop { + let state = app.state::(); + let minutes = state.lock().settings.check_interval_minutes; + tokio::select! { + () = tokio::time::sleep(Duration::from_secs(u64::from(minutes) * 60)) => { + let _ = run_check(&app, false).await; + } + // Intervall wurde geändert: Wartezeit mit dem neuen Wert neu beginnen. + () = state.wake.notified() => {} + } + } +} + +/// Wird von der Oberfläche aufgerufen, sobald sie bereit ist; startet die zyklische Prüfung (einmalig). +#[tauri::command] +pub fn updater_ready(app: AppHandle, state: State<'_, UpdaterState>) -> UpdaterStatus { + if !state.started.swap(true, Ordering::SeqCst) { + tauri::async_runtime::spawn(scheduler(app.clone())); + } + state.status(&app.package_info().version.to_string()) +} + +/// Liefert Zustand und Einstellungen des Updaters. +#[tauri::command] +pub fn get_updater_status(app: AppHandle, state: State<'_, UpdaterState>) -> UpdaterStatus { + state.status(&app.package_info().version.to_string()) +} + +/// Liefert das aktuell anzubietende Update, falls vorhanden (z. B. nachdem das Fenster wieder sichtbar wurde). +#[tauri::command] +pub fn get_update_offer(state: State<'_, UpdaterState>) -> Option { + UpdaterState::offer_of(&state.lock()) +} + +/// Prüft sofort auf Updates. Manuelle Prüfungen ignorieren „Überspringen“ und „Später erinnern“. +#[tauri::command] +pub async fn check_for_update(app: AppHandle, manual: bool) -> Result { + run_check(&app, manual).await +} + +/// Stellt das gefundene Update für vier Stunden zurück. +#[tauri::command] +pub fn snooze_update(state: State<'_, UpdaterState>) { + let mut inner = state.lock(); + if let Some(version) = inner.pending.as_ref().map(|u| u.version.clone()) { + inner.settings.snoozed_version = Some(version); + inner.settings.snoozed_until = Some(now_secs() + policy::SNOOZE_SECONDS); + let settings = inner.settings.clone(); + drop(inner); + state.persist(&settings); + } +} + +/// Überspringt die gefundene Version dauerhaft (neuere Versionen werden weiterhin angeboten). +#[tauri::command] +pub fn skip_update(state: State<'_, UpdaterState>) { + let mut inner = state.lock(); + if let Some(version) = inner.pending.as_ref().map(|u| u.version.clone()) { + inner.settings.skipped_version = Some(version); + let settings = inner.settings.clone(); + drop(inner); + state.persist(&settings); + } +} + +/// Hebt „Diese Version überspringen“ wieder auf. +#[tauri::command] +pub fn clear_skipped_version(state: State<'_, UpdaterState>) -> UpdaterSettings { + let mut inner = state.lock(); + inner.settings.skipped_version = None; + inner.settings.snoozed_version = None; + inner.settings.snoozed_until = None; + let settings = inner.settings.clone(); + drop(inner); + state.persist(&settings); + settings +} + +/// Ändert das Prüfintervall (in Minuten, wird begrenzt) und startet die Wartezeit neu. +#[tauri::command] +pub fn set_update_interval(state: State<'_, UpdaterState>, minutes: u32) -> UpdaterSettings { + let mut inner = state.lock(); + inner.settings.check_interval_minutes = minutes; + inner.settings = inner.settings.clone().sanitized(); + let settings = inner.settings.clone(); + drop(inner); + state.persist(&settings); + state.wake.notify_one(); + settings +} + +/// Lädt das Update herunter, prüft die Signatur, installiert es und startet die Anwendung neu. +#[tauri::command] +pub async fn install_update( + app: AppHandle, + state: State<'_, UpdaterState>, + on_event: Channel, +) -> Result<(), String> { + let update = state.lock().pending.clone().ok_or_else(|| "Es liegt kein Update zur Installation vor.".to_owned())?; + let notice = UpdateNotice { version: update.version.clone(), notes: update.body.clone().unwrap_or_default() }; + let notice_path = state.dir.join(NOTICE_FILE); + if let Ok(text) = serde_json::to_string(¬ice) { + if let Err(err) = write_atomic(¬ice_path, &text) { + tracing::warn!(%err, "Update-Hinweis konnte nicht gespeichert werden"); + } + } + + let mut downloaded: u64 = 0; + let mut started = false; + let progress = on_event.clone(); + let finished = on_event.clone(); + tracing::info!(version = %update.version, "Update wird heruntergeladen und installiert"); + let result = update + .download_and_install( + move |chunk, total| { + if !started { + started = true; + let _ = progress.send(InstallEvent::Started { total }); + } + downloaded += chunk as u64; + let _ = progress.send(InstallEvent::Progress { downloaded, total }); + }, + move || { + let _ = finished.send(InstallEvent::Installing); + }, + ) + .await; + + match result { + // Unter Windows beendet das Plugin die Anwendung selbst; der Installer startet sie neu. + Ok(()) => app.restart(), + Err(err) => { + let _ = std::fs::remove_file(¬ice_path); + let raw = error_chain(&err); + tracing::error!(error = %raw, "Update-Installation fehlgeschlagen"); + let text = policy::friendly_error(&raw); + state.lock().last_error = Some(text.clone()); + Err(text) + } + } +} + +/// Liefert (und löscht) den Hinweis auf ein soeben installiertes Update, wenn die laufende Version dazu passt. +#[tauri::command] +pub fn take_update_notice(app: AppHandle, state: State<'_, UpdaterState>) -> Option { + let path = state.dir.join(NOTICE_FILE); + let notice = std::fs::read_to_string(&path).ok().and_then(|t| serde_json::from_str::(&t).ok()); + if notice.is_some() || path.exists() { + let _ = std::fs::remove_file(&path); + } + let current = app.package_info().version.to_string(); + notice.filter(|n| n.version == current) +} diff --git a/src-tauri/src/updater/policy.rs b/src-tauri/src/updater/policy.rs new file mode 100644 index 0000000..aa8aa07 --- /dev/null +++ b/src-tauri/src/updater/policy.rs @@ -0,0 +1,190 @@ +//! Reine Entscheidungslogik des Selbst-Updaters (ohne Netzwerk, daher gut testbar). + +use serde::{Deserialize, Serialize}; + +/// Dauer, für die „Später erinnern“ ein Update zurückstellt (4 Stunden). +pub const SNOOZE_SECONDS: i64 = 4 * 60 * 60; +/// Standardintervall der Update-Prüfung in Minuten. +pub const DEFAULT_INTERVAL_MINUTES: u32 = 30; +/// Kleinstes erlaubtes Prüfintervall in Minuten. +pub const MIN_INTERVAL_MINUTES: u32 = 5; +/// Größtes erlaubtes Prüfintervall in Minuten (eine Woche). +pub const MAX_INTERVAL_MINUTES: u32 = 7 * 24 * 60; + +/// Gespeicherte Einstellungen des Selbst-Updaters. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +pub struct UpdaterSettings { + /// Prüfintervall in Minuten. + pub check_interval_minutes: u32, + /// Version, die der Nutzer übersprungen hat. + pub skipped_version: Option, + /// Version, die zurückgestellt wurde. + pub snoozed_version: Option, + /// Zeitpunkt (Unix-Sekunden), bis zu dem zurückgestellt wurde. + pub snoozed_until: Option, +} + +impl Default for UpdaterSettings { + fn default() -> Self { + Self { + check_interval_minutes: DEFAULT_INTERVAL_MINUTES, + skipped_version: None, + snoozed_version: None, + snoozed_until: None, + } + } +} + +impl UpdaterSettings { + /// Begrenzt das Intervall auf den erlaubten Bereich. + pub fn sanitized(mut self) -> Self { + self.check_interval_minutes = self.check_interval_minutes.clamp(MIN_INTERVAL_MINUTES, MAX_INTERVAL_MINUTES); + self + } +} + +/// Entscheidung, ob eine gefundene Version angeboten wird. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Offer { + /// Popup zeigen. + Show, + /// Vom Nutzer übersprungen. + Skipped, + /// Noch zurückgestellt. + Snoozed, +} + +/// Prüft per SemVer, ob `remote` neuer als `current` ist. Ungültige Versionen gelten nie als neuer. +pub fn is_newer(remote: &str, current: &str) -> bool { + match (semver::Version::parse(remote.trim_start_matches('v')), semver::Version::parse(current.trim_start_matches('v'))) { + (Ok(r), Ok(c)) => r > c, + _ => false, + } +} + +/// Entscheidet anhand der Einstellungen, ob `version` angeboten wird. Manuelle Prüfungen bieten immer an. +pub fn decide(settings: &UpdaterSettings, version: &str, now: i64, manual: bool) -> Offer { + if manual { + return Offer::Show; + } + if settings.skipped_version.as_deref() == Some(version) { + return Offer::Skipped; + } + if settings.snoozed_version.as_deref() == Some(version) && settings.snoozed_until.is_some_and(|until| until > now) { + return Offer::Snoozed; + } + Offer::Show +} + +/// Art eines Update-Fehlers, abgeleitet aus der Fehlermeldung. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ErrorKind { + Certificate, + NotFound, + Network, + Signature, + Other, +} + +/// Ordnet eine technische Fehlermeldung einer Fehlerart zu. +pub fn classify_error(message: &str) -> ErrorKind { + let m = message.to_lowercase(); + const CERT: [&str; 9] = [ + "certificate", + "schannel", + "sec_e_", + "0x80090325", + "0x80090326", + "unknown ca", + "untrusted", + "invalid peer", + "ssl", + ]; + if CERT.iter().any(|p| m.contains(p)) { + ErrorKind::Certificate + } else if m.contains("signature") || m.contains("minisign") || m.contains("signed version") { + ErrorKind::Signature + } else if m.contains("404") || m.contains("not found") { + ErrorKind::NotFound + } else if ["error sending request", "dns", "connect", "timed out", "timeout", "network", "unreachable"] + .iter() + .any(|p| m.contains(p)) + { + ErrorKind::Network + } else { + ErrorKind::Other + } +} + +/// Deutsche, für die Oberfläche bestimmte Fehlermeldung. +pub fn friendly_error(message: &str) -> String { + match classify_error(message) { + ErrorKind::Certificate => "Die sichere Verbindung zum Update-Server wurde abgelehnt, weil Windows dem Zertifikat nicht vertraut. \ + Das Zertifikat der Zertifizierungsstelle muss in Windows als vertrauenswürdig installiert werden (siehe README, Abschnitt „Zertifikat“)." + .to_owned(), + ErrorKind::NotFound => "Die Update-Beschreibung (latest.json) wurde auf dem Server nicht gefunden. Möglicherweise ist noch kein Release veröffentlicht." + .to_owned(), + ErrorKind::Network => "Der Update-Server ist nicht erreichbar. Bitte die Netzwerkverbindung prüfen.".to_owned(), + ErrorKind::Signature => "Die Signatur des Updates ist ungültig. Das Update wurde aus Sicherheitsgründen nicht installiert.".to_owned(), + ErrorKind::Other => format!("Die Update-Prüfung ist fehlgeschlagen: {message}"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn newer_uses_semver_not_string_order() { + assert!(is_newer("0.10.0", "0.9.0")); + assert!(is_newer("v1.0.0", "0.9.9")); + assert!(!is_newer("0.9.0", "0.10.0"), "Downgrade darf nie angeboten werden"); + assert!(!is_newer("1.0.0", "1.0.0")); + assert!(!is_newer("1.0.0-beta.1", "1.0.0")); + assert!(!is_newer("kaputt", "1.0.0")); + } + + #[test] + fn skipped_version_is_not_offered_but_newer_is() { + let s = UpdaterSettings { skipped_version: Some("1.2.0".into()), ..Default::default() }; + assert_eq!(decide(&s, "1.2.0", 0, false), Offer::Skipped); + assert_eq!(decide(&s, "1.3.0", 0, false), Offer::Show); + assert_eq!(decide(&s, "1.2.0", 0, true), Offer::Show, "manuelle Prüfung zeigt immer an"); + } + + #[test] + fn snooze_expires() { + let s = UpdaterSettings { snoozed_version: Some("2.0.0".into()), snoozed_until: Some(1_000), ..Default::default() }; + assert_eq!(decide(&s, "2.0.0", 999, false), Offer::Snoozed); + assert_eq!(decide(&s, "2.0.0", 1_000, false), Offer::Show); + assert_eq!(decide(&s, "2.1.0", 10, false), Offer::Show); + } + + #[test] + fn interval_is_clamped() { + let low = UpdaterSettings { check_interval_minutes: 0, ..Default::default() }.sanitized(); + let high = UpdaterSettings { check_interval_minutes: u32::MAX, ..Default::default() }.sanitized(); + assert_eq!(low.check_interval_minutes, MIN_INTERVAL_MINUTES); + assert_eq!(high.check_interval_minutes, MAX_INTERVAL_MINUTES); + } + + #[test] + fn errors_are_classified() { + assert_eq!(classify_error("error sending request: invalid peer certificate: UnknownIssuer"), ErrorKind::Certificate); + assert_eq!(classify_error("SEC_E_UNTRUSTED_ROOT (0x80090325)"), ErrorKind::Certificate); + assert_eq!(classify_error("Download request failed with status: 404 Not Found"), ErrorKind::NotFound); + assert_eq!(classify_error("error sending request for url: dns error"), ErrorKind::Network); + assert_eq!(classify_error("minisign: signature verification failed"), ErrorKind::Signature); + assert!(friendly_error("unknown ca").contains("Zertifikat")); + } + + #[test] + fn settings_tolerate_missing_fields() { + let s: UpdaterSettings = serde_json::from_str("{\"checkIntervalMinutes\": 60}").unwrap(); + assert_eq!(s.check_interval_minutes, 60); + assert!(s.skipped_version.is_none()); + let empty: UpdaterSettings = serde_json::from_str("{}").unwrap(); + assert_eq!(empty, UpdaterSettings::default()); + } +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..0868fa7 --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "VitosUpdater", + "version": "0.1.0", + "identifier": "de.vitos.updater", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "../dist" + }, + "app": { + "withGlobalTauri": false, + "windows": [ + { + "label": "main", + "title": "VitosUpdater", + "width": 1280, + "height": 800, + "minWidth": 960, + "minHeight": 600, + "visible": false, + "resizable": true, + "center": true + } + ], + "security": { + "csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: asset: http://asset.localhost; connect-src 'self' ipc: http://ipc.localhost" + } + }, + "bundle": { + "active": true, + "targets": ["nsis"], + "createUpdaterArtifacts": true, + "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.ico"], + "windows": { + "nsis": { "languages": ["German"], "displayLanguageSelector": false, "installMode": "currentUser" } + } + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDNFRDA4NTBFNEM1MjE3OUYKUldTZkYxSk1Eb1hRUGdEV0lycFF4SnZ4SHBmZm5EVzVXemZNd0VUd2hsSUI4SllIMThrcTV0QUoK", + "endpoints": ["https://gitea.davito.synology.me/Davito/VitosUpdater/releases/download/latest/latest.json"], + "requireSignedVersion": true, + "windows": { "installMode": "passive" } + } + } +} diff --git a/src-tauri/tests/fixtures/winget_list_de.txt b/src-tauri/tests/fixtures/winget_list_de.txt new file mode 100644 index 0000000..f39e9d7 --- /dev/null +++ b/src-tauri/tests/fixtures/winget_list_de.txt @@ -0,0 +1,133 @@ +Name ID Version Verfügbar Quelle +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +AMD Chipset Software ARP\Machine\X86\AMD_Chipset_IODrivers 8.09.28.1124 +Antigravity 2.25.0 Google.Antigravity 2.25.0 2.28.2 winget +App-Installer Microsoft.AppInstaller 1.29.380.0 winget +Apple Application Support (64-Bit) Apple.AppleApplicationSupport.x64 8.7 winget +AV1 Video Extension MSIX\Microsoft.AV1VideoExtension_2.0.35.0_x64__8wekyb3d8bbwe 2.0.35.0 +AVC Encoder-Videoerweiterung MSIX\Microsoft.AVCEncoderVideoExtension_1.1.47.0_x64__8wekyb3d8bbwe 1.1.47.0 +Betterbird (x64 de) Betterbird.Betterbird 153.5.0 winget +Betterbird (x64 en-US) Betterbird.Betterbird 153.6.0 winget +Bloons TD 6 ARP\Machine\X64\Steam App 960090 Unknown +Ccache Ccache.Ccache 4.14.1 winget +Composite Blur plugin for OBS ARP\Machine\X64\{186038F5-DE78-439D-B727-1D9B43E3701F} 1.6.1 +Corsair Device Control Service Bundle ARP\Machine\X86\{dffe1772-7f42-4dd2-ae7c-f07b34d720be} 1.9.9 +Deezer 8.0.10 Deezer.Deezer 8.0.10 winget +Deno DenoLand.Deno 2.9.8 winget +Deutsch Local Experience Pack MSIX\Microsoft.LanguageExperiencePackde-DE_26100.169.270.0_neutral__8wekyb3d8bbwe 26100.169.270.0 +Dev Home MSIX\Microsoft.Windows.DevHome_0.0.0.0_x64__8wekyb3d8bbwe 0.0.0.0 +Discord ARP\User\X64\Discord 1.0.9266 +EarTrumpet MSIX\40459File-New-Project.EarTrumpet_2.3.0.0_x86__1sdd7yawvg6ne 2.3.0.0 +eM Client MSIX\eMClient.20054CA46072C_11.0.1201.0_x64__veq4nz3ze5v72 11.0.1201.0 +EuerChat ARP\Machine\X64\EuerChat 1.2.10 +FFmpeg Gyan.FFmpeg 9.0.2 winget +Game Bar MSIX\Microsoft.XboxGamingOverlay_7.326.10141.0_x64__8wekyb3d8bbwe 7.326.10141.0 +GBT_Dynamic_Lighting_Lib_UC 25.07.21.01 ARP\Machine\X64\GBT_Dynamic_Lighting_Lib_UC 25.07.21.01 +GBT_MB_Update ARP\Machine\X64\GBT_MB_Update 26.07.22.01 +GBT_RGB_Sync_Control 26.08.31.01 ARP\Machine\X64\GBT_RGB_Sync_Control 26.08.31.01 +GBT_rgbMotherboard_UC 26.08.27.01 ARP\Machine\X64\GBT_rgbMotherboard_UC 26.08.27.01 +GIGABYTE Control Center 26.08.28.01 ARP\Machine\X64\GIGABYTE Control Center 26.08.28.01 +GIGABYTE Performance Library ARP\Machine\X64\MBEasyTune 26.08.06.01 +GIGABYTE Storage Library ARP\Machine\X64\MBStorage 26.09.18.01 +GigabyteControlCenter MSIX\65d483df-b37e-4fcf-94de-8b795233db63_25.1.23.0_x64__1mmjbktjj1mkp 25.1.23.0 +Git Git.Git 2.56.0.4 winget +Google Chrome ARP\Machine\X86\Google Chrome 156.0.8078.80 +HEIF-Bilderweiterung MSIX\Microsoft.HEIFImageExtension_1.2.62.0_x64__8wekyb3d8bbwe 1.2.62.0 +HEVC-Videoerweiterungen vom Gerätehersteller MSIX\Microsoft.HEVCVideoExtension_2.5.48.0_x64__8wekyb3d8bbwe 2.5.48.0 +Host der Store-Benutzeroberfläche MSIX\Microsoft.StorePurchaseApp_22608.1401.1.0_x64__8wekyb3d8bbwe 22608.1401.1.0 +Microsoft .Net Native Framework Package 2.2 MSIX\Microsoft.NET.Native.Framework.2.2_2.2.29512.0_x64__8wekyb3d8bbwe 2.2.29512.0 +Microsoft .Net Native Framework Package 2.2 MSIX\Microsoft.NET.Native.Framework.2.2_2.2.29512.0_x86__8wekyb3d8bbwe 2.2.29512.0 +Microsoft .Net Native Runtime Package 2.2 Microsoft.DotNet.Native.Runtime 2.2.28604.0 winget +Microsoft .Net Native Runtime Package 2.2 Microsoft.DotNet.Native.Runtime 2.2.28604.0 winget +Microsoft Edge Microsoft.Edge 156.0.4314.50 156.0.4314.56 winget +Microsoft Edge MSIX\Microsoft.MicrosoftEdge.Stable_155.0.4283.45_neutral__8wekyb3d8bbwe 155.0.4283.45 +Microsoft Fotos MSIX\Microsoft.Windows.Photos_2026.11090.22001.0_x64__8wekyb3d8bbwe 2026.11090.22001.0 +Microsoft OneDrive Microsoft.OneDrive 26.189.0928.0009 winget +Microsoft Store MSIX\Microsoft.WindowsStore_22609.1401.8.0_x64__8wekyb3d8bbwe 22609.1401.8.0 +Microsoft Visual C++ 2010 x64 Redistributable - 10.0.40219 Microsoft.VCRedist.2010.x64 10.0.40219 winget +Microsoft Visual C++ 2010 x86 Redistributable - 10.0.40219 Microsoft.VCRedist.2010.x86 10.0.40219 winget +Microsoft Visual C++ 2015 UWP Desktop Runtime Package Microsoft.VCLibs.Desktop.14 14.0.33728.0 winget +Microsoft Visual C++ 2015 UWP Desktop Runtime Package Microsoft.VCLibs.Desktop.14 14.0.33728.0 winget +Microsoft Visual C++ 2015 UWP Runtime Package Microsoft.VCLibs.14 14.0.33519.0 winget +Microsoft Visual C++ 2015 UWP Runtime Package Microsoft.VCLibs.14 14.0.33519.0 winget +Microsoft Visual C++ v14 Redistributable (x64) - 14.51.36247 Microsoft.VCRedist.2015+.x64 14.51.36247.0 winget +Microsoft Visual C++ v14 Redistributable (x86) - 14.51.36247 Microsoft.VCRedist.2015+.x86 14.51.36247.0 winget +Microsoft Visual Studio Code (User) Microsoft.VisualStudioCode 1.144.0 winget +Microsoft Visual Studio Installer ARP\Machine\X64\{6F320B93-EE3C-4826-85E0-ADF79F8D4C61} 4.10.30.62513 +Microsoft Windows Application Compatibility Fix Database ARP\Machine\X64\{22221111-1111-1111-1111-111111111111}.sdb Unknown +Microsoft Windows Desktop Runtime - 8.0.21 (x64) Microsoft.DotNet.DesktopRuntime.8 8.0.21 8.0.33 winget +Microsoft Windows Desktop Runtime - 9.0.22 (x64) Microsoft.DotNet.DesktopRuntime.9 9.0.22 winget +Microsoft Windows Desktop Runtime 10.0.14 (x64) Microsoft.DotNet.DesktopRuntime.10 10.0.14 winget +Microsoft.UI.Xaml.2.8 Microsoft.UI.Xaml.2.8 8.2511.26001.0 winget +Microsoft.UI.Xaml.2.8 Microsoft.UI.Xaml.2.8 8.2501.31001.0 winget +Mozilla Firefox (x64 de) ARP\Machine\X64\Mozilla Firefox 159.0 +Mozilla Maintenance Service ARP\Machine\X64\MozillaMaintenanceService 158.0 +Mp3tag MSIX\35795FlorianHeidenreich.Mp3tag.ShellExtension_3.34.0.6_x64__hyhc94k68ebpm 3.34.0.6 +Mp3tag v3.36.1 FlorianHeidenreich.Mp3tag 3.36.1 winget +MPC-HC 2.8.4 (64-bit) clsid2.mpc-hc 2.8.4 winget +MPEG-2-Videoerweiterung MSIX\Microsoft.MPEG2VideoExtension_1.2.32.0_x64__8wekyb3d8bbwe 1.2.32.0 +MusicBrain 4.1.8 ARP\User\X64\{B7D1E98B-35F6-4F63-9F99-7D77DF0C5C4A}_is1 4.1.8 +NanaZip M2Team.NanaZip 7.0.1882.0 winget +Node.js OpenJS.NodeJS.LTS 26.13.0 winget +Notepad++ MSIX\NotepadPlusPlus_1.0.0.0_neutral__2247w0b46hfww 1.0.0.0 +Notepad++ (64-bit x64) Notepad++.Notepad++ 8.9.8.1 winget +NVIDIA Broadcast 2.3.0 ARP\Machine\X64\{B2FE1952-0186-46C3-BAEC-A80AA35AC5B8}_NVIDIABroadcast 2.3.0 +NVIDIA Grafiktreiber 617.42 ARP\Machine\X64\{B2FE1952-0186-46C3-BAEC-A80AA35AC5B8}_Display.Driver 617.42 +OBS Studio OBSProject.OBSStudio 33.0.1 winget +Oh My Posh JanDeDobbeleer.OhMyPosh 31.9.1.0 winget +ParkControl BitSum.ParkControl 6.0.1.8 winget +Peak Level Meter Version 1.76 ARP\Machine\X86\Peak Level Meter_is1 Unknown +Power Automate MSIX\Microsoft.PowerAutomateDesktop_1.0.2159.0_x64__8wekyb3d8bbwe 1.0.2159.0 +PowerShell Microsoft.PowerShell 7.6.9.0 winget +Python 3.12.10 (64-bit) Python.Python.3.12 3.12.10 winget +Python 3.14.10 (64-bit) Python.Python.3.13 > 3.13.16 winget +Python Launcher Python.Launcher > 3.14.7 winget +Python Launcher Python.Launcher 3.14.7 winget +Qobuz Qobuz.Qobuz 8.3.0-b028 winget +Raw-Bilderweiterung MSIX\Microsoft.RawImageExtension_2.5.42.0_x64__8wekyb3d8bbwe 2.5.42.0 +Realtek Audio Control MSIX\RealtekSemiconductorCorp.RealtekAudioControl_1.53.385.0_x64__dt26b99r8h8gj 1.53.385.0 +Realtek Audio Driver ARP\Machine\X86\{F132AF7F-7BCA-4EDE-8A7C-958108FE7DBC} 6.0.9927.1 +Realtek Ethernet Controller Driver ARP\Machine\X86\{8833FFB6-5B0C-4764-81AA-06DFEED9A476} 11.29.50.0202 +RustDesk ARP\Machine\X64\RustDesk 1.5.1 +Rustup: the Rust toolchain installer Rustlang.Rustup 1.29.2 winget +ShareX ShareX.ShareX 21.0.0 winget +Start Experiences-App MSIX\Microsoft.StartExperiencesApp_1.398.4.0_x64__8wekyb3d8bbwe 1.398.4.0 +Steam Valve.Steam 2.10.91.91 winget +Tuta 362.261023.2 Tutanota.Tutanota 362.261023.2 winget +UniGetUI ARP\User\X64\{889610CC-4337-4BDB-AC3B-4F21806C0BDE}_is1 2026.3.3 +Verbesserungen an der Windows-Anwendungskompatibilität MSIX\Microsoft.ApplicationCompatibilityEnhancements_1.2511.9.0_x64__8wekyb3d8bbwe 1.2511.9.0 +Visual Studio Build Tools 2022 Microsoft.VisualStudio.2022.BuildTools 17.14.44 winget +VLC media player VideoLAN.VLC 3.0.23 3.0.24 winget +VP9-Videoerweiterungen MSIX\Microsoft.VP9VideoExtensions_1.2.20.0_x64__8wekyb3d8bbwe 1.2.20.0 +vs_CoreEditorFonts ARP\Machine\X86\{1851460E-0E63-4117-B5BA-25A2F045801B} 17.7.40001 +Wallpaper Engine ARP\Machine\X64\Steam App 431960 Unknown +WebCamSettingsReloader-GUI version 1.02 ARP\Machine\X64\{3a628199-4053-4b34-8eb6-f3d8021a7164}_is1 1.02 +Webmedienerweiterungen MSIX\Microsoft.WebMediaExtensions_2.1.51.0_x64__8wekyb3d8bbwe 2.1.51.0 +WebP-Bilderweiterung MSIX\Microsoft.WebpImageExtension_1.2.31.0_x64__8wekyb3d8bbwe 1.2.31.0 +Widgets Platform Runtime MSIX\Microsoft.WidgetsPlatformRuntime_1.6.19.0_x64__8wekyb3d8bbwe 1.6.19.0 +WinAppRuntime.Main.1.5 MSIX\MicrosoftCorporationII.WinAppRuntime.Main.1.5_5001.373.1736.0_x64__8wekyb3d8bbwe 5001.373.1736.0 +WinAppRuntime.Main.2 MSIX\MicrosoftCorporationII.WinAppRuntime.Main.2_2.5.1.0_x64__8wekyb3d8bbwe 2.5.1.0 +WinAppRuntime.Singleton MSIX\MicrosoftCorporationII.WinAppRuntime.Singleton_8002.5.1.0_x64__8wekyb3d8bbwe 8002.5.1.0 +Windows App Runtime DDLM 5001.373.1736.0-x6 MSIX\Microsoft.WinAppRuntime.DDLM.5001.373.1736.0-x6_5001.373.1736.0_x64__8wekyb3d8bbwe 5001.373.1736.0 +Windows App Runtime DDLM 5001.373.1736.0-x8 MSIX\Microsoft.WinAppRuntime.DDLM.5001.373.1736.0-x8_5001.373.1736.0_x86__8wekyb3d8bbwe 5001.373.1736.0 +Windows ML Runtime NVIDIA TensorRT-RTX Execution Provider 2 MSIX\Microsoft.WinML.NVIDIA.TRT-RTX.EP.2_2.30.49.0_x64__8wekyb3d8bbwe 2.30.49.0 +Windows Package Manager Source (winget) V2 MSIX\Microsoft.Winget.Source_2026.1030.1409.1_neutral__8wekyb3d8bbwe 2026.1030.1409.1 +Windows Package Manager Source (winget-font) V2 MSIX\Microsoft.Winget.Fonts.Source_2025.1016.311.49_neutral__8wekyb3d8bbwe 2025.1016.311.49 +Windows SDK AddOn ARP\Machine\X86\{13D2A7A2-1B42-4C03-8F0F-697991259AEE} 10.1.0.0 +Windows Software Development Kit - Windows 10.0.26100.7705 Microsoft.WindowsSDK.10.0.26100 10.0.26100.7705 winget +Windows Web Experience Pack MSIX\MicrosoftWindows.Client.WebExperience_526.21100.40.0_x64__cw5n1h2txyewy 526.21100.40.0 +Windows-Editor MSIX\Microsoft.WindowsNotepad_11.2607.14.0_x64__8wekyb3d8bbwe 11.2607.14.0 +Windows-Rechner MSIX\Microsoft.WindowsCalculator_11.2607.0.0_x64__8wekyb3d8bbwe 11.2607.0.0 +Windows-Sicherheit MSIX\Microsoft.SecHealthUI_1000.29680.1000.0_x64__8wekyb3d8bbwe 1000.29680.1000.0 +Windows-Terminal Microsoft.WindowsTerminal 1.25.2733.0 winget +WindowsAppRuntime.1.1 Microsoft.WindowsAppRuntime.1.1 1.1.5 winget +WindowsAppRuntime.1.1 Microsoft.WindowsAppRuntime.1.1 1.1.5 winget +WindowsAppRuntime.1.5 Microsoft.WindowsAppRuntime.1.5 1.5.9 winget +WindowsAppRuntime.1.5 Microsoft.WindowsAppRuntime.1.5 1.5.9 winget +WindowsAppRuntime.1.7 Microsoft.WindowsAppRuntime.1.7 1.7.9 winget +WindowsAppRuntime.1.7 Microsoft.WindowsAppRuntime.1.7 1.7.9 winget +WindowsAppRuntime.1.8 Microsoft.WindowsAppRuntime.1.8 > 1.8.10 winget +WindowsAppRuntime.1.8 Microsoft.WindowsAppRuntime.1.8 > 1.8.10 winget +WindowsAppRuntime.2 Microsoft.WindowsAppRuntime.2 2.5.1.0 winget +WindowsAppRuntime.2 Microsoft.WindowsAppRuntime.2 2.5.1.0 winget +XBOX MSIX\Microsoft.GamingApp_2610.1001.20.0_x64__8wekyb3d8bbwe 2610.1001.20.0 diff --git a/src-tauri/tests/fixtures/winget_search_de.txt b/src-tauri/tests/fixtures/winget_search_de.txt new file mode 100644 index 0000000..8eb1b31 --- /dev/null +++ b/src-tauri/tests/fixtures/winget_search_de.txt @@ -0,0 +1,12 @@ +Name ID Version Übereinstimmung Quelle +---------------------------------------------------------------------------------------------------- +VLC media player VideoLAN.VLC 3.0.24 Moniker: vlc winget +pympress Cimbali.pympress 1.8.6 Tag: vlc winget +Jellyfin VLC Bridge CrySer66.JellyfinVlcBridge 1.18.0 Tag: vlc winget +Screenbox Starpine.Screenbox 0.24.0 Tag: vlc winget +Streamlink Streamlink.Streamlink 8.6.3-1 Tag: vlc winget +wcap mmozeiko.wcap 2025.11.27 Tag: vlc winget +SubRenamer qwqcode.SubRenamer 2.4.0 Tag: vlc winget +tuna univrsal.tuna 1.9.11 Tag: vlc winget +VLC media player (Nightly) VideoLAN.VLC.Nightly 4.0.0.0-nightly20260509 winget +Darshan Player UjjwalDadhich.Darshan 1.2.0 Tag: libvlc winget diff --git a/src-tauri/tests/fixtures/winget_show_de.txt b/src-tauri/tests/fixtures/winget_show_de.txt new file mode 100644 index 0000000..186e628 --- /dev/null +++ b/src-tauri/tests/fixtures/winget_show_de.txt @@ -0,0 +1,35 @@ +Gefunden VLC media player [VideoLAN.VLC] +Version: 3.0.24 +Herausgeber: VideoLAN +Herausgeber-URL: https://www.videolan.org/ +Herausgeber-Support-URL: https://www.videolan.org/support/ +Autor: VideoLAN +Moniker: vlc +Beschreibung: + VLC is a free and open source cross-platform multimedia player and framework + that plays most multimedia files as well as DVDs, Audio CDs, VCDs, and various + streaming protocols. + + Simple, fast and powerful + Plays everything - Files, Discs, Webcams, Devices and Streams. + Plays most codecs with no codec packs needed - MPEG-2, MPEG-4, H.264, MKV, WebM, + WMV, MP3... + Completely Free - no spyware, no ads and no user tracking. +Startseite: https://www.videolan.org/vlc/ +Lizenz: GPL-2.0 +Lizenz-URL: https://code.videolan.org/videolan/vlc/-/blob/HEAD/COPYING +Markierungen: + audio-player + audioplayer + cross-platform + dvd + foss + media-player + multimedia + video-player +Installationsprogramm: + Installertyp: wix + Installer-URL: https://download.videolan.org/pub/videolan/vlc/3.0.24/win64/vlc-3.0.24-win64.msi + Sha256-Installer: 22172095ce4c2d5f5b600bb4c142d78105db29a77fa59a931941876f3b20f6d0 + Freigabedatum: 2026-09-21 + Unterstützte Offlineverteilung: true diff --git a/src-tauri/tests/fixtures/winget_upgrade_de.txt b/src-tauri/tests/fixtures/winget_upgrade_de.txt new file mode 100644 index 0000000..7fd855f --- /dev/null +++ b/src-tauri/tests/fixtures/winget_upgrade_de.txt @@ -0,0 +1,7 @@ +Name ID Version Verfügbar Quelle +--------------------------------------------------------------------------------------------------------------------- +Antigravity 2.25.0 Google.Antigravity 2.25.0 2.28.2 winget +Microsoft Edge Microsoft.Edge 156.0.4314.50 156.0.4314.56 winget +Microsoft Windows Desktop Runtime - 8.0.21 (x64) Microsoft.DotNet.DesktopRuntime.8 8.0.21 8.0.33 winget +VLC media player VideoLAN.VLC 3.0.23 3.0.24 winget +4 Aktualisierungen verfügbar. diff --git a/src/App.svelte b/src/App.svelte new file mode 100644 index 0000000..38e946c --- /dev/null +++ b/src/App.svelte @@ -0,0 +1,48 @@ + + +
+ +
+ {#if managers.detecting} +

{t("setup.detecting")}

+ {/if} + +
+ {#if ui.view === "discover"} + {:else if ui.view === "updates"} + {:else if ui.view === "installed"} + {:else if ui.view === "bundles"} + {:else}{/if} +
+
+
+ + + diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..e948926 --- /dev/null +++ b/src/app.css @@ -0,0 +1,128 @@ +@import "tailwindcss"; + +:root, +[data-theme="dark"] { + color-scheme: dark; + --bg: #12141a; + --surface: #1a1d25; + --surface-2: #222632; + --surface-hover: #2a2f3d; + --border: #2d3240; + --text: #e8eaf0; + --text-muted: #a0a8b8; + --danger: #ff7a7a; + --warning: #f5b94a; + --success: #5fd49a; +} + +[data-theme="light"] { + color-scheme: light; + --bg: #f3f4f8; + --surface: #ffffff; + --surface-2: #eceef4; + --surface-hover: #e3e6ee; + --border: #d5d9e4; + --text: #1b1e27; + --text-muted: #525a6b; + --danger: #b3261e; + --warning: #8a5a00; + --success: #1b7a4b; +} + +/* Akzentfarben; Kontrastfarbe wird je Akzent so gewählt, dass Text auf Schaltflächen lesbar bleibt. */ +[data-accent="blue"] { --accent: #4c8dff; --accent-strong: #3a78e8; --accent-contrast: #06142e; } +[data-accent="violet"] { --accent: #8b7bff; --accent-strong: #7466ee; --accent-contrast: #0e0a30; } +[data-accent="teal"] { --accent: #2cc4b8; --accent-strong: #20ada2; --accent-contrast: #032420; } +[data-accent="green"] { --accent: #4cc97a; --accent-strong: #3bb369; --accent-contrast: #05200f; } +[data-accent="orange"] { --accent: #ff9a4d; --accent-strong: #ee8638; --accent-contrast: #2b1100; } +[data-accent="pink"] { --accent: #ff6fae; --accent-strong: #ee5a9b; --accent-contrast: #2e0618; } + +@theme inline { + --color-bg: var(--bg); + --color-surface: var(--surface); + --color-surface-2: var(--surface-2); + --color-hover: var(--surface-hover); + --color-border: var(--border); + --color-fg: var(--text); + --color-muted: var(--text-muted); + --color-accent: var(--accent); + --color-accent-strong: var(--accent-strong); + --color-accent-fg: var(--accent-contrast); + --color-danger: var(--danger); + --color-warning: var(--warning); + --color-success: var(--success); + --font-sans: "Inter Variable", "Segoe UI Variable Text", "Segoe UI", system-ui, sans-serif; + --radius-card: 12px; + --radius-control: 8px; +} + +html, +body, +#app { + height: 100%; +} + +body { + margin: 0; + background: var(--bg); + color: var(--text); + font-family: var(--font-sans); + font-size: 14px; + line-height: 20px; + overflow: hidden; + -webkit-font-smoothing: antialiased; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +button, +select, +input { + font: inherit; + color: inherit; +} + +::-webkit-scrollbar { width: 12px; height: 12px; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; border: 3px solid transparent; background-clip: content-box; } +::-webkit-scrollbar-thumb:hover { background: var(--text-muted); background-clip: content-box; border: 3px solid transparent; } + +@keyframes shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } +} + +.skeleton { + background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-hover) 50%, var(--surface-2) 75%); + background-size: 200% 100%; + animation: shimmer 1.4s linear infinite; + border-radius: 6px; +} + +@keyframes fade-in { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: none; } +} + +.fade-in { animation: fade-in 180ms ease-out; } + +/* Dargestellte Markdown-Release-Notizen */ +.notes { font-size: 15px; line-height: 24px; overflow-wrap: anywhere; } +.notes > :first-child { margin-top: 0; } +.notes p, .notes ul, .notes ol, .notes pre, .notes blockquote { margin: 0 0 10px; } +.notes ul { list-style: disc; padding-left: 22px; } +.notes ol { list-style: decimal; padding-left: 22px; } +.notes li { margin: 2px 0; } +.notes h1, .notes h2, .notes h3, .notes h4 { margin: 14px 0 6px; font-weight: 600; line-height: 24px; } +.notes h1 { font-size: 18px; } .notes h2 { font-size: 17px; } .notes h3, .notes h4 { font-size: 15px; } +.notes code { background: var(--surface-2); border-radius: 4px; padding: 1px 6px; font-size: 13px; } +.notes pre { background: var(--surface-2); border-radius: 8px; padding: 10px 12px; overflow-x: auto; } +.notes pre code { background: none; padding: 0; } +.notes a { color: var(--accent); text-decoration: underline; } +.notes blockquote { border-left: 3px solid var(--border); padding-left: 12px; color: var(--text-muted); } + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { animation-duration: 1ms !important; transition-duration: 1ms !important; } +} diff --git a/src/components/DetailsPanel.svelte b/src/components/DetailsPanel.svelte new file mode 100644 index 0000000..69b573f --- /dev/null +++ b/src/components/DetailsPanel.svelte @@ -0,0 +1,145 @@ + + + diff --git a/src/components/Markdown.svelte b/src/components/Markdown.svelte new file mode 100644 index 0000000..2b9e740 --- /dev/null +++ b/src/components/Markdown.svelte @@ -0,0 +1,23 @@ + + + + +
{@html html}
diff --git a/src/components/Modal.svelte b/src/components/Modal.svelte new file mode 100644 index 0000000..553e861 --- /dev/null +++ b/src/components/Modal.svelte @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/PackageView.svelte b/src/components/PackageView.svelte new file mode 100644 index 0000000..6192c36 --- /dev/null +++ b/src/components/PackageView.svelte @@ -0,0 +1,162 @@ + + +
+
+

{title}

+ {@render extraActions?.()} +
+ + + +
+ {#if store.loaded} + {tp(countKey, filtered.length)} + {/if} + {#if selected.size > 0} + + {tp("table.selected", selected.size)} + + + {/if} + {#if store.loadedAt} + {t("status.lastUpdated", { time: formatTime(store.loadedAt) })} + {/if} +
+ + {#if store.error} + + {/if} + +
+
+ p.key} + bind:selected + activeKey={active?.key ?? null} + onactivate={(p) => (active = p)} + {storageKey} + loading={store.loading} + > + {#snippet empty()} + {#if showIdle} + {@render idle?.()} + {:else if !store.error} +
+

{emptyTitle}

+

{emptyText(mode === "search" ? store.query : input.trim())}

+
+ {/if} + {/snippet} +
+
+ {#if active} + (active = null)} /> + {/if} +
+
diff --git a/src/components/SetupBanner.svelte b/src/components/SetupBanner.svelte new file mode 100644 index 0000000..4ba6370 --- /dev/null +++ b/src/components/SetupBanner.svelte @@ -0,0 +1,50 @@ + + +{#if managers.wingetMissing} + +{:else if managers.moduleMissing && !managers.moduleHintDismissed} +
+
+ +
+

{t("setup.moduleMissingTitle")}

+

{t("setup.moduleMissingText")}

+ {#if managers.installError}

{managers.installError}

{/if} +
+ + +
+
+
+
+{/if} diff --git a/src/components/Sidebar.svelte b/src/components/Sidebar.svelte new file mode 100644 index 0000000..484033a --- /dev/null +++ b/src/components/Sidebar.svelte @@ -0,0 +1,59 @@ + + + diff --git a/src/components/UpdateDialog.svelte b/src/components/UpdateDialog.svelte new file mode 100644 index 0000000..a755c1a --- /dev/null +++ b/src/components/UpdateDialog.svelte @@ -0,0 +1,100 @@ + + +{#if updater.dialogOpen && info} + updater.later()}> +
+
+
+
+

{t("updater.title")}

+

+ {t("updater.versionChange", { current: info.currentVersion, next: info.version })} +

+
+
+

+ {#if info.date}{t("updater.released", { date: formatDate(new Date(info.date * 1000)) })}{/if} + {#if info.size}{t("updater.size", { size: formatBytes(info.size) })}{/if} +

+
+ +
+

{t("updater.notes")}

+ {#if info.notes.trim()} + + {:else} +

{t("updater.noNotes")}

+ {/if} +
+ + {#if updater.busy} +
+
+ {statusText} + {#if updater.phase === "downloading"} + + {#if updater.percent !== null}{formatNumber(updater.percent)} % · {/if}{formatBytes(updater.downloaded)}{#if updater.total} + {t("updater.of")} {formatBytes(updater.total)}{/if} + + {/if} +
+
+
+
+
+ {:else if updater.phase === "error"} + + {/if} + +
+ + + +
+
+{/if} diff --git a/src/components/UpdatedNotice.svelte b/src/components/UpdatedNotice.svelte new file mode 100644 index 0000000..9bacbd5 --- /dev/null +++ b/src/components/UpdatedNotice.svelte @@ -0,0 +1,29 @@ + + +{#if updater.notice && !updater.dialogOpen} + updater.dismissNotice()}> +
+
+

{t("updater.updatedTo", { version: updater.notice.version })}

+
+
+

{t("updater.changes")}

+ {#if updater.notice.notes.trim()} + + {:else} +

{t("updater.noNotes")}

+ {/if} +
+
+ +
+
+{/if} diff --git a/src/components/VirtualTable.svelte b/src/components/VirtualTable.svelte new file mode 100644 index 0000000..51f6227 --- /dev/null +++ b/src/components/VirtualTable.svelte @@ -0,0 +1,216 @@ + + +
(scrollTop = e.currentTarget.scrollTop)} + onkeydown={onKeydown} + role="grid" + tabindex="0" + aria-rowcount={sorted.length} +> +
+
+
+ +
+ {#each columns as col (col.key)} +
+ + +
startResize(e, col)} + title={t("table.resizeColumn")} + >
+
+ {/each} +
+ + {#if loading && rows.length === 0} + {#each Array(12) as _, i (i)} +
+
+
+
+ {/each} + {:else if sorted.length === 0} +
{@render empty?.()}
+ {:else} +
+ {#each visible as row, i (rowKey(row))} + {@const index = start + i} + {@const key = rowKey(row)} + +
onactivate?.(row)} + > +
+ toggleRow(row)} + onclick={(e) => e.stopPropagation()} + aria-label={t("table.selectRow")} + class="size-4 accent-[var(--accent)]" + /> +
+ {#each columns as col (col.key)} +
+ {col.value(row)} +
+ {/each} +
+ {/each} +
+ {/if} +
+
diff --git a/src/i18n/de.json b/src/i18n/de.json new file mode 100644 index 0000000..ca509e0 --- /dev/null +++ b/src/i18n/de.json @@ -0,0 +1,190 @@ +{ + "app": { + "name": "VitosUpdater", + "tagline": "Software verwalten und aktuell halten" + }, + "nav": { + "discover": "Pakete entdecken", + "updates": "Software-Updates", + "installed": "Installierte Pakete", + "bundles": "Paket-Bundles", + "settings": "Einstellungen" + }, + "common": { + "search": "Suchen", + "reload": "Neu laden", + "close": "Schließen", + "retry": "Erneut versuchen", + "unknown": "Unbekannt", + "loading": "Wird geladen …", + "dismiss": "Ausblenden", + "details": "Details" + }, + "columns": { + "name": "Name", + "id": "ID", + "version": "Version", + "installedVersion": "Installiert", + "availableVersion": "Verfügbar", + "source": "Quelle", + "manager": "Manager" + }, + "table": { + "selectAll": "Alle sichtbaren Zeilen auswählen", + "selectRow": "Zeile auswählen", + "selected": { + "one": "{count} Paket ausgewählt", + "other": "{count} Pakete ausgewählt" + }, + "clearSelection": "Auswahl aufheben", + "resizeColumn": "Spaltenbreite ändern" + }, + "filters": { + "manager": "Paketmanager", + "source": "Quelle", + "allManagers": "Alle Manager", + "allSources": "Alle Quellen" + }, + "discover": { + "title": "Pakete entdecken", + "placeholder": "Pakete suchen (Name, ID oder Stichwort)", + "hintTitle": "Pakete finden", + "hint": "Suchbegriff eingeben, um Pakete in allen aktivierten Quellen zu finden.", + "noResultsTitle": "Keine Treffer", + "noResults": "Für „{query}“ wurde kein Paket gefunden.", + "results": { + "one": "{count} Treffer", + "other": "{count} Treffer" + } + }, + "updates": { + "title": "Software-Updates", + "emptyTitle": "Alles aktuell", + "empty": "Alle Programme sind auf dem neuesten Stand.", + "count": { + "one": "{count} Aktualisierung verfügbar", + "other": "{count} Aktualisierungen verfügbar" + }, + "check": "Nach Updates suchen", + "filterPlaceholder": "Aktualisierungen filtern …" + }, + "installed": { + "title": "Installierte Pakete", + "filterPlaceholder": "Installierte Pakete filtern …", + "emptyTitle": "Keine Pakete gefunden", + "empty": "Es wurden keine installierten Pakete gefunden.", + "noMatches": "Kein installiertes Paket passt zu „{query}“.", + "count": { + "one": "{count} Paket", + "other": "{count} Pakete" + } + }, + "bundles": { + "title": "Paket-Bundles", + "emptyTitle": "Noch keine Bundles", + "empty": "Bundles fassen mehrere Pakete zusammen, damit sie gemeinsam installiert werden können. Diese Funktion folgt in einer späteren Version." + }, + "details": { + "title": "Paketdetails", + "selectHint": "Ein Paket auswählen, um Details anzuzeigen.", + "none": "Für dieses Paket sind keine Details verfügbar.", + "loadFailed": "Die Details konnten nicht geladen werden.", + "description": "Beschreibung", + "publisher": "Herausgeber", + "license": "Lizenz", + "homepage": "Webseite", + "openHomepage": "Webseite öffnen", + "installerType": "Installationstyp", + "installerUrl": "Download-Adresse", + "hash": "SHA-256-Prüfsumme", + "versions": "Verfügbare Versionen", + "moreVersions": { + "one": "und {count} weitere Version", + "other": "und {count} weitere Versionen" + }, + "installedVersion": "Installierte Version", + "availableVersion": "Verfügbare Version" + }, + "setup": { + "wingetMissingTitle": "WinGet wurde nicht gefunden", + "wingetMissingText": "Für die Paketverwaltung wird WinGet (Windows-Paket-Manager) benötigt. Es ist Bestandteil des „App-Installers“ von Microsoft.", + "installAppInstaller": "App-Installer installieren", + "moduleMissingTitle": "PowerShell-Modul nicht installiert", + "moduleMissingText": "Das Modul Microsoft.WinGet.Client ist optional. Ohne das Modul wertet VitosUpdater die Textausgabe von WinGet aus. Mit dem Modul stehen die Daten strukturiert zur Verfügung.", + "installModule": "Modul installieren", + "installing": "Installation läuft …", + "installFailed": "Die Installation ist fehlgeschlagen.", + "detecting": "Paketmanager werden erkannt …" + }, + "settings": { + "title": "Einstellungen", + "appearance": "Erscheinungsbild", + "theme": "Design", + "themeDark": "Dunkel", + "themeLight": "Hell", + "themeSystem": "System folgen", + "accent": "Akzentfarbe", + "accentBlue": "Blau", + "accentViolet": "Violett", + "accentTeal": "Türkis", + "accentGreen": "Grün", + "accentOrange": "Orange", + "accentPink": "Rosa", + "managers": "Paketmanager", + "statusFound": "Erkannt", + "statusMissing": "Nicht gefunden", + "path": "Pfad", + "version": "Version", + "module": "PowerShell-Modul", + "moduleFound": "Version {version}", + "moduleMissing": "Nicht installiert", + "redetect": "Erneut erkennen", + "sources": "Quellen", + "sourceExplicit": "nur auf Anfrage", + "noSources": "Keine Quellen gefunden.", + "updates": { + "title": "Programm-Updates", + "currentVersion": "Installierte Version", + "lastCheck": "Letzte Prüfung", + "never": "Noch nicht geprüft", + "lastCheckValue": "{date}, {time} Uhr", + "interval": "Prüfintervall", + "intervalMinutes": "{count} Minuten", + "intervalHour": "1 Stunde", + "intervalHours": "{count} Stunden", + "check": "Nach Updates suchen", + "checking": "Es wird gesucht …", + "upToDate": "VitosUpdater ist auf dem neuesten Stand.", + "skippedVersion": "Übersprungene Version", + "resetSkipped": "Zurücksetzen", + "lastError": "Letzter Fehler" + } + }, + "errors": { + "loadFailed": "Die Daten konnten nicht geladen werden.", + "unexpected": "Ein unerwarteter Fehler ist aufgetreten." + }, + "updater": { + "title": "Update verfügbar", + "versionChange": "Version {current} → {next}", + "released": "Veröffentlicht am {date}", + "size": "Downloadgröße: {size}", + "notes": "Neuerungen", + "noNotes": "Für diese Version liegen keine Release-Notizen vor.", + "now": "Jetzt aktualisieren", + "later": "Später erinnern", + "skip": "Diese Version überspringen", + "downloading": "Wird heruntergeladen …", + "installing": "Wird installiert …", + "restarting": "Neustart …", + "of": "von", + "installFailed": "Das Update konnte nicht installiert werden.", + "checkFailed": "Die Update-Prüfung ist fehlgeschlagen.", + "updatedTo": "Aktualisiert auf Version {version}", + "changes": "Änderungen", + "ok": "OK" + }, + "status": { + "lastUpdated": "Zuletzt aktualisiert: {time} Uhr" + } +} diff --git a/src/i18n/t.ts b/src/i18n/t.ts new file mode 100644 index 0000000..6a87774 --- /dev/null +++ b/src/i18n/t.ts @@ -0,0 +1,68 @@ +import de from "./de.json"; + +type Params = Record; + +function lookup(key: string): unknown { + return key.split(".").reduce((node, part) => { + return node && typeof node === "object" ? (node as Record)[part] : undefined; + }, de); +} + +function fill(template: string, params?: Params): string { + if (!params) return template; + return template.replace(/\{(\w+)\}/g, (_, name: string) => String(params[name] ?? `{${name}}`)); +} + +/** Liefert den deutschen Text zu einem Schlüssel; Platzhalter wie `{count}` werden ersetzt. */ +export function t(key: string, params?: Params): string { + const value = lookup(key); + if (typeof value !== "string") { + console.warn(`Fehlender Textschlüssel: ${key}`); + return `⟦${key}⟧`; + } + return fill(value, params); +} + +/** Liefert die Singular-/Pluralform eines Textes (`one` bei genau 1, sonst `other`). */ +export function tp(key: string, count: number, params?: Params): string { + const value = lookup(key) as { one?: string; other?: string } | undefined; + const template = count === 1 ? value?.one : value?.other; + if (typeof template !== "string") { + console.warn(`Fehlender Pluralschlüssel: ${key}`); + return `⟦${key}⟧`; + } + return fill(template, { count: formatNumber(count), ...params }); +} + +const numberFormat = new Intl.NumberFormat("de-DE"); + +/** Zahl mit Tausenderpunkt und Dezimalkomma, z. B. `1.234,5`. */ +export function formatNumber(value: number, fractionDigits = 0): string { + if (fractionDigits === 0) return numberFormat.format(value); + return new Intl.NumberFormat("de-DE", { + minimumFractionDigits: fractionDigits, + maximumFractionDigits: fractionDigits, + }).format(value); +} + +/** Dateigröße, z. B. `1.234,5 MB`. */ +export function formatBytes(bytes: number): string { + const units = ["B", "KB", "MB", "GB", "TB"]; + let value = bytes; + let unit = 0; + while (value >= 1024 && unit < units.length - 1) { + value /= 1024; + unit += 1; + } + return `${formatNumber(value, unit === 0 ? 0 : 1)} ${units[unit]}`; +} + +/** Datum im Format `dd.MM.yyyy`. */ +export function formatDate(date: Date): string { + return new Intl.DateTimeFormat("de-DE", { day: "2-digit", month: "2-digit", year: "numeric" }).format(date); +} + +/** Uhrzeit im 24-Stunden-Format `HH:mm`. */ +export function formatTime(date: Date): string { + return new Intl.DateTimeFormat("de-DE", { hour: "2-digit", minute: "2-digit", hour12: false }).format(date); +} diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..4d55b60 --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,49 @@ +import { Channel, invoke } from "@tauri-apps/api/core"; +import type { + CheckOutcome, + InstallEvent, + ManagerId, + ManagerInfo, + Package, + PackageDetails, + Source, + UpdateInfo, + UpdateNotice, + UpdaterSettings, + UpdaterStatus, +} from "./types"; + +/** Typisierte Aufrufe der Tauri-Befehle. Fehler sind bereits deutsche Texte aus dem Backend. */ +export const api = { + detectManagers: () => invoke("detect_managers"), + searchPackages: (manager: ManagerId, query: string) => + invoke("search_packages", { manager, query }), + listInstalled: (manager: ManagerId) => invoke("list_installed", { manager }), + listUpdates: (manager: ManagerId) => invoke("list_updates", { manager }), + getPackageDetails: (pkg: Package) => invoke("get_package_details", { package: pkg }), + listSources: (manager: ManagerId) => invoke("list_sources", { manager }), + installWingetModule: () => invoke("install_winget_module"), + installAppInstaller: () => invoke("install_app_installer"), + + updaterReady: () => invoke("updater_ready"), + getUpdaterStatus: () => invoke("get_updater_status"), + getUpdateOffer: () => invoke("get_update_offer"), + checkForUpdate: (manual: boolean) => invoke("check_for_update", { manual }), + snoozeUpdate: () => invoke("snooze_update"), + skipUpdate: () => invoke("skip_update"), + clearSkippedVersion: () => invoke("clear_skipped_version"), + setUpdateInterval: (minutes: number) => invoke("set_update_interval", { minutes }), + installUpdate: (onEvent: (event: InstallEvent) => void) => { + const channel = new Channel(); + channel.onmessage = onEvent; + return invoke("install_update", { onEvent: channel }); + }, + takeUpdateNotice: () => invoke("take_update_notice"), +}; + +/** Wandelt einen abgefangenen Fehler in einen anzeigbaren Text um. */ +export function errorText(error: unknown, fallback: string): string { + if (typeof error === "string" && error) return error; + if (error instanceof Error && error.message) return error.message; + return fallback; +} diff --git a/src/lib/columns.ts b/src/lib/columns.ts new file mode 100644 index 0000000..ef15e2c --- /dev/null +++ b/src/lib/columns.ts @@ -0,0 +1,25 @@ +import type { Column } from "./table"; +import type { Package } from "./types"; +import { t } from "../i18n/t"; + +const text = (value: string | null | undefined) => value ?? ""; + +/** Spalten der Suchansicht. */ +export const searchColumns = (): Column[] => [ + { key: "name", label: t("columns.name"), width: 280, value: (p) => p.name }, + { key: "id", label: t("columns.id"), width: 260, value: (p) => p.id, muted: true }, + { key: "version", label: t("columns.version"), width: 140, value: (p) => p.version }, + { key: "source", label: t("columns.source"), width: 120, value: (p) => text(p.source), muted: true }, +]; + +/** Spalten der Ansicht „Installierte Pakete“. */ +export const installedColumns = (): Column[] => [ + { key: "name", label: t("columns.name"), width: 280, value: (p) => p.name }, + { key: "id", label: t("columns.id"), width: 260, value: (p) => p.id, muted: true }, + { key: "version", label: t("columns.installedVersion"), width: 150, value: (p) => p.version }, + { key: "available", label: t("columns.availableVersion"), width: 150, value: (p) => text(p.availableVersion) }, + { key: "source", label: t("columns.source"), width: 120, value: (p) => text(p.source), muted: true }, +]; + +/** Spalten der Ansicht „Software-Updates“. */ +export const updateColumns = (): Column[] => installedColumns(); diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts new file mode 100644 index 0000000..f1643da --- /dev/null +++ b/src/lib/markdown.ts @@ -0,0 +1,14 @@ +import { marked } from "marked"; +import DOMPurify from "dompurify"; + +marked.setOptions({ gfm: true, breaks: true }); + +/** Wandelt Markdown (z. B. Release-Notizen) in bereinigtes HTML um. Links öffnen nie im App-Fenster. */ +export function renderMarkdown(source: string): string { + const html = marked.parse(source, { async: false }) as string; + return DOMPurify.sanitize(html, { + ALLOWED_TAGS: ["p", "br", "ul", "ol", "li", "strong", "em", "code", "pre", "h1", "h2", "h3", "h4", "blockquote", "a", "hr"], + ALLOWED_ATTR: ["href"], + ALLOWED_URI_REGEXP: /^https?:/i, + }); +} diff --git a/src/lib/table.ts b/src/lib/table.ts new file mode 100644 index 0000000..84e354e --- /dev/null +++ b/src/lib/table.ts @@ -0,0 +1,19 @@ +/** Spaltendefinition der virtualisierten Tabelle. */ +export interface Column { + key: string; + label: string; + /** Startbreite in Pixeln. */ + width: number; + /** Mindestbreite in Pixeln (Standard: 80). */ + min?: number; + /** Zellentext; wird auch zum Sortieren verwendet. */ + value: (row: T) => string; + muted?: boolean; +} + +const collator = new Intl.Collator("de", { numeric: true, sensitivity: "base" }); + +/** Vergleicht zwei Texte natürlich (Zahlen als Zahlen, deutsche Sortierung). */ +export function compareText(a: string, b: string): number { + return collator.compare(a, b); +} diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..43d8175 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,93 @@ +export type ManagerId = + | "winget" + | "scoop" + | "choco" + | "pip" + | "npm" + | "dotnet" + | "psgallery" + | "cargo" + | "vcpkg"; + +export interface Package { + key: string; + id: string; + name: string; + version: string; + availableVersion: string | null; + manager: ManagerId; + source: string | null; +} + +export interface PackageDetails { + description: string | null; + publisher: string | null; + license: string | null; + licenseUrl: string | null; + homepage: string | null; + installerUrl: string | null; + installerType: string | null; + hash: string | null; + versions: string[]; +} + +export interface ExtraInfo { + name: string; + available: boolean; + version: string | null; +} + +export interface ManagerInfo { + id: ManagerId; + name: string; + available: boolean; + path: string | null; + version: string | null; + extras: ExtraInfo[]; +} + +export interface Source { + name: string; + url: string; + explicit: boolean; +} + +export type ViewId = "discover" | "updates" | "installed" | "bundles" | "settings"; + +export interface UpdateInfo { + version: string; + currentVersion: string; + /** Veröffentlichungsdatum als Unix-Sekunden. */ + date: number | null; + notes: string; + size: number | null; +} + +export interface UpdaterSettings { + checkIntervalMinutes: number; + skippedVersion: string | null; + snoozedVersion: string | null; + snoozedUntil: number | null; +} + +export interface UpdaterStatus { + currentVersion: string; + settings: UpdaterSettings; + lastCheck: number | null; + lastError: string | null; + offer: UpdateInfo | null; +} + +export type CheckOutcome = + | { status: "available" | "skipped" | "snoozed"; info: UpdateInfo } + | { status: "upToDate" }; + +export type InstallEvent = + | { stage: "started"; total: number | null } + | { stage: "progress"; downloaded: number; total: number | null } + | { stage: "installing" }; + +export interface UpdateNotice { + version: string; + notes: string; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..9a6918e --- /dev/null +++ b/src/main.ts @@ -0,0 +1,10 @@ +import "@fontsource-variable/inter"; +import "./app.css"; +import { mount } from "svelte"; +import App from "./App.svelte"; +import "./stores/theme.svelte"; + +const target = document.getElementById("app"); +if (!target) throw new Error("Wurzelelement #app fehlt."); + +export default mount(App, { target }); diff --git a/src/stores/list.svelte.ts b/src/stores/list.svelte.ts new file mode 100644 index 0000000..88e8207 --- /dev/null +++ b/src/stores/list.svelte.ts @@ -0,0 +1,41 @@ +import type { Package } from "../lib/types"; +import { errorText } from "../lib/api"; +import { t } from "../i18n/t"; + +/** Lade-Zustand einer Paketliste (Suche, installiert, Aktualisierungen). */ +export class ListStore { + items = $state([]); + loading = $state(false); + error = $state(null); + loadedAt = $state(null); + loaded = $state(false); + query = $state(""); + + #loader: (query: string) => Promise; + #token = 0; + + constructor(loader: (query: string) => Promise) { + this.#loader = loader; + } + + /** Lädt die Liste neu. Ältere, noch laufende Anfragen werden verworfen. */ + async load(query = ""): Promise { + const token = ++this.#token; + this.loading = true; + this.error = null; + this.query = query; + try { + const items = await this.#loader(query); + if (token !== this.#token) return; + this.items = items; + this.loadedAt = new Date(); + this.loaded = true; + } catch (err) { + if (token !== this.#token) return; + this.items = []; + this.error = errorText(err, t("errors.loadFailed")); + } finally { + if (token === this.#token) this.loading = false; + } + } +} diff --git a/src/stores/managers.svelte.ts b/src/stores/managers.svelte.ts new file mode 100644 index 0000000..30bc58a --- /dev/null +++ b/src/stores/managers.svelte.ts @@ -0,0 +1,53 @@ +import { api, errorText } from "../lib/api"; +import type { ManagerInfo } from "../lib/types"; +import { t } from "../i18n/t"; + +/** Erkennungszustand der Paketmanager. */ +class ManagersStore { + infos = $state([]); + detecting = $state(true); + error = $state(null); + installing = $state<"module" | "appInstaller" | null>(null); + installError = $state(null); + moduleHintDismissed = $state(false); + + winget = $derived(this.infos.find((m) => m.id === "winget") ?? null); + wingetMissing = $derived(!this.detecting && !this.error && !(this.winget?.available ?? false)); + moduleMissing = $derived( + !!this.winget?.available && !(this.winget.extras.find((e) => e.name === "Microsoft.WinGet.Client")?.available ?? false), + ); + + async detect(): Promise { + this.detecting = true; + this.error = null; + try { + this.infos = await api.detectManagers(); + } catch (err) { + this.error = errorText(err, t("errors.unexpected")); + } finally { + this.detecting = false; + } + } + + async installModule(): Promise { + await this.#install("module", api.installWingetModule); + } + + async installAppInstaller(): Promise { + await this.#install("appInstaller", api.installAppInstaller); + } + + async #install(kind: "module" | "appInstaller", run: () => Promise): Promise { + this.installing = kind; + this.installError = null; + try { + this.infos = await run(); + } catch (err) { + this.installError = errorText(err, t("setup.installFailed")); + } finally { + this.installing = null; + } + } +} + +export const managers = new ManagersStore(); diff --git a/src/stores/packages.svelte.ts b/src/stores/packages.svelte.ts new file mode 100644 index 0000000..cf25986 --- /dev/null +++ b/src/stores/packages.svelte.ts @@ -0,0 +1,14 @@ +import { api } from "../lib/api"; +import { ListStore } from "./list.svelte"; +import type { ViewId } from "../lib/types"; + +/** Gemeinsame Listen, damit Daten beim Wechseln der Ansicht erhalten bleiben. */ +export const searchStore = new ListStore((query) => api.searchPackages("winget", query)); +export const installedStore = new ListStore(() => api.listInstalled("winget")); +export const updatesStore = new ListStore(() => api.listUpdates("winget")); + +class UiState { + view = $state("updates"); +} + +export const ui = new UiState(); diff --git a/src/stores/theme.svelte.ts b/src/stores/theme.svelte.ts new file mode 100644 index 0000000..00826c5 --- /dev/null +++ b/src/stores/theme.svelte.ts @@ -0,0 +1,45 @@ +export type ThemeChoice = "dark" | "light" | "system"; +export type AccentChoice = "blue" | "violet" | "teal" | "green" | "orange" | "pink"; + +const THEME_KEY = "vu.theme"; +const ACCENT_KEY = "vu.accent"; + +function readStored(key: string, allowed: readonly T[], fallback: T): T { + const value = localStorage.getItem(key); + return allowed.includes(value as T) ? (value as T) : fallback; +} + +export const THEMES: readonly ThemeChoice[] = ["dark", "light", "system"]; +export const ACCENTS: readonly AccentChoice[] = ["blue", "violet", "teal", "green", "orange", "pink"]; + +/** Erscheinungsbild: Design (dunkel/hell/System) und Akzentfarbe. */ +class ThemeStore { + theme = $state(readStored(THEME_KEY, THEMES, "dark")); + accent = $state(readStored(ACCENT_KEY, ACCENTS, "blue")); + #media = window.matchMedia("(prefers-color-scheme: dark)"); + + constructor() { + this.#media.addEventListener("change", () => this.apply()); + this.apply(); + } + + setTheme(theme: ThemeChoice): void { + this.theme = theme; + localStorage.setItem(THEME_KEY, theme); + this.apply(); + } + + setAccent(accent: AccentChoice): void { + this.accent = accent; + localStorage.setItem(ACCENT_KEY, accent); + this.apply(); + } + + apply(): void { + const effective = this.theme === "system" ? (this.#media.matches ? "dark" : "light") : this.theme; + document.documentElement.dataset.theme = effective; + document.documentElement.dataset.accent = this.accent; + } +} + +export const themeStore = new ThemeStore(); diff --git a/src/stores/updater.svelte.ts b/src/stores/updater.svelte.ts new file mode 100644 index 0000000..cd76e6f --- /dev/null +++ b/src/stores/updater.svelte.ts @@ -0,0 +1,137 @@ +import { listen } from "@tauri-apps/api/event"; +import { api, errorText } from "../lib/api"; +import type { UpdateInfo, UpdateNotice, UpdaterStatus } from "../lib/types"; +import { t } from "../i18n/t"; + +export type UpdatePhase = "idle" | "downloading" | "installing" | "restarting" | "error"; +export type ManualResult = { kind: "upToDate" } | { kind: "error"; text: string } | null; + +/** Zustand des Selbst-Updaters und des Update-Popups. */ +class UpdaterStore { + status = $state(null); + offer = $state(null); + dialogOpen = $state(false); + phase = $state("idle"); + downloaded = $state(0); + total = $state(null); + error = $state(null); + checking = $state(false); + manualResult = $state(null); + notice = $state(null); + + percent = $derived(this.total && this.total > 0 ? Math.min(100, Math.round((this.downloaded / this.total) * 100)) : null); + busy = $derived(this.phase === "downloading" || this.phase === "installing" || this.phase === "restarting"); + + /** Startet Prüfung im Backend, registriert Ereignisse und holt den Hinweis zum letzten Update. */ + async init(): Promise { + await listen("update-available", (event) => this.show(event.payload)); + await listen("update-restarting", () => { + this.phase = "restarting"; + }); + const pull = () => void this.pullOffer(); + window.addEventListener("focus", pull); + document.addEventListener("visibilitychange", () => { + if (!document.hidden) pull(); + }); + try { + this.notice = await api.takeUpdateNotice(); + this.status = await api.updaterReady(); + if (this.status.offer) this.show(this.status.offer); + } catch (err) { + console.error("Updater konnte nicht gestartet werden", err); + } + } + + async refreshStatus(): Promise { + this.status = await api.getUpdaterStatus(); + } + + /** Fragt das Backend nach einem anzubietenden Update (z. B. nach Rückkehr aus dem Tray). */ + async pullOffer(): Promise { + if (this.dialogOpen) return; + try { + const offer = await api.getUpdateOffer(); + if (offer) this.show(offer); + } catch { + /* Ein fehlgeschlagener Abruf ist unkritisch; die nächste Prüfung versucht es erneut. */ + } + } + + /** Öffnet das Popup. Es wird nie mehr als ein Popup gleichzeitig angezeigt. */ + show(info: UpdateInfo): void { + if (this.dialogOpen || this.busy) return; + this.offer = info; + this.phase = "idle"; + this.error = null; + this.downloaded = 0; + this.total = null; + this.dialogOpen = true; + } + + async later(): Promise { + if (this.busy) return; + this.dialogOpen = false; + await api.snoozeUpdate().catch(() => undefined); + await this.refreshStatus().catch(() => undefined); + } + + async skip(): Promise { + if (this.busy) return; + this.dialogOpen = false; + await api.skipUpdate().catch(() => undefined); + await this.refreshStatus().catch(() => undefined); + } + + async install(): Promise { + if (this.busy) return; + this.error = null; + this.phase = "downloading"; + this.downloaded = 0; + this.total = null; + try { + await api.installUpdate((event) => { + if (event.stage === "started") this.total = event.total; + else if (event.stage === "progress") { + this.downloaded = event.downloaded; + this.total = event.total ?? this.total; + } else if (this.phase !== "restarting") this.phase = "installing"; + }); + } catch (err) { + this.phase = "error"; + this.error = errorText(err, t("updater.installFailed")); + } + } + + /** Manuelle Prüfung aus den Einstellungen. */ + async checkManually(): Promise { + if (this.checking) return; + this.checking = true; + this.manualResult = null; + try { + const outcome = await api.checkForUpdate(true); + if (outcome.status === "upToDate") this.manualResult = { kind: "upToDate" }; + else this.show(outcome.info); + } catch (err) { + this.manualResult = { kind: "error", text: errorText(err, t("updater.checkFailed")) }; + } finally { + this.checking = false; + await this.refreshStatus().catch(() => undefined); + } + } + + async setInterval(minutes: number): Promise { + await api.setUpdateInterval(minutes); + await this.refreshStatus(); + } + + async clearSkipped(): Promise { + await api.clearSkippedVersion(); + await this.refreshStatus(); + } + + dismissNotice(): void { + this.notice = null; + } +} + +export const updater = new UpdaterStore(); diff --git a/src/views/Bundles.svelte b/src/views/Bundles.svelte new file mode 100644 index 0000000..7eac5fe --- /dev/null +++ b/src/views/Bundles.svelte @@ -0,0 +1,13 @@ + + +
+

{t("bundles.title")}

+
+ +

{t("bundles.emptyTitle")}

+

{t("bundles.empty")}

+
+
diff --git a/src/views/Discover.svelte b/src/views/Discover.svelte new file mode 100644 index 0000000..79eefd4 --- /dev/null +++ b/src/views/Discover.svelte @@ -0,0 +1,27 @@ + + + t("discover.noResults", { query })} +> + {#snippet idle()} +
+ +

{t("discover.hintTitle")}

+

{t("discover.hint")}

+
+ {/snippet} +
diff --git a/src/views/Installed.svelte b/src/views/Installed.svelte new file mode 100644 index 0000000..a130b5f --- /dev/null +++ b/src/views/Installed.svelte @@ -0,0 +1,18 @@ + + + (query ? t("installed.noMatches", { query }) : t("installed.empty"))} +/> diff --git a/src/views/Settings.svelte b/src/views/Settings.svelte new file mode 100644 index 0000000..1711ffc --- /dev/null +++ b/src/views/Settings.svelte @@ -0,0 +1,201 @@ + + +
+

{t("settings.title")}

+ +
+
+

{t("settings.updates.title")}

+ {#if updater.status} +
+
{t("settings.updates.currentVersion")}
+
{updater.status.currentVersion}
+
{t("settings.updates.lastCheck")}
+
{lastCheckText(updater.status.lastCheck)}
+
+
+ +
+ {#if updater.status.settings.skippedVersion} +
{t("settings.updates.skippedVersion")}
+
+ {updater.status.settings.skippedVersion} + +
+ {/if} +
+ {/if} +
+ + {#if updater.manualResult?.kind === "upToDate"} + {t("settings.updates.upToDate")} + {:else if updater.manualResult?.kind === "error"} + {updater.manualResult.text} + {/if} +
+ {#if updater.status?.lastError && updater.manualResult?.kind !== "error"} +

+ {t("settings.updates.lastError")}: {updater.status.lastError} +

+ {/if} +
+
+

{t("settings.appearance")}

+
+
+

{t("settings.theme")}

+
+ {#each THEMES as choice (choice)} + + {/each} +
+
+
+

{t("settings.accent")}

+
+ {#each ACCENTS as choice (choice)} + + {/each} +
+
+
+
+ +
+
+

{t("settings.managers")}

+ +
+ {#if managers.winget} +
+ {#if managers.winget.available}{:else}{/if} + {managers.winget.name} + – {managers.winget.available ? t("settings.statusFound") : t("settings.statusMissing")} +
+
+
{t("settings.version")}
+
{managers.winget.version ?? t("common.unknown")}
+
{t("settings.path")}
+
{managers.winget.path ?? t("common.unknown")}
+
{t("settings.module")}
+
{module?.available ? t("settings.moduleFound", { version: module.version ?? "" }) : t("settings.moduleMissing")}
+
+ {/if} +
+ +
+

{t("settings.sources")}

+ {#if sources.length === 0} +

{t("settings.noSources")}

+ {:else} +
    + {#each sources as source (source.name)} +
  • + {source.name} + {source.url} + {#if source.explicit}{t("settings.sourceExplicit")}{/if} +
  • + {/each} +
+ {/if} +
+
+
diff --git a/src/views/Updates.svelte b/src/views/Updates.svelte new file mode 100644 index 0000000..0ed7f55 --- /dev/null +++ b/src/views/Updates.svelte @@ -0,0 +1,18 @@ + + + t("updates.empty")} +/> diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..d0e6448 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + preprocess: vitePreprocess(), +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e45cf39 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "allowJs": false, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["svelte", "vite/client"] + }, + "include": ["src/**/*.ts", "src/**/*.svelte", "vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..4c87cf5 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [tailwindcss(), svelte()], + clearScreen: false, + server: { + port: 1420, + strictPort: true, + watch: { ignored: ["**/src-tauri/**"] }, + }, + build: { + target: "chrome105", + outDir: "dist", + emptyOutDir: true, + }, +});