#Requires -Version 5.1 <# .SYNOPSIS Construit l'installeur de BUREAU Windows de LoreMind (.msi), sans Docker. .DESCRIPTION Pipeline complet "local-first" : 1. Build du front Angular (web/ -> web/dist/web) 2. Prep du Brain (Python embeddable) (brain/ -> dist-embed : python.exe signe PSF + deps + sources) 3. Build du Core en fat jar + front (core/ -> target/*.jar, profil Maven "desktop") 4. Assemblage de la charge utile (jar + brain) dans un dossier d'entree jpackage 5. jpackage -> installeur .msi avec JRE embarque L'app resultante se lance d'un double-clic : le Core demarre en profil Spring "local" (H2 fichier + stockage filesystem) et lance lui-meme le Brain en sidecar. Aucune dependance externe a installer cote utilisateur (ni Docker, ni Java, ni Python). L'IA fonctionne via le cloud (1min.ai / Gemini / Mistral...) ou via Ollama si present. .PARAMETER Version Version de l'installeur (X.Y.Z, numerique). Defaut : derivee de core/pom.xml (le suffixe -beta est retire car les MSI n'acceptent qu'une version numerique). .PARAMETER SkipFront / SkipBrain / SkipJar Sauter une etape (build incrementaux pendant la mise au point). .PREREQUIS (sur la machine de build uniquement, PAS chez l'utilisateur final) - JDK 21+ avec jpackage dans le PATH (Temurin OK). - WiX Toolset v3 (https://github.com/wixtoolset/wix3/releases) — requis par jpackage pour produire un .msi sur Windows. - Node.js + npm (build Angular). - Python + pip (telecharge les wheels cp312 du Brain ; toute version 3.x convient, on cible explicitement 3.12 via --python-version) + acces Internet (python.org). .NOTES Projet : LoreMind — assistant pour Maitres de Jeu de JDR Licence : AGPL-3.0 #> [CmdletBinding()] param( [string]$Version, [switch]$SkipFront, [switch]$SkipBrain, [switch]$SkipJar ) $ErrorActionPreference = 'Stop' function Write-Step($m) { Write-Host "==> $m" -ForegroundColor Cyan } function Write-Ok($m) { Write-Host " OK $m" -ForegroundColor Green } function Write-Err($m) { Write-Host " XX $m" -ForegroundColor Red } # --- Chemins --------------------------------------------------------------- $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path $WebDir = Join-Path $RepoRoot 'web' $BrainDir = Join-Path $RepoRoot 'brain' $CoreDir = Join-Path $RepoRoot 'core' $StageDir = Join-Path $CoreDir 'target\dist-input' # charge utile jpackage $OutDir = Join-Path $CoreDir 'target\dist-out' # .msi produit # --- Version (numerique pour MSI) ------------------------------------------ if (-not $Version) { $pom = Get-Content (Join-Path $CoreDir 'pom.xml') -Raw # IMPORTANT : viser la version DU PROJET, pas le premier du fichier # (qui est celui du parent spring-boot-starter-parent). On l'ancre sur # l'artifactId du projet, puis on garde la partie numerique (MSI = X.Y.Z). if ($pom -match 'loremind-core\s*([0-9]+\.[0-9]+\.[0-9]+)') { $Version = $Matches[1] } else { $Version = '0.0.0' } } Write-Host "" Write-Host "============================================================" -ForegroundColor Magenta Write-Host " LoreMind - Build installeur bureau Windows (v$Version)" -ForegroundColor Magenta Write-Host "============================================================" -ForegroundColor Magenta # --- Verif outils ---------------------------------------------------------- if (-not (Get-Command jpackage -ErrorAction SilentlyContinue)) { Write-Err "jpackage introuvable dans le PATH. Installez un JDK 21+ (Temurin)." exit 1 } # --- 1. Front Angular ------------------------------------------------------ if (-not $SkipFront) { Write-Step "Build du front Angular" Push-Location $WebDir try { if (-not (Test-Path 'node_modules')) { npm ci } npm run build if ($LASTEXITCODE -ne 0) { throw "Echec du build Angular" } } finally { Pop-Location } Write-Ok "Front construit (web/dist/web)" } else { Write-Step "Front : saute (--SkipFront)" } # --- 2. Brain (Python embeddable officiel, PAS d'exe gele) ----------------- # On embarque le Python *embeddable* de python.org (python.exe SIGNE par la PSF) # + les dependances + les sources .py. Aucun executable "gele" type PyInstaller # -> plus de faux positif antivirus (le bootloader packe etait pris pour un # trojan). Le Core lancera : brain\python\python.exe brain\run_local.py $PyVersion = '3.12.8' # doit matcher python:3.12 du Docker $PyTag = 'python312' # prefixe du fichier ._pth $BrainEmbed = Join-Path $BrainDir 'dist-embed' # staging du brain empaquete if (-not $SkipBrain) { Write-Step "Preparation du Brain (Python embeddable $PyVersion)" if (Test-Path $BrainEmbed) { Remove-Item $BrainEmbed -Recurse -Force } $PyDir = Join-Path $BrainEmbed 'python' New-Item -ItemType Directory -Force -Path $PyDir | Out-Null # a) Telecharger + extraire le Python embeddable (zip officiel). $zip = Join-Path $BrainEmbed 'python-embed.zip' $url = "https://www.python.org/ftp/python/$PyVersion/python-$PyVersion-embed-amd64.zip" Write-Host " Telechargement $url" Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing Expand-Archive -Path $zip -DestinationPath $PyDir -Force Remove-Item $zip -Force # b) Activer site-packages dans le ._pth (sinon les deps ne sont pas importees). $pth = Join-Path $PyDir "$PyTag._pth" Set-Content -Path $pth -Encoding ascii -Value @( "$PyTag.zip", '.', 'Lib\site-packages', 'import site' ) # c) Installer les deps en wheels cp312 dans python\Lib\site-packages. # --python-version 3.12 + --only-binary : on telecharge les roues 3.12 # meme si le pip courant tourne sous une autre version de Python. $site = Join-Path $PyDir 'Lib\site-packages' New-Item -ItemType Directory -Force -Path $site | Out-Null Push-Location $BrainDir try { python -m pip install --upgrade pip *> $null python -m pip install --target $site --only-binary=:all: --python-version 3.12 -r requirements.txt if ($LASTEXITCODE -ne 0) { throw "Echec pip install (deps Brain)" } } finally { Pop-Location } # d) Copier les sources du Brain + le point d'entree. Copy-Item (Join-Path $BrainDir 'app') (Join-Path $BrainEmbed 'app') -Recurse Copy-Item (Join-Path $BrainDir 'run_local.py') (Join-Path $BrainEmbed 'run_local.py') Write-Ok "Brain prepare (brain/dist-embed : python embeddable + deps + sources)" } else { Write-Step "Brain : saute (--SkipBrain)" } # --- 3. Core (fat jar avec front embarque) --------------------------------- if (-not $SkipJar) { Write-Step "Build du Core (fat jar, profil desktop)" Push-Location $CoreDir try { # -Pdesktop : copie web/dist/web dans le jar (classpath:/static). cmd /c "mvn -q -Pdesktop -DskipTests clean package" if ($LASTEXITCODE -ne 0) { throw "Echec du build Maven" } } finally { Pop-Location } Write-Ok "Core construit (core/target)" } else { Write-Step "Core : saute (--SkipJar)" } # --- 4. Assemblage de la charge utile -------------------------------------- Write-Step "Assemblage de la charge utile jpackage" if (Test-Path $StageDir) { Remove-Item $StageDir -Recurse -Force } New-Item -ItemType Directory -Force -Path $StageDir | Out-Null # Le jar repackage Spring Boot (executable) — on ignore le *.jar.original. $jar = Get-ChildItem (Join-Path $CoreDir 'target') -Filter 'loremind-core-*.jar' | Where-Object { $_.Name -notlike '*.original' } | Select-Object -First 1 if (-not $jar) { Write-Err "Jar introuvable dans core/target. Lancez sans --SkipJar."; exit 1 } Copy-Item $jar.FullName (Join-Path $StageDir 'loremind-core.jar') # Le Brain (python embeddable + deps + sources) -> stage/brain/ # stage/brain/python/python.exe , stage/brain/app , stage/brain/run_local.py if (-not (Test-Path $BrainEmbed)) { Write-Err "Brain introuvable (brain/dist-embed). Lancez sans --SkipBrain."; exit 1 } $stageBrain = Join-Path $StageDir 'brain' New-Item -ItemType Directory -Force -Path $stageBrain | Out-Null Copy-Item (Join-Path $BrainEmbed '*') $stageBrain -Recurse Write-Ok "Charge utile prete ($StageDir)" # --- 5. jpackage -> .msi --------------------------------------------------- Write-Step "Generation de l'installeur (.msi) via jpackage" if (Test-Path $OutDir) { Remove-Item $OutDir -Recurse -Force } New-Item -ItemType Directory -Force -Path $OutDir | Out-Null # $APPDIR est substitue par jpackage au LANCEMENT par le dossier 'app' de # l'install (qui contient le jar ET le dossier brain copie depuis --input). # Le Brain se lance via le python embeddable + run_local.py. brain.sidecar.command # est une liste : les deux chemins separes par une virgule (aucun chemin Windows # ne contient de virgule) sont bindes en List par Spring. $brainCmd = '$APPDIR\brain\python\python.exe,$APPDIR\brain\run_local.py' # Note : pas de --win-console (app de bureau). Les logs Spring/Brain peuvent # etre rediriges vers un fichier via une option ulterieure si besoin de debug. jpackage ` --type msi ` --name LoreMind ` --app-version $Version ` --vendor 'IGML Creation' ` --input $StageDir ` --main-jar loremind-core.jar ` --main-class org.springframework.boot.loader.launch.JarLauncher ` --dest $OutDir ` --java-options '-Dspring.profiles.active=local' ` --java-options "-Dbrain.sidecar.command=$brainCmd" ` --win-per-user-install ` --win-menu --win-menu-group 'LoreMind' ` --win-shortcut --win-dir-chooser if ($LASTEXITCODE -ne 0) { Write-Err "Echec jpackage. Cause probable : WiX Toolset v3 absent." Write-Err "Installez-le : https://github.com/wixtoolset/wix3/releases" exit 1 } $msi = Get-ChildItem $OutDir -Filter '*.msi' | Select-Object -First 1 Write-Host "" Write-Host "============================================================" -ForegroundColor Green Write-Host " Installeur genere !" -ForegroundColor Green Write-Host " $($msi.FullName)" -ForegroundColor Green Write-Host "============================================================" -ForegroundColor Green