feat: mode application de bureau + packaging Windows autonome

Permet de distribuer l'application en executable Windows double-clic,
sans Python/Node/terminal pour l'utilisateur final.

- `desktop.py` : demarre le serveur sur un port libre dans un thread et
  ouvre une fenetre native (pywebview). `PLESNA_DEBUG=1` active les
  DevTools. Commande `plesna-gerance desktop` (import paresseux).
- `paths.py` : resolution centralisee des chemins selon le contexte
  (dev, executable PyInstaller, surcharge par variables d'env). Les
  donnees vont dans un dossier utilisateur inscriptible (%APPDATA%),
  les ressources dans le bundle. connection.py/storage.py utilisent
  `get_data_dir()`, app.py `resource_path()`.
- app.py : type MIME `.mjs` force (modules ES / worker PDF.js sous
  Windows).
- Groupes de deps optionnels desktop / desktop-linux / build.
- packaging/ : spec PyInstaller, build_windows.ps1, installeur Inno
  Setup. Workflow GitHub build-windows.yml (runner Windows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 17:21:27 +02:00
parent 7cebc0bb3d
commit a90d1c4f64
15 changed files with 912 additions and 29 deletions

View File

@@ -1,7 +1,7 @@
"""Application FastAPI pour l'extraction de comptes rendus de gérance."""
import mimetypes
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI
from fastapi.responses import FileResponse
@@ -9,6 +9,7 @@ from fastapi.staticfiles import StaticFiles
from .. import __version__
from ..database import init_db
from ..paths import resource_path
from .routes import (
analytics_router,
config_router,
@@ -20,6 +21,10 @@ from .routes import (
tags_router,
)
# Garantit le bon type MIME pour les modules ES (worker PDF.js notamment),
# indépendamment du registre système (Windows peut ne pas connaître .mjs).
mimetypes.add_type("text/javascript", ".mjs")
@asynccontextmanager
async def lifespan(app: FastAPI):
@@ -67,8 +72,8 @@ async def health() -> dict:
return {"status": "ok"}
# Determine the frontend dist path
FRONTEND_DIST = Path(__file__).parent.parent.parent.parent / "frontend" / "dist"
# Determine the frontend dist path (works both from source and when packaged)
FRONTEND_DIST = resource_path("frontend", "dist")
# Mount static files for production (if dist exists)