[CmdletBinding()] param( [string]$OutputDirectory = '', [switch]$IncludeUploads, [switch]$IncludeDatabase, [switch]$SkipBuild ) $ErrorActionPreference = 'Stop' $ProjectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path if ([string]::IsNullOrWhiteSpace($OutputDirectory)) { $OutputDirectory = Join-Path $ProjectRoot 'release' } $OutputDirectory = [System.IO.Path]::GetFullPath($OutputDirectory) function Require-File([string]$Path, [string]$Message) { if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "$Message`nMissing: $Path" } } function Copy-OptionalFile([string]$Source, [string]$DestinationDirectory) { if (Test-Path -LiteralPath $Source -PathType Leaf) { Copy-Item -LiteralPath $Source -Destination $DestinationDirectory -Force return $true } return $false } function Validate-YamlWindowsPaths([string]$Path) { $lineNumber = 0 foreach ($line in Get-Content -LiteralPath $Path) { $lineNumber++ if ($line -match ':\s*"[^"]*\\[^"]*"') { throw "Invalid YAML path at $Path line $lineNumber. Use forward slashes (D:/data) or single quotes ('D:\data'), not a double-quoted backslash path." } } } if (-not $SkipBuild) { if ($null -eq (Get-Command wails -ErrorAction SilentlyContinue)) { throw 'Wails CLI was not found. Install Wails v2 before publishing.' } Write-Host 'Building Wails release...' -ForegroundColor Cyan Push-Location $ProjectRoot try { & wails build if ($LASTEXITCODE -ne 0) { throw "wails build failed with exit code $LASTEXITCODE" } } finally { Pop-Location } } $BinDirectory = Join-Path $ProjectRoot 'build\bin' $Executable = Join-Path $BinDirectory 'smart-parking.exe' $Config = Join-Path $ProjectRoot 'config.yaml' Require-File $Executable 'The Wails executable was not found. Run without -SkipBuild or build the project first.' Require-File $Config 'Production config.yaml was not found.' Validate-YamlWindowsPaths $Config $ReleaseName = 'smart-parking-' + (Get-Date -Format 'yyyyMMdd-HHmmss') $ReleaseDirectory = Join-Path $OutputDirectory $ReleaseName New-Item -ItemType Directory -Path $ReleaseDirectory -Force | Out-Null Copy-Item -LiteralPath $Executable -Destination $ReleaseDirectory -Force Copy-Item -LiteralPath $Config -Destination $ReleaseDirectory -Force $PrinterDLL = Join-Path $BinDirectory 'CsnPrinterLibs.dll' if (-not (Copy-OptionalFile $PrinterDLL $ReleaseDirectory)) { Write-Warning 'CsnPrinterLibs.dll was not found. Printing will be unavailable unless it is added before deployment.' } if ($IncludeUploads) { $Uploads = Join-Path $ProjectRoot 'uploads' if (Test-Path -LiteralPath $Uploads -PathType Container) { Copy-Item -LiteralPath $Uploads -Destination (Join-Path $ReleaseDirectory 'uploads') -Recurse -Force } else { Write-Warning 'uploads directory was not found; no local images were included.' } } if ($IncludeDatabase) { $AppData = [Environment]::GetFolderPath([Environment+SpecialFolder]::ApplicationData) $DatabaseDirectory = Join-Path $AppData 'smart-parking' $Database = Join-Path $DatabaseDirectory 'lc_garage.db' if (Test-Path -LiteralPath $Database -PathType Leaf) { $DatabasePackageDirectory = Join-Path $ReleaseDirectory 'data\AppData\smart-parking' New-Item -ItemType Directory -Path $DatabasePackageDirectory -Force | Out-Null Copy-Item -LiteralPath $Database -Destination $DatabasePackageDirectory -Force Copy-OptionalFile ($Database + '-wal') $DatabasePackageDirectory | Out-Null Copy-OptionalFile ($Database + '-shm') $DatabasePackageDirectory | Out-Null Write-Warning 'Database files were copied. Stop the source application before deployment to guarantee a consistent SQLite backup.' } else { Write-Warning "Database was not found: $Database" } } $Readme = @" Smart Parking deployment package Contents: - smart-parking.exe: Wails desktop application - config.yaml: production configuration. Review it before first start. - CsnPrinterLibs.dll: required when the CSN ticket printer is used. Prerequisites: - Windows x64 - Microsoft Edge WebView2 Evergreen Runtime - Allow inbound TCP 8888 when edge devices or the central payment machine connect to this host. Before starting: 1. Set system.gate-simulator to false in config.yaml. 2. Set the production MQTT, camera, and ffmpeg configuration in config.yaml. 3. For Windows paths in YAML, use forward slashes (D:/SmartParking/data) or single quotes ('D:\SmartParking\data'). Do not use double-quoted paths with backslashes such as "D:\SmartParking\data"; YAML treats backslashes as escapes. 4. Start smart-parking.exe with this folder as its working directory. Database: - Default path: %APPDATA%\smart-parking\lc_garage.db for the Windows account that starts the application. - If a data folder is included, copy data\AppData\smart-parking\* to that exact path after the application is stopped. Local images: - Copy the included uploads folder next to smart-parking.exe when historical local images must be retained. "@ Set-Content -LiteralPath (Join-Path $ReleaseDirectory 'DEPLOYMENT.txt') -Value $Readme -Encoding UTF8 Write-Host '' Write-Host "Release package created: $ReleaseDirectory" -ForegroundColor Green Write-Host 'Deploy the complete directory to the production machine.' -ForegroundColor Green