From c721464af24998701df20299f4f799efdfcd309f Mon Sep 17 00:00:00 2001 From: Schalli Date: Wed, 16 Sep 2026 16:12:53 +0200 Subject: [PATCH] =?UTF-8?q?docs(18-01):=20Plan=2001=20abgeschlossen=20?= =?UTF-8?q?=E2=80=94=20Durchstich=20Skript=20->=20Abbild=20->=20API=20bewi?= =?UTF-8?q?esen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SUMMARY: 8 gruene Spec-Tests, Typpruefung fehlerfrei, lokaler Stack liefert /desktop/latest und /desktop/download/linux auf Basislinie 1.1.0 aus. Ein Deviation-Eintrag (Rule 3): @Inject(DesktopService) explizit noetig, da Vitest ueber esbuild ohne emitDecoratorMetadata transpiliert. Co-Authored-By: Claude Opus 5 (1M context) --- .../18-01-SUMMARY.md | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 .planning/phases/18-desktop-client-fertigstellen/18-01-SUMMARY.md diff --git a/.planning/phases/18-desktop-client-fertigstellen/18-01-SUMMARY.md b/.planning/phases/18-desktop-client-fertigstellen/18-01-SUMMARY.md new file mode 100644 index 0000000..54f9d5c --- /dev/null +++ b/.planning/phases/18-desktop-client-fertigstellen/18-01-SUMMARY.md @@ -0,0 +1,217 @@ +--- +phase: 18-desktop-client-fertigstellen +plan: 01 +subsystem: infra +tags: [nestjs, tauri, gitea-actions, streamable-file, desktop-distribution] + +# Dependency graph +requires: + - phase: 06-desktop-client-ci-cd + provides: Tauri-Grundgeruest (apps/desktop, AppImage+NSIS-Bundle-Ziele, Tray, Setup-Seite) +provides: + - .gitea/scripts/desktop-collect.sh (Pakete einsammeln, manifest.json schreiben) + - .gitea/scripts/desktop-version.sh (Version aus dem Freigabe-Tag schreiben) + - apps/api/src/desktop/ (GET /desktop/latest, GET /desktop/download/:platform, beide @Public()) + - packages/shared DesktopPlatform/DesktopManifest(File)/DesktopLatest(Response) Typen + - apps/api/Dockerfile mit COPY desktop-dist + - Basislinie 1.1.0 in tauri.conf.json/Cargo.toml/Cargo.lock/package.json +affects: [18-02-ci-pipeline-release-assets, 18-03-web-oberflaeche, 18-04-client-updateprüfung] + +actuals: + tokens: 6718 + tasks: 2 + commits: 2 +plan_head_before: 0e4eb9b + +tech-stack: + added: [] + patterns: + - "NestJS StreamableFile fuer grosse Downloads statt res.send(buffer) (Installer-Groessenordnung)" + - "Manifest-getriebene Dateiauswahl: Dateiname kommt ausschliesslich aus manifest.json, nie aus dem Request-Pfad (Whitelist vor Dateisystemzugriff)" + - "HTTP-Durchstich-Spec ueber NestFactory.create() + app.listen(0) statt fs-Mocks fuer datei-lesende Module" + +key-files: + created: + - .gitea/scripts/desktop-collect.sh + - .gitea/scripts/desktop-version.sh + - apps/api/src/desktop/desktop.module.ts + - apps/api/src/desktop/desktop.controller.ts + - apps/api/src/desktop/desktop.service.ts + - apps/api/src/desktop/desktop.service.spec.ts + - desktop-dist/.gitkeep + modified: + - .gitignore + - apps/api/Dockerfile + - apps/api/src/app.module.ts + - packages/shared/src/index.ts + - apps/desktop/package.json + - apps/desktop/src-tauri/Cargo.toml + - apps/desktop/src-tauri/Cargo.lock + - apps/desktop/src-tauri/tauri.conf.json + +key-decisions: + - "DesktopController braucht @Inject(DesktopService) explizit auf dem Konstruktor-Parameter — Vitest transpiliert ueber esbuild, das emitDecoratorMetadata nicht abbildet; ohne den expliziten Token bleibt desktopService bei einem echten NestFactory-Bau (der HTTP-Durchstich-Test) undefined, obwohl derselbe Code unter tsc (nest build) korrekt aufgeloest wuerde." + - "Lokaler Stack am Ende beider Tasks zweimal neu gebaut (einmal je Task) statt nur einmal am Schluss, damit jede Verify-Stufe gegen den tatsaechlich damals gueltigen desktop-dist-Inhalt prueft und der Stack in einem konsistenten 1.1.0-Endzustand stehen bleibt." + +patterns-established: + - "PLATFORMS-Konstante (geschlossener Wertevorrat) vor jedem Dateisystemzugriff pruefen, danach erst das Manifest lesen — Reihenfolge ist die Sicherheitseigenschaft (T-18-01)." + +requirements-completed: [DESK-01, DESK-03, DESK-05] + +coverage: + - id: D1 + description: "GET /desktop/latest liefert Version/Kanal/Dateiliste aus manifest.json (200) oder 404 ohne Manifest" + requirement: "DESK-03" + verification: + - kind: integration + ref: "apps/api/src/desktop/desktop.service.spec.ts#Test 1 (latest, Manifest vorhanden)" + status: pass + - kind: integration + ref: "apps/api/src/desktop/desktop.service.spec.ts#Test 2 (getLatest ohne Manifest)" + status: pass + - kind: other + ref: "curl -sf http://localhost:3001/desktop/latest (lokaler Docker-Stack, neu gebautes Abbild)" + status: pass + human_judgment: false + - id: D2 + description: "GET /desktop/download/:platform streamt die Datei mit attachment-Header, Whitelist vor Dateisystemzugriff, Traversal/unbekannte Plattform enden mit 400, fehlende Pakete/Namen mit 404" + requirement: "DESK-03" + verification: + - kind: integration + ref: "apps/api/src/desktop/desktop.service.spec.ts#Test 3 (download/linux)" + status: pass + - kind: integration + ref: "apps/api/src/desktop/desktop.service.spec.ts#Test 4 (Plattform-Whitelist + Traversal ueber HTTP)" + status: pass + - kind: integration + ref: "apps/api/src/desktop/desktop.service.spec.ts#Test 5 (Whitelist vor Dateisystem)" + status: pass + - kind: integration + ref: "apps/api/src/desktop/desktop.service.spec.ts#Test 6 (Manifest nur mit windows)" + status: pass + - kind: integration + ref: "apps/api/src/desktop/desktop.service.spec.ts#Test 7 (manipulierter Name im Manifest)" + status: pass + - kind: other + ref: "curl -sI http://localhost:3001/desktop/download/linux (lokaler Docker-Stack)" + status: pass + human_judgment: false + - id: D3 + description: "Beide Routen tragen @Public() (kein Anmelde-Zwang)" + requirement: "DESK-03" + verification: + - kind: unit + ref: "apps/api/src/desktop/desktop.service.spec.ts#Test 8 (bewusst oeffentlich)" + status: pass + human_judgment: false + - id: D4 + description: "desktop-collect.sh sammelt das Tauri-AppImage ein, benennt es kanonisch um und schreibt manifest.json mit korrekter Groesse/SHA-256" + requirement: "DESK-01" + verification: + - kind: other + ref: "sh .gitea/scripts/desktop-collect.sh --require linux + sha256sum-Vergleich gegen manifest.json (zweimal ausgefuehrt: 0.0.1 und 1.1.0)" + status: pass + human_judgment: false + - id: D5 + description: "desktop-version.sh schreibt die reine X.Y.Z-Version des letzten Freigabe-Tags in tauri.conf.json/Cargo.toml, verweigert Vorab-/Metadatenformen" + requirement: "DESK-05" + verification: + - kind: other + ref: "sh .gitea/scripts/desktop-version.sh --print + Negativproben (v1.2.3-beta abgelehnt, v2.0.0 akzeptiert-aber-ungeschrieben)" + status: pass + human_judgment: false + - id: D6 + description: "Basislinie 1.1.0 in allen vier Client-Dateien eingecheckt, cargo check bleibt gruen" + requirement: "DESK-05" + verification: + - kind: other + ref: "cargo check (apps/desktop/src-tauri) -> Finished" + status: pass + human_judgment: false + +duration: 13min +completed: 2026-09-16 +status: complete +--- + +# Phase 18 Plan 01: Desktop-Paket-Durchstich (Skript -> Abbild -> API) Summary + +**Linux-AppImage aus dem Tauri-Bau wird per neuem `.gitea/scripts/desktop-collect.sh` unter kanonischem Namen samt `manifest.json` eingesammelt, vom neu gebauten API-Abbild (`apps/api/src/desktop/`) ohne Anmeldung ausgeliefert (`GET /desktop/latest`, `GET /desktop/download/linux`), und die Client-Version stammt ab sofort aus dem Freigabe-Tag (`desktop-version.sh`, Basislinie 1.1.0).** + +## Performance + +- **Duration:** 13 min +- **Started:** 2026-09-16T13:58:00Z (geschaetzt) +- **Completed:** 2026-09-16T14:11:25Z +- **Tasks:** 2 +- **Files modified:** 15 + +## Accomplishments +- Neues API-Modul `apps/api/src/desktop/` mit `GET /desktop/latest` (200 mit Version/Kanal/Dateiliste, 404 ohne Manifest) und `GET /desktop/download/:platform` (Stream mit `Content-Disposition: attachment`, Plattform-Whitelist vor jedem Dateisystemzugriff, Traversal/unbekannte Plattform -> 400, fehlende Pakete/manipulierte Namen -> 404) — 8 gruene Spec-Tests via echtem HTTP-Durchstich (`NestFactory.create` + `app.listen(0)`, kein `fs`-Mock). +- `.gitea/scripts/desktop-collect.sh` sammelt das gebaute AppImage ein, benennt es kanonisch (`Tessera-{Version}{Suffix}.AppImage`) und schreibt `manifest.json` (Version, Kanal, Commit, Groesse, SHA-256) — Kanalmodell identisch zu `publish-images.sh` (main=beta, Tag=live, sonst dev); Windows-Zweig bereits angelegt, aber in diesem Plan noch nicht gefordert (kommt in 18-05). +- `.gitea/scripts/desktop-version.sh` schreibt die reine `X.Y.Z`-Version des letzten Freigabe-Tags in `tauri.conf.json`/`Cargo.toml`, verweigert jede Vorab-/Metadatenform (Pitfall 2 — NSIS-Ressourcen sind rein numerisch); Basislinie `1.1.0` (aktueller Tag `v1.1.0`) in allen vier Client-Dateien eingecheckt, `cargo check` bleibt gruen. +- Lokaler Durchstich zweimal bewiesen: einmal mit dem Phase-6-AppImage (Version 0.0.1) fuer Task 1, einmal mit einem frisch gebauten AppImage (Version 1.1.0, Task 2) — beide Male liefert das neu gebaute API-Abbild die Datei ueber `/desktop/download/linux` und `/api-proxy/desktop/latest` (Web-Container) korrekt aus. Der lokale Stack steht am Ende auf der finalen 1.1.0-Baseline. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Ein Linux-Paket aus dem Bau bis zum Download aus der API — eine Strecke** - `ae8fecb` (feat) +2. **Task 2: Die Version kommt aus dem Freigabe-Tag — Skript und Basislinie 1.1.0** - `614289a` (feat) + +**Plan metadata:** commit pending (this SUMMARY + STATE.md/ROADMAP.md/REQUIREMENTS.md) + +## Files Created/Modified +- `.gitea/scripts/desktop-collect.sh` - Pakete einsammeln, umbenennen, `manifest.json` schreiben (Kanalmodell, `--require linux[,windows]`) +- `.gitea/scripts/desktop-version.sh` - Version aus dem letzten Freigabe-Tag in `tauri.conf.json`/`Cargo.toml` schreiben, `--print`-Option +- `.gitignore` - `desktop-dist/*` ignoriert, `!desktop-dist/.gitkeep` als versionierter Platzhalter +- `apps/api/Dockerfile` - `COPY desktop-dist ./desktop-dist` vor `USER nestjs` +- `apps/api/src/app.module.ts` - `DesktopModule` registriert (hinter `HealthModule`) +- `apps/api/src/desktop/desktop.module.ts` - Modul-Verdrahtung (Vorbild `health.module.ts`) +- `apps/api/src/desktop/desktop.controller.ts` - `GET /desktop/latest`, `GET /desktop/download/:platform`, beide `@Public()`, `@Inject(DesktopService)` explizit +- `apps/api/src/desktop/desktop.service.ts` - Manifest lesen, `PLATFORMS`-Whitelist, Datei-Stream, 6-stufige Sicherheitspruefung in `getPackage()` +- `apps/api/src/desktop/desktop.service.spec.ts` - HTTP-Durchstich-Spec (8 Tests, echtes Temp-Verzeichnis, unabhaengig berechneter SHA-256) +- `packages/shared/src/index.ts` - `DesktopPlatform`, `DesktopManifestFile`, `DesktopManifest`, `DesktopLatestFile`, `DesktopLatestResponse` +- `desktop-dist/.gitkeep` - Platzhalter, damit `docker build` auch ohne CI-Pakete funktioniert +- `apps/desktop/package.json`, `apps/desktop/src-tauri/tauri.conf.json`, `apps/desktop/src-tauri/Cargo.toml`, `apps/desktop/src-tauri/Cargo.lock` - Basislinie `1.1.0` + +## Decisions Made +- `@Inject(DesktopService)` explizit auf dem Controller-Konstruktor gesetzt, weil Vitest ueber esbuild transpiliert (kein `emitDecoratorMetadata`) — ohne den expliziten Token bleibt die Abhaengigkeit im echten `NestFactory.create()`-Durchstich `undefined`, obwohl `nest build` (tsc) denselben Code ohne `@Inject()` korrekt aufloest. Kein Verhaltensunterschied im Produktionsbau, nur eine Testinfrastruktur-Notwendigkeit fuer den in RESEARCH/PATTERNS vorgeschlagenen echten HTTP-Durchstich ohne `fs`-Mocks. +- Lokaler Docker-Stack (API-Container) wurde zweimal neu gebaut — einmal je Task — statt nur am Ende, damit jede der drei automatisierten ``-Stufen tatsaechlich gegen den zu diesem Zeitpunkt gueltigen `desktop-dist`-Inhalt prueft, und der Stack am Ende in einem konsistenten 1.1.0-Zustand stehen bleibt (nicht mit der Task-1-Zwischenversion 0.0.1). +- Testobjekt fuer Task 1: das bereits vorhandene Phase-6-AppImage (`Tessera_0.0.1_amd64.AppImage`, 106.461.688 Bytes, SHA-256 `ea5e1ef5...`) wurde direkt verwendet, wie im Plan als zulaessige Abkuerzung vorgesehen ("Liegt ... noch das AppImage aus Phase 6, reicht es fuer diesen Durchstich"). Fuer Task 2 war ein frischer Bau mit der neuen Version 1.1.0 zwingend (Basislinie-Nachweis). + +## AppImage-Baudaten (Auftrag des Output-Abschnitts) +- **Task 1 (Testobjekt Phase-6-AppImage, kein frischer Bau):** `Tessera_0.0.1_amd64.AppImage`, 106.461.688 Bytes, SHA-256 `ea5e1ef56c282009ab8c20adbf84dbdb8b3fc29e777884817d50c7ad44bfb0ec` (Build-Datum 25. Juni, aus einer fruaheren Sitzung — nicht in dieser Sitzung neu gebaut). +- **Task 2 (frischer Bau mit Basislinie 1.1.0):** `Tessera_1.1.0_amd64.AppImage`, 106.928.632 Bytes, SHA-256 `da38fd89ced60c91e4a32929f43dfdc1435efdc8b668cddb2f47c9348010fcb4`. `pnpm --filter @tessera/desktop exec tauri build --bundles appimage` lief bei warmem `target/`-Verzeichnis (nach Entfernen des alten `bundle/`-Ordners) — Rust-Kompilierung 40,99 s laut `cargo`-Ausgabe, Gesamtlauf (inkl. Bundling) rund 2,5 Minuten Wanduhrzeit (14:06:56Z Start bis 14:09:39Z Manifest-Buildzeit). + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] `@Inject(DesktopService)` noetig fuer den HTTP-Durchstich-Test unter Vitest** +- **Found during:** Task 1 (erster Testlauf von `desktop.service.spec.ts`) +- **Issue:** Alle 5 HTTP-abhaengigen Tests scheiterten mit 500 ("Cannot read properties of undefined (reading 'getLatest')"). Ursache: Vitest transpiliert `.ts`-Dateien ueber esbuild, das `emitDecoratorMetadata` (TypeScript-Compiler-Feature) nicht abbildet — NestJS' automatische Konstruktor-Injection stuetzt sich normalerweise auf die von `tsc` erzeugten `design:paramtypes`-Metadaten, die unter esbuild fehlen. Ein echter `NestFactory.create()`-Bau (wie ihn RESEARCH/PATTERNS fuer den fs-mock-freien Test vorschlagen) konnte `DesktopService` deshalb nicht automatisch in `DesktopController` injizieren. +- **Fix:** Expliziten Injection-Token per `@Inject(DesktopService)` auf dem Konstruktor-Parameter ergaenzt — das macht die Abhaengigkeit unabhaengig von `design:paramtypes` explizit und funktioniert sowohl unter Vitest/esbuild als auch im echten `nest build` (tsc) unveraendert. +- **Files modified:** `apps/api/src/desktop/desktop.controller.ts` +- **Verification:** Alle 8 Spec-Tests gruen nach der Aenderung (`pnpm --filter @tessera/api exec vitest run src/desktop`). +- **Committed in:** `ae8fecb` (Task 1 commit) + +--- + +**Total deviations:** 1 auto-fixed (1 blocking) +**Impact on plan:** Notwendig, um den vom Plan geforderten fs-mock-freien HTTP-Durchstich-Test ueberhaupt lauffaehig zu machen. Keine Verhaltensaenderung im Produktionscode, keine Ausweitung des Umfangs. + +## Issues Encountered +None. + +## User Setup Required +None - no external service configuration required. + +## Next Phase Readiness +- Die duenne Strecke Skript -> Abbild -> API ist bewiesen; 18-02 (CI-Pipeline, `desktop`-Job, `publish-release.sh`-Erweiterung) kann direkt auf `desktop-collect.sh`/`desktop-version.sh` und dem API-Modul aufbauen. +- `packages/shared`-Typen (`DesktopLatestResponse` etc.) stehen fuer 18-03 (Web-Oberflaeche) und 18-04 (Client-Versionspruefung) bereit. +- Kein Blocker. Der Windows-Cross-Bau (cargo-xwin, NSIS) ist NICHT Teil dieses Plans — `desktop-collect.sh` hat den Windows-Zweig bereits vorbereitet (ungetestet), 18-05 baut ihn aus und beweist ihn in der Pipeline. + +--- +*Phase: 18-desktop-client-fertigstellen* +*Completed: 2026-09-16*