commit e0773ba2994fee38ad36cf11b4e9a31e28587864 Author: Davito Date: Fri Oct 30 15:58:58 2026 +0100 Erste Version: Tauri-Grundgerüst, WinGet-Anbindung, Selbst-Updater und Release-Skript Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 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 0000000..7b1548b Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..75a3b78 Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000..3c0df5b Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png new file mode 100644 index 0000000..0040475 Binary files /dev/null and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..2b33457 Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..2910e84 Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..3f2d97a Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..4c9dbb8 Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..78f9062 Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..cfe420e Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..3179012 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..1620879 Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..6c19895 Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..945a4f3 Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..3abf716 Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000..66ad986 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 0000000..6c3b2b6 Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/source.png b/src-tauri/icons/source.png new file mode 100644 index 0000000..da05e78 Binary files /dev/null and b/src-tauri/icons/source.png differ 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, + }, +});