param( [string]$BackendHost = "127.0.0.1", [int]$BackendPort = 8008, [int]$FrontendPort = 5173, [int]$HealthTimeoutSeconds = 90, [string]$PythonExe = "", [ValidateSet("web", "electron")][string]$FrontendMode = "web", [switch]$SkipFrontend, [switch]$RequireA1111 ) $ErrorActionPreference = "Stop" $projectRoot = Split-Path -Parent $PSScriptRoot Set-Location $projectRoot function Resolve-PythonExe { param([string]$Override) if ($Override -and (Test-Path $Override)) { return (Resolve-Path $Override).Path } $candidates = @( (Join-Path $projectRoot ".venv\Scripts\python.exe"), (Join-Path (Split-Path -Parent $projectRoot) ".venv\Scripts\python.exe") ) foreach ($candidate in $candidates) { if (Test-Path $candidate) { return (Resolve-Path $candidate).Path } } throw "Python executable not found. Use -PythonExe ." } function Stop-PortListeners { param([int[]]$Ports) foreach ($port in $Ports) { $listeners = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue foreach ($listener in $listeners) { try { Stop-Process -Id $listener.OwningProcess -Force -ErrorAction Stop Write-Host "Stopped PID $($listener.OwningProcess) on port $port" } catch { Write-Host "Could not stop PID $($listener.OwningProcess) on port $port" } } } } function Wait-Http { param( [Parameter(Mandatory = $true)][string]$Url, [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][int]$TimeoutSeconds ) $deadline = (Get-Date).AddSeconds($TimeoutSeconds) do { try { $response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 5 if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) { Write-Host "OK $Name -> $Url" return } } catch { Start-Sleep -Milliseconds 800 } } while ((Get-Date) -lt $deadline) throw "$Name not reachable within ${TimeoutSeconds}s: $Url" } $python = Resolve-PythonExe -Override $PythonExe $logsDir = Join-Path $projectRoot "logs" New-Item -ItemType Directory -Force -Path $logsDir | Out-Null $backendLog = Join-Path $logsDir "backend.log" $backendErrLog = Join-Path $logsDir "backend.err.log" $frontendLog = Join-Path $logsDir "frontend.log" $frontendErrLog = Join-Path $logsDir "frontend.err.log" Stop-PortListeners -Ports @($BackendPort, $FrontendPort) $backendCmd = @( "Set-Location '$projectRoot'", "`$env:IMAGEFORGE_HOST='$BackendHost'", "`$env:IMAGEFORGE_PORT='$BackendPort'", "`$env:IMAGEFORGE_CORS_ORIGINS='http://localhost:$FrontendPort,http://127.0.0.1:$FrontendPort'", "& '$python' -m backend.app.main" ) -join "; " $backendProc = Start-Process -FilePath "powershell.exe" -ArgumentList @( "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $backendCmd ) -RedirectStandardOutput $backendLog -RedirectStandardError $backendErrLog -PassThru Write-Host "Backend started (PID $($backendProc.Id))." $frontendProc = $null if (-not $SkipFrontend.IsPresent) { $frontendScript = if ($FrontendMode -eq "electron") { "dev" } else { "dev:web" } $frontendCheckUrl = if ($FrontendMode -eq "electron") { "http://localhost:$FrontendPort" } else { "http://127.0.0.1:$FrontendPort" } $frontendCmd = @( "Set-Location '$projectRoot'", "npm --prefix frontend run $frontendScript" ) -join "; " $frontendProc = Start-Process -FilePath "powershell.exe" -ArgumentList @( "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $frontendCmd ) -RedirectStandardOutput $frontendLog -RedirectStandardError $frontendErrLog -PassThru Write-Host "Frontend started (PID $($frontendProc.Id))." Wait-Http -Url $frontendCheckUrl -Name "Frontend" -TimeoutSeconds $HealthTimeoutSeconds } $healthArgs = @( "-BackendUrl", "http://$BackendHost`:$BackendPort", "-TimeoutSeconds", "$HealthTimeoutSeconds" ) if ($RequireA1111.IsPresent) { $healthArgs += "-RequireA1111" } & (Join-Path $PSScriptRoot "healthcheck-stack.ps1") @healthArgs Write-Host "" Write-Host "Ready:" Write-Host "- Backend: http://$BackendHost`:$BackendPort" if (-not $SkipFrontend.IsPresent) { if ($FrontendMode -eq "electron") { Write-Host "- Frontend mode: electron (with embedded Vite on $FrontendPort)" } else { Write-Host "- Frontend (Web): http://localhost:$FrontendPort" } } Write-Host "- Backend log: $backendLog" Write-Host "- Backend error log: $backendErrLog" if (-not $SkipFrontend.IsPresent) { Write-Host "- Frontend log: $frontendLog" Write-Host "- Frontend error log: $frontendErrLog" }