param( [switch]$SkipFrontendBuild, [switch]$SkipSmoke, [string]$WebUrl = "http://127.0.0.1:3000", [string]$WebReadyUrl = "http://127.0.0.1:3000/editor", [string]$ApiHealthUrl = "http://127.0.0.1:8000/health" ) $ErrorActionPreference = "Stop" function Step($Name) { Write-Output "" Write-Output "==> $Name" } function Stop-SferaNextProcesses { $processes = Get-CimInstance Win32_Process -Filter "name = 'node.exe'" | Where-Object { $_.CommandLine -match 'sfera-web' -and ( $_.CommandLine -match 'next.*start' -or $_.CommandLine -match 'next.*build' -or $_.CommandLine -match 'npm-cli\.js.*run start' ) } if ($processes) { Write-Output "Stopping running SFERA Next.js processes." $processes | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } Start-Sleep -Seconds 2 } } function Stop-SferaApiProcesses { $processes = Get-CimInstance Win32_Process | Where-Object { ($_.Name -in @("python.exe", "uvicorn.exe")) -and $_.CommandLine -match 'api_server\.main:app' -and $_.CommandLine -match 'SFERA' } if ($processes) { Write-Output "Stopping SFERA API smoke processes." $processes | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } Start-Sleep -Seconds 1 } } function Wait-HttpOk($Url, $TimeoutSeconds = 30) { $deadline = (Get-Date).AddSeconds($TimeoutSeconds) do { try { $response = Invoke-WebRequest -UseBasicParsing -Uri $Url -TimeoutSec 15 if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) { return } } catch { Start-Sleep -Milliseconds 500 } } while ((Get-Date) -lt $deadline) throw "Timed out waiting for $Url" } function Wait-TcpEndpoint($Url, $TimeoutSeconds = 30) { $uri = [System.Uri]$Url $hostName = $uri.Host $port = $uri.Port if ($port -le 0) { $port = if ($uri.Scheme -eq "https") { 443 } else { 80 } } $deadline = (Get-Date).AddSeconds($TimeoutSeconds) do { $client = [System.Net.Sockets.TcpClient]::new() try { $async = $client.BeginConnect($hostName, $port, $null, $null) if ($async.AsyncWaitHandle.WaitOne(2000)) { $client.EndConnect($async) return } } catch { Start-Sleep -Milliseconds 500 } finally { $client.Close() } } while ((Get-Date) -lt $deadline) throw "Timed out waiting for TCP $hostName`:$port" } $Root = Resolve-Path (Join-Path $PSScriptRoot "..") $RustRoot = Join-Path $Root "rust" $FrontendRoot = "R:\codex\SFERA\frontend\sfera-web" $StartedApiForSmoke = $false $StartedWebForSmoke = $false if (-not (Test-Path $FrontendRoot)) { $FrontendRoot = Join-Path $Root "frontend\sfera-web" } Step "Rust tests and BSL parser build" Push-Location $RustRoot cargo test cargo build -p bsl-parser Pop-Location Step "Python and API tests" Push-Location $Root uv run pytest Pop-Location Step "Frontend typecheck" Push-Location $FrontendRoot npm run typecheck if (-not $SkipFrontendBuild) { Step "Frontend production build" Stop-SferaNextProcesses $env:NEXT_TELEMETRY_DISABLED = "1" node_modules\.bin\next.cmd build } if (-not $SkipSmoke) { Step "Frontend smoke checks" try { Wait-HttpOk $ApiHealthUrl 5 } catch { Write-Host "Starting API server for smoke checks." $apiStdout = Join-Path $Root ".sfera\api-stdout.log" $apiStderr = Join-Path $Root ".sfera\api-stderr.log" Start-Process -FilePath "uv.exe" ` -ArgumentList @("run", "uvicorn", "api_server.main:app", "--app-dir", "services/api-server/src", "--host", "0.0.0.0", "--port", "8000") ` -WorkingDirectory $Root ` -RedirectStandardOutput $apiStdout ` -RedirectStandardError $apiStderr $StartedApiForSmoke = $true Wait-HttpOk $ApiHealthUrl 120 } $env:SFERA_WEB_URL = $WebUrl Stop-SferaNextProcesses $runningNext = Get-CimInstance Win32_Process -Filter "name = 'node.exe'" | Where-Object { $_.CommandLine -match 'next.*start' -and $_.CommandLine -match 'sfera-web' } if (-not $runningNext) { $stdout = Join-Path $FrontendRoot ".next\start-stdout.log" $stderr = Join-Path $FrontendRoot ".next\start-stderr.log" Start-Process -FilePath "npm.cmd" ` -ArgumentList @("run", "start", "--", "--hostname", "0.0.0.0", "--port", "3000") ` -WorkingDirectory $FrontendRoot ` -RedirectStandardOutput $stdout ` -RedirectStandardError $stderr $StartedWebForSmoke = $true Wait-TcpEndpoint $WebUrl 45 } npm run smoke:editor npm run smoke:editor:runtime if ($StartedWebForSmoke) { Stop-SferaNextProcesses } if ($StartedApiForSmoke) { Stop-SferaApiProcesses } } Pop-Location Write-Output "" Write-Output "All requested checks passed."