manage-project.ps1 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. [CmdletBinding()]
  2. param(
  3. [Parameter(Position = 0)]
  4. [ValidateSet('menu', 'start', 'restart', 'stop', 'status')]
  5. [string]$Action = 'menu',
  6. [ValidateSet('split', 'wails')]
  7. [string]$Mode = 'split'
  8. )
  9. $ErrorActionPreference = 'Stop'
  10. $Root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
  11. $RunDir = Join-Path $Root '.run'
  12. $StatePath = Join-Path $RunDir 'project-processes.json'
  13. $BackendExe = Join-Path $RunDir 'smart-parking-backend.exe'
  14. $WailsGoCache = Join-Path $Root '.gocache-wails'
  15. $BackendBuildLog = Join-Path $RunDir 'backend-build.log'
  16. $BackendStdout = Join-Path $RunDir 'backend.stdout.log'
  17. $BackendStderr = Join-Path $RunDir 'backend.stderr.log'
  18. $FrontendStdout = Join-Path $RunDir 'frontend.stdout.log'
  19. $FrontendStderr = Join-Path $RunDir 'frontend.stderr.log'
  20. $WailsStdout = Join-Path $RunDir 'wails.stdout.log'
  21. $WailsStderr = Join-Path $RunDir 'wails.stderr.log'
  22. function Ensure-RunDirectory {
  23. if (-not (Test-Path -LiteralPath $RunDir)) {
  24. New-Item -ItemType Directory -Path $RunDir | Out-Null
  25. }
  26. }
  27. function Read-State {
  28. if (-not (Test-Path -LiteralPath $StatePath)) { return $null }
  29. try {
  30. return Get-Content -LiteralPath $StatePath -Raw | ConvertFrom-Json
  31. } catch {
  32. Write-Warning "Ignoring invalid state file: $StatePath"
  33. return $null
  34. }
  35. }
  36. function Write-State([string]$RunMode, [array]$Processes) {
  37. Ensure-RunDirectory
  38. [ordered]@{
  39. root = $Root
  40. mode = $RunMode
  41. started_at = (Get-Date).ToString('o')
  42. processes = $Processes
  43. } | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $StatePath -Encoding UTF8
  44. }
  45. function Remove-State {
  46. if (Test-Path -LiteralPath $StatePath) {
  47. Remove-Item -LiteralPath $StatePath -Force
  48. }
  49. }
  50. function Stop-ProcessTree([int]$ProcessId) {
  51. if ($ProcessId -le 0) { return }
  52. $process = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
  53. if ($null -eq $process) { return }
  54. Write-Host "Stopping $($process.ProcessName) (PID $ProcessId)"
  55. & taskkill.exe /PID $ProcessId /T /F 2>$null | Out-Null
  56. }
  57. function Stop-ManagedProcesses {
  58. $state = Read-State
  59. if ($null -eq $state) {
  60. Write-Host 'No managed project state found.'
  61. return
  62. }
  63. foreach ($item in @($state.processes)) {
  64. Stop-ProcessTree ([int]$item.pid)
  65. }
  66. Remove-State
  67. Write-Host 'Project processes stopped.'
  68. }
  69. function Assert-Command([string]$Name) {
  70. if ($null -eq (Get-Command $Name -ErrorAction SilentlyContinue)) {
  71. throw "Required command not found: $Name"
  72. }
  73. }
  74. function Wait-Http([string]$Url, [int]$TimeoutSeconds = 30) {
  75. for ($i = 0; $i -lt $TimeoutSeconds; $i++) {
  76. try {
  77. $response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
  78. if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) { return $true }
  79. } catch { }
  80. Start-Sleep -Seconds 1
  81. }
  82. return $false
  83. }
  84. function Start-SplitMode {
  85. Assert-Command 'go'
  86. Assert-Command 'npm'
  87. Ensure-RunDirectory
  88. Write-Host 'Building backend...'
  89. Push-Location $Root
  90. try {
  91. & go build -o $BackendExe .\cmd_backend_only\ 2>&1 | Tee-Object -FilePath $BackendBuildLog
  92. if ($LASTEXITCODE -ne 0) { throw 'Backend build failed.' }
  93. } finally {
  94. Pop-Location
  95. }
  96. $backend = Start-Process -FilePath $BackendExe -WorkingDirectory $Root `
  97. -RedirectStandardOutput $BackendStdout -RedirectStandardError $BackendStderr -PassThru
  98. $frontend = Start-Process -FilePath 'npm.cmd' -ArgumentList @('run', 'dev') `
  99. -WorkingDirectory (Join-Path $Root 'frontend') `
  100. -RedirectStandardOutput $FrontendStdout -RedirectStandardError $FrontendStderr -PassThru
  101. Write-State 'split' @(
  102. [ordered]@{ name = 'backend'; pid = $backend.Id; port = 8888 },
  103. [ordered]@{ name = 'frontend'; pid = $frontend.Id; port = 5173 }
  104. )
  105. if (-not (Wait-Http 'http://127.0.0.1:8888/health')) {
  106. Stop-ManagedProcesses
  107. throw "Backend did not become ready. See $BackendStdout and $BackendStderr"
  108. }
  109. Write-Host 'Backend ready: http://127.0.0.1:8888'
  110. Write-Host 'Frontend: http://localhost:5173'
  111. }
  112. function Start-WailsMode {
  113. Assert-Command 'wails'
  114. Ensure-RunDirectory
  115. New-Item -ItemType Directory -Path $WailsGoCache -Force | Out-Null
  116. # Keep Wails/Go build artifacts in the project workspace. The default
  117. # user cache can be inaccessible on locked-down Windows installations.
  118. $env:GOCACHE = $WailsGoCache
  119. $wails = Start-Process -FilePath 'wails' -ArgumentList @('dev') `
  120. -WorkingDirectory $Root -RedirectStandardOutput $WailsStdout `
  121. -RedirectStandardError $WailsStderr -PassThru
  122. Write-State 'wails' @([ordered]@{ name = 'wails'; pid = $wails.Id; port = 5173 })
  123. Write-Host "Wails dev started (PID $($wails.Id)). See $WailsStdout and $WailsStderr"
  124. }
  125. function Show-Status {
  126. $state = Read-State
  127. if ($null -eq $state) {
  128. Write-Host 'Project is not managed by this script.'
  129. return
  130. }
  131. Write-Host "Mode: $($state.mode)"
  132. Write-Host "Started: $($state.started_at)"
  133. foreach ($item in @($state.processes)) {
  134. $process = Get-Process -Id ([int]$item.pid) -ErrorAction SilentlyContinue
  135. $status = if ($null -eq $process) { 'stopped' } else { 'running' }
  136. Write-Host ("{0}: PID {1}, port {2}, {3}" -f $item.name, $item.pid, $item.port, $status)
  137. }
  138. }
  139. function Show-Menu {
  140. while ($true) {
  141. Clear-Host
  142. Write-Host '========================================' -ForegroundColor Cyan
  143. Write-Host ' Smart Parking Project Manager' -ForegroundColor Cyan
  144. Write-Host '========================================'
  145. Write-Host '1. Start project'
  146. Write-Host '2. Stop project'
  147. Write-Host '3. Restart project'
  148. Write-Host '4. Show status'
  149. Write-Host '0. Exit'
  150. Write-Host ''
  151. $choice = Read-Host 'Select action'
  152. if ($choice -eq '1') {
  153. if ($null -ne (Read-State)) {
  154. Write-Host 'Project is already running; use stop or restart.' -ForegroundColor Yellow
  155. } elseif ($Mode -eq 'wails') {
  156. Start-WailsMode
  157. } else {
  158. Start-SplitMode
  159. }
  160. } elseif ($choice -eq '2') {
  161. Stop-ManagedProcesses
  162. } elseif ($choice -eq '3') {
  163. Stop-ManagedProcesses
  164. if ($Mode -eq 'wails') { Start-WailsMode } else { Start-SplitMode }
  165. } elseif ($choice -eq '4') {
  166. Show-Status
  167. } elseif ($choice -eq '0') {
  168. return
  169. } else {
  170. Write-Host 'Invalid option.' -ForegroundColor Red
  171. }
  172. if ($choice -ne '0') {
  173. Write-Host ''
  174. Read-Host 'Press Enter to return to menu'
  175. }
  176. }
  177. }
  178. switch ($Action) {
  179. 'menu' {
  180. Show-Menu
  181. }
  182. 'stop' {
  183. Stop-ManagedProcesses
  184. }
  185. 'status' {
  186. Show-Status
  187. }
  188. 'restart' {
  189. Stop-ManagedProcesses
  190. if ($Mode -eq 'wails') { Start-WailsMode } else { Start-SplitMode }
  191. }
  192. 'start' {
  193. if ($null -ne (Read-State)) {
  194. Write-Host 'A managed instance already exists; use restart or stop first.'
  195. exit 1
  196. }
  197. if ($Mode -eq 'wails') { Start-WailsMode } else { Start-SplitMode }
  198. }
  199. }