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:
70
src/plesna_gerance/paths.py
Normal file
70
src/plesna_gerance/paths.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Résolution centralisée des chemins (données utilisateur et ressources).
|
||||
|
||||
Gère trois contextes d'exécution :
|
||||
|
||||
- **Développement** : lancé depuis les sources. Les données vivent dans
|
||||
``<racine_projet>/data`` et les ressources (frontend buildé) dans
|
||||
``<racine_projet>/frontend/dist``.
|
||||
- **Exécutable empaqueté** (PyInstaller, ex. ``.exe`` Windows) : les données
|
||||
doivent aller dans un dossier utilisateur inscriptible (``%APPDATA%`` sous
|
||||
Windows) — l'exécutable lui-même est souvent en lecture seule
|
||||
(``Program Files``). Les ressources sont extraites dans ``sys._MEIPASS``.
|
||||
- **Surcharge explicite** via variables d'environnement (tests, Docker).
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
APP_NAME = "PlesnaGerance"
|
||||
|
||||
|
||||
def is_frozen() -> bool:
|
||||
"""Vrai si on tourne depuis un exécutable PyInstaller."""
|
||||
return getattr(sys, "frozen", False)
|
||||
|
||||
|
||||
def get_bundle_dir() -> Path:
|
||||
"""Répertoire racine des ressources embarquées (lecture seule).
|
||||
|
||||
- Empaqueté : ``sys._MEIPASS`` (dossier d'extraction PyInstaller), avec
|
||||
repli sur le dossier de l'exécutable.
|
||||
- Développement : racine du projet (``src/plesna_gerance/paths.py`` -> 3 crans).
|
||||
"""
|
||||
if is_frozen():
|
||||
meipass = getattr(sys, "_MEIPASS", None)
|
||||
if meipass:
|
||||
return Path(meipass)
|
||||
return Path(sys.executable).resolve().parent
|
||||
return Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
|
||||
def resource_path(*parts: str) -> Path:
|
||||
"""Chemin d'une ressource embarquée (ex. ``resource_path('frontend', 'dist')``)."""
|
||||
return get_bundle_dir().joinpath(*parts)
|
||||
|
||||
|
||||
def get_data_dir() -> Path:
|
||||
"""Répertoire inscriptible des données utilisateur (DB, documents).
|
||||
|
||||
Priorité :
|
||||
1. ``PLESNA_DATA_DIR`` si défini ;
|
||||
2. dossier applicatif utilisateur si empaqueté ;
|
||||
3. ``<racine_projet>/data`` en développement (comportement historique).
|
||||
"""
|
||||
env = os.environ.get("PLESNA_DATA_DIR")
|
||||
if env:
|
||||
return Path(env)
|
||||
|
||||
if is_frozen():
|
||||
if sys.platform == "win32":
|
||||
base = Path(os.environ.get("APPDATA") or Path.home())
|
||||
elif sys.platform == "darwin":
|
||||
base = Path.home() / "Library" / "Application Support"
|
||||
else:
|
||||
base = Path(
|
||||
os.environ.get("XDG_DATA_HOME") or (Path.home() / ".local" / "share")
|
||||
)
|
||||
return base / APP_NAME
|
||||
|
||||
return get_bundle_dir() / "data"
|
||||
Reference in New Issue
Block a user