# Build script: packages the CLI as a single self-executing .exe # # Usage: # .\build.ps1 -> dist\mitsubishi-label-converter.exe # .\build.ps1 -Clean -> remove build artifacts first # .\build.ps1 -Onefile -> single-file exe (slower start, one file) # default is one-folder mode (fast start) # .\build.ps1 -Test -> run the exe against the real assets afterwards # # Requirements: pyinstaller installed in the active environment # pip install pyinstaller [CmdletBinding()] param( [switch]$Clean, [switch]$Onefile, [switch]$Test, # Explicit interpreter, e.g. -Python C:\Path\To\python.exe [string]$Python ) $ErrorActionPreference = "Stop" $root = $PSScriptRoot $exename = "mitsubishi-label-converter" $dist = Join-Path $root "dist" # Resolve a working Python interpreter ------------------------------- # The Microsoft Store 'python' alias can shadow a real install, so verify # each candidate by actually running it. function Test-Python([string]$cmd) { if (-not $cmd) { return $false } try { & $cmd -c "import sys; sys.exit(0)" 2>$null return ($LASTEXITCODE -eq 0) } catch { return $false } } if (-not $Python) { $candidates = @( (Join-Path $root ".venv\Scripts\python.exe"), # project venv first "python", "py" ) foreach ($c in $candidates) { if (Test-Python $c) { $Python = $c; break } } } if (-not $Python -or -not (Test-Python $Python)) { Write-Error @" No working Python interpreter found. Tried: project .venv, 'python' on PATH, 'py' launcher. Create a venv (python -m venv .venv) or pass one explicitly: .\build.ps1 -Python C:\Path\To\python.exe "@ } Write-Host "==> Using Python: $Python" # Sanity checks ------------------------------------------------------------ & $Python -m PyInstaller --version 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { Write-Error "PyInstaller not found in '$Python'. Run: $Python -m pip install pyinstaller" } if ($Clean) { Write-Host "==> Cleaning build artifacts..." Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ` (Join-Path $root "build"), $dist # remove the egg-info generated by editable installs Get-ChildItem $root -Filter "*.egg-info" -Directory -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue } # Build -------------------------------------------------------------------- $modeArgs = @() if ($Onefile) { $modeArgs += "--onefile" } else { $modeArgs += "--onedir" } Write-Host "==> Building $exename ($($modeArgs -join ' '))..." & $Python -m PyInstaller @modeArgs ` --name $exename ` --noconfirm ` --clean ` --console ` --paths "$root\src" ` "$root\run.py" if ($LASTEXITCODE -ne 0) { Write-Error "PyInstaller failed with exit code $LASTEXITCODE" } # Locate the exe ----------------------------------------------------------- if ($Onefile) { $exe = Join-Path $dist "$exename.exe" } else { $exe = Join-Path $dist "$exename\$exename.exe" } if (-not (Test-Path $exe)) { Write-Error "Build finished but exe not found at $exe" } $size = "{0:N1} MB" -f ((Get-Item $exe).Length / 1MB) Write-Host "==> Built: $exe ($size)" # Optional smoke test ------------------------------------------------------- if ($Test) { Write-Host "==> Smoke testing against assets/Global.xml..." $testOut = Join-Path $dist "smoketest.xlsx" & $exe to-xlsx (Join-Path $root "assets\Global.xml") ` --sdt (Join-Path $root "assets\SDT.xml") ` -o $testOut --force if ($LASTEXITCODE -ne 0) { Write-Error "Smoke test failed" } & $exe report (Join-Path $root "assets\Global.xml") --sdt (Join-Path $root "assets\SDT.xml") if ($LASTEXITCODE -ne 0) { Write-Error "Smoke test (report) failed" } Remove-Item $testOut -Force -ErrorAction SilentlyContinue Write-Host "==> Smoke test OK" } Write-Host "" Write-Host "Done. Distribute: $exe" if (-not $Onefile) { Write-Host "Note: one-folder mode requires the whole dist\$exename folder." }