[CmdletBinding()] param( [Parameter(Position = 0)] [ValidateSet('menu', 'start', 'restart', 'stop', 'status')] [string]$Action = 'menu', [ValidateSet('split', 'wails')] [string]$Mode = 'split' ) $ErrorActionPreference = 'Stop' $Root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path $RunDir = Join-Path $Root '.run' $StatePath = Join-Path $RunDir 'project-processes.json' $BackendExe = Join-Path $RunDir 'smart-parking-backend.exe' $WailsGoCache = Join-Path $Root '.gocache-wails' $BackendBuildLog = Join-Path $RunDir 'backend-build.log' $BackendStdout = Join-Path $RunDir 'backend.stdout.log' $BackendStderr = Join-Path $RunDir 'backend.stderr.log' $FrontendStdout = Join-Path $RunDir 'frontend.stdout.log' $FrontendStderr = Join-Path $RunDir 'frontend.stderr.log' $WailsStdout = Join-Path $RunDir 'wails.stdout.log' $WailsStderr = Join-Path $RunDir 'wails.stderr.log' function Ensure-RunDirectory { if (-not (Test-Path -LiteralPath $RunDir)) { New-Item -ItemType Directory -Path $RunDir | Out-Null } } function Read-State { if (-not (Test-Path -LiteralPath $StatePath)) { return $null } try { return Get-Content -LiteralPath $StatePath -Raw | ConvertFrom-Json } catch { Write-Warning "Ignoring invalid state file: $StatePath" return $null } } function Write-State([string]$RunMode, [array]$Processes) { Ensure-RunDirectory [ordered]@{ root = $Root mode = $RunMode started_at = (Get-Date).ToString('o') processes = $Processes } | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $StatePath -Encoding UTF8 } function Remove-State { if (Test-Path -LiteralPath $StatePath) { Remove-Item -LiteralPath $StatePath -Force } } function Stop-ProcessTree([int]$ProcessId) { if ($ProcessId -le 0) { return } $process = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue if ($null -eq $process) { return } Write-Host "Stopping $($process.ProcessName) (PID $ProcessId)" & taskkill.exe /PID $ProcessId /T /F 2>$null | Out-Null } function Stop-ManagedProcesses { $state = Read-State if ($null -eq $state) { Write-Host 'No managed project state found.' return } foreach ($item in @($state.processes)) { Stop-ProcessTree ([int]$item.pid) } Remove-State Write-Host 'Project processes stopped.' } function Assert-Command([string]$Name) { if ($null -eq (Get-Command $Name -ErrorAction SilentlyContinue)) { throw "Required command not found: $Name" } } function Wait-Http([string]$Url, [int]$TimeoutSeconds = 30) { for ($i = 0; $i -lt $TimeoutSeconds; $i++) { try { $response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) { return $true } } catch { } Start-Sleep -Seconds 1 } return $false } function Start-SplitMode { Assert-Command 'go' Assert-Command 'npm' Ensure-RunDirectory Write-Host 'Building backend...' Push-Location $Root try { & go build -o $BackendExe .\cmd_backend_only\ 2>&1 | Tee-Object -FilePath $BackendBuildLog if ($LASTEXITCODE -ne 0) { throw 'Backend build failed.' } } finally { Pop-Location } $backend = Start-Process -FilePath $BackendExe -WorkingDirectory $Root ` -RedirectStandardOutput $BackendStdout -RedirectStandardError $BackendStderr -PassThru $frontend = Start-Process -FilePath 'npm.cmd' -ArgumentList @('run', 'dev') ` -WorkingDirectory (Join-Path $Root 'frontend') ` -RedirectStandardOutput $FrontendStdout -RedirectStandardError $FrontendStderr -PassThru Write-State 'split' @( [ordered]@{ name = 'backend'; pid = $backend.Id; port = 8888 }, [ordered]@{ name = 'frontend'; pid = $frontend.Id; port = 5173 } ) if (-not (Wait-Http 'http://127.0.0.1:8888/health')) { Stop-ManagedProcesses throw "Backend did not become ready. See $BackendStdout and $BackendStderr" } Write-Host 'Backend ready: http://127.0.0.1:8888' Write-Host 'Frontend: http://localhost:5173' } function Start-WailsMode { Assert-Command 'wails' Ensure-RunDirectory New-Item -ItemType Directory -Path $WailsGoCache -Force | Out-Null # Keep Wails/Go build artifacts in the project workspace. The default # user cache can be inaccessible on locked-down Windows installations. $env:GOCACHE = $WailsGoCache $wails = Start-Process -FilePath 'wails' -ArgumentList @('dev') ` -WorkingDirectory $Root -RedirectStandardOutput $WailsStdout ` -RedirectStandardError $WailsStderr -PassThru Write-State 'wails' @([ordered]@{ name = 'wails'; pid = $wails.Id; port = 5173 }) Write-Host "Wails dev started (PID $($wails.Id)). See $WailsStdout and $WailsStderr" } function Show-Status { $state = Read-State if ($null -eq $state) { Write-Host 'Project is not managed by this script.' return } Write-Host "Mode: $($state.mode)" Write-Host "Started: $($state.started_at)" foreach ($item in @($state.processes)) { $process = Get-Process -Id ([int]$item.pid) -ErrorAction SilentlyContinue $status = if ($null -eq $process) { 'stopped' } else { 'running' } Write-Host ("{0}: PID {1}, port {2}, {3}" -f $item.name, $item.pid, $item.port, $status) } } function Show-Menu { while ($true) { Clear-Host Write-Host '========================================' -ForegroundColor Cyan Write-Host ' Smart Parking Project Manager' -ForegroundColor Cyan Write-Host '========================================' Write-Host '1. Start project' Write-Host '2. Stop project' Write-Host '3. Restart project' Write-Host '4. Show status' Write-Host '0. Exit' Write-Host '' $choice = Read-Host 'Select action' if ($choice -eq '1') { if ($null -ne (Read-State)) { Write-Host 'Project is already running; use stop or restart.' -ForegroundColor Yellow } elseif ($Mode -eq 'wails') { Start-WailsMode } else { Start-SplitMode } } elseif ($choice -eq '2') { Stop-ManagedProcesses } elseif ($choice -eq '3') { Stop-ManagedProcesses if ($Mode -eq 'wails') { Start-WailsMode } else { Start-SplitMode } } elseif ($choice -eq '4') { Show-Status } elseif ($choice -eq '0') { return } else { Write-Host 'Invalid option.' -ForegroundColor Red } if ($choice -ne '0') { Write-Host '' Read-Host 'Press Enter to return to menu' } } } switch ($Action) { 'menu' { Show-Menu } 'stop' { Stop-ManagedProcesses } 'status' { Show-Status } 'restart' { Stop-ManagedProcesses if ($Mode -eq 'wails') { Start-WailsMode } else { Start-SplitMode } } 'start' { if ($null -ne (Read-State)) { Write-Host 'A managed instance already exists; use restart or stop first.' exit 1 } if ($Mode -eq 'wails') { Start-WailsMode } else { Start-SplitMode } } }