| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- [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 }
- }
- }
|