From 98bce1b0da675715b2e5482c0ad61ba21135f62f Mon Sep 17 00:00:00 2001
From: Server-Bibliothek <server-bibliothek@lokal>
Date: Di, 04 Aug 2026 07:00:04 +0200
Subject: [PATCH] Add a desktop client that knows what is installed locally

---
 desktop/build_exe.ps1 |   76 ++++++++++++++++++++++++++++++++++++++
 1 files changed, 76 insertions(+), 0 deletions(-)

diff --git a/desktop/build_exe.ps1 b/desktop/build_exe.ps1
new file mode 100644
index 0000000..48d9efe
--- /dev/null
+++ b/desktop/build_exe.ps1
@@ -0,0 +1,76 @@
+# Baut EINE eigenstaendige Windows-.exe des Software-Bibliothek-Clients.
+#
+# Laeuft aus einem FRISCHEN Git-Klon ohne Vorbereitung: die virtuelle Umgebung wird
+# angelegt, Python- und npm-Abhaengigkeiten werden installiert, die Oberflaeche
+# gebaut und alles zu einer Datei gepackt. Damit ist dieses Skript der einzige
+# Bau-Befehl, den die Software-Bibliothek braucht (siehe software-bibliothek.json).
+$ErrorActionPreference = "Stop"
+$desktop = $PSScriptRoot
+$projekt = Split-Path $desktop -Parent
+Set-Location $desktop
+
+$python = "$desktop\.venv\Scripts\python.exe"
+
+# 1. Virtuelle Umgebung sicherstellen. Ein frischer Klon hat keine; der Starter 'py'
+#    ist auf Windows der zuverlaessigste Weg (nicht der Store-Stub).
+if (-not (Test-Path $python)) {
+  Write-Host "Virtuelle Umgebung fehlt - wird angelegt ..."
+  $angelegt = $false
+  foreach ($starter in @(@("py", @("-3")), @("python", @()), @("python3", @()))) {
+    $befehl = $starter[0]
+    if (-not (Get-Command $befehl -ErrorAction SilentlyContinue)) { continue }
+    & $befehl @($starter[1] + @("-m", "venv", "$desktop\.venv"))
+    if ($LASTEXITCODE -eq 0 -and (Test-Path $python)) { $angelegt = $true; break }
+  }
+  if (-not $angelegt) { throw "Keine Python-Installation gefunden (py / python / python3)." }
+}
+
+# 2. Python-Abhaengigkeiten
+Write-Host "Python-Abhaengigkeiten ..."
+& $python -m pip install --upgrade pip --quiet
+& $python -m pip install -r "$desktop\requirements.txt" --quiet
+& $python -m pip install pyinstaller --quiet
+if ($LASTEXITCODE -ne 0) { throw "pip fehlgeschlagen." }
+
+# 3. Oberflaeche bauen. Sie liegt als npm-Workspace im Projekt-Wurzelverzeichnis
+#    und teilt sich die Typen mit dem Server - deshalb wird dort installiert.
+Write-Host "Oberflaeche bauen ..."
+Push-Location $projekt
+npm install --no-audit --no-fund
+if ($LASTEXITCODE -ne 0) { Pop-Location; throw "npm install fehlgeschlagen." }
+npm run build:desktop-ui
+if ($LASTEXITCODE -ne 0) { Pop-Location; throw "Bau der Oberflaeche fehlgeschlagen." }
+Pop-Location
+
+if (-not (Test-Path "$desktop\frontend\dist\index.html")) {
+  throw "Die gebaute Oberflaeche fehlt unter frontend\dist."
+}
+
+# 4. Alte Bau-Artefakte weg, damit nichts von einem frueheren Lauf ueberlebt.
+Remove-Item "$desktop\build", "$desktop\dist" -Recurse -Force -ErrorAction SilentlyContinue
+
+# 5. Eine Datei daraus machen. Die Oberflaeche wird als Datenverzeichnis
+#    mitgepackt; __main__.py findet sie zur Laufzeit unter sys._MEIPASS.
+#
+#    `--collect-all` ist bei den drei webview-Paketen Pflicht: pywebview zeigt das
+#    Fenster unter Windows ueber WebView2, und das spricht es via pythonnet in .NET
+#    an. Diese Bestandteile findet PyInstaller nicht durch Nachverfolgen der
+#    Importe — ohne sie startet die exe und beendet sich sofort wieder.
+Write-Host "PyInstaller ..."
+& $python -m PyInstaller `
+  --noconfirm `
+  --onefile `
+  --windowed `
+  --name "Software-Bibliothek" `
+  --icon "$desktop\assets\icon.ico" `
+  --distpath "$desktop\dist" --workpath "$desktop\build" `
+  --add-data "$desktop\frontend\dist;frontend/dist" `
+  --collect-all webview `
+  --collect-all pythonnet `
+  --collect-all clr_loader `
+  "$desktop\run_app.py"
+if ($LASTEXITCODE -ne 0) { throw "PyInstaller fehlgeschlagen." }
+
+$ergebnis = "$desktop\dist\Software-Bibliothek.exe"
+if (-not (Test-Path $ergebnis)) { throw "Erwartete Datei fehlt: $ergebnis" }
+Write-Host "Fertig: $ergebnis ($([math]::Round((Get-Item $ergebnis).Length / 1MB, 1)) MB)"

--
Gitblit v1.9.3