172 lines
5.5 KiB
PowerShell
172 lines
5.5 KiB
PowerShell
param(
|
|
[string]$BackendBaseUrl = "http://192.168.3.90:8001",
|
|
[int]$BackendPort = 8002,
|
|
[int]$FrontendPort = 3005,
|
|
[switch]$UseLocalBackend,
|
|
[switch]$SkipBaseline
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
|
$frontendDir = Join-Path $repoRoot "frontend"
|
|
$backendDir = Join-Path $repoRoot "backend"
|
|
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss"
|
|
$artifactDir = Join-Path $repoRoot "project-docs\qa-artifacts\phase2-closed-loop\$timestamp"
|
|
$frontendLog = Join-Path $artifactDir "frontend-dev-server.log"
|
|
$backendLog = Join-Path $artifactDir "backend-server.log"
|
|
$localBackendUrl = "http://127.0.0.1:$BackendPort"
|
|
$backendUrl = if ($UseLocalBackend) { $localBackendUrl } else { $BackendBaseUrl.TrimEnd('/') }
|
|
$webUrl = "http://127.0.0.1:$FrontendPort"
|
|
$runLocalBackend = [bool]$UseLocalBackend
|
|
|
|
if (-not $runLocalBackend) {
|
|
$BackendPort = 0
|
|
}
|
|
|
|
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
|
|
|
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
|
throw "gstack is required for all AI-assisted work in this repo."
|
|
}
|
|
|
|
function Wait-HttpReady {
|
|
param(
|
|
[string]$Url,
|
|
[int]$TimeoutSeconds = 90
|
|
)
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
do {
|
|
try {
|
|
$response = Invoke-WebRequest -Uri $Url -TimeoutSec 3
|
|
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) {
|
|
return
|
|
}
|
|
} catch {
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
} while ((Get-Date) -lt $deadline)
|
|
|
|
throw "Timed out waiting for $Url"
|
|
}
|
|
|
|
function Stop-Listener {
|
|
param([int]$Port)
|
|
$listener = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.State -eq "Listen" } |
|
|
Select-Object -First 1
|
|
if ($listener) {
|
|
Stop-Process -Id $listener.OwningProcess -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
function Stop-ExistingNextDevProcess {
|
|
param([string]$TargetDirectory)
|
|
|
|
$normalizedTarget = $TargetDirectory.ToLowerInvariant()
|
|
$processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.Name -eq "node.exe" -and
|
|
$_.CommandLine -and
|
|
$_.CommandLine.ToLowerInvariant().Contains($normalizedTarget) -and
|
|
(
|
|
$_.CommandLine -match "next\\dist\\bin\\next" -or
|
|
$_.CommandLine -match "start-server\.js"
|
|
)
|
|
}
|
|
|
|
foreach ($process in $processes) {
|
|
Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
$frontendProcess = $null
|
|
$backendProcess = $null
|
|
|
|
try {
|
|
if (-not (Test-Path (Join-Path $frontendDir "node_modules"))) {
|
|
Push-Location $frontendDir
|
|
npm.cmd install --no-package-lock
|
|
if ($LASTEXITCODE -ne 0) { throw "npm install failed" }
|
|
Pop-Location
|
|
}
|
|
|
|
if (-not (Test-Path (Join-Path $frontendDir "node_modules\playwright"))) {
|
|
Push-Location $frontendDir
|
|
npm.cmd install --no-save --no-package-lock playwright
|
|
if ($LASTEXITCODE -ne 0) { throw "playwright package install failed" }
|
|
Pop-Location
|
|
}
|
|
|
|
Push-Location $frontendDir
|
|
npx.cmd playwright install chromium
|
|
if ($LASTEXITCODE -ne 0) { throw "playwright browser install failed" }
|
|
Pop-Location
|
|
|
|
if (-not $SkipBaseline) {
|
|
Push-Location $frontendDir
|
|
npm.cmd run lint
|
|
if ($LASTEXITCODE -ne 0) { throw "frontend lint failed" }
|
|
npm.cmd run build
|
|
if ($LASTEXITCODE -ne 0) { throw "frontend build failed" }
|
|
Pop-Location
|
|
|
|
Push-Location $repoRoot
|
|
.\backend\.venv\Scripts\python.exe -m pytest -q backend/tests
|
|
if ($LASTEXITCODE -ne 0) { throw "backend pytest failed" }
|
|
Pop-Location
|
|
}
|
|
|
|
if ($runLocalBackend) {
|
|
Stop-Listener -Port $BackendPort
|
|
}
|
|
Stop-ExistingNextDevProcess -TargetDirectory $frontendDir
|
|
Stop-Listener -Port $FrontendPort
|
|
|
|
if ($runLocalBackend) {
|
|
$backendCommand = "`$env:PYTHONPATH='$backendDir'; .\.venv\Scripts\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port $BackendPort *> '$backendLog'"
|
|
$backendProcess = Start-Process -FilePath "powershell.exe" `
|
|
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) `
|
|
-WorkingDirectory $backendDir `
|
|
-WindowStyle Hidden `
|
|
-PassThru
|
|
}
|
|
|
|
Wait-HttpReady -Url "$backendUrl/health" -TimeoutSeconds 90
|
|
|
|
if (-not $runLocalBackend) {
|
|
"REMOTE_BACKEND_MODE $backendUrl" | Set-Content -Path $backendLog -Encoding UTF8
|
|
}
|
|
|
|
$frontendCommand = "`$env:NEXT_PUBLIC_API_BASE_URL='/api/v1'; `$env:BACKEND_BASE_URL='$backendUrl'; npm.cmd run dev -- --hostname 127.0.0.1 --port $FrontendPort *> '$frontendLog'"
|
|
$frontendProcess = Start-Process -FilePath "powershell.exe" `
|
|
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $frontendCommand) `
|
|
-WorkingDirectory $frontendDir `
|
|
-WindowStyle Hidden `
|
|
-PassThru
|
|
|
|
Wait-HttpReady -Url "$webUrl/login" -TimeoutSeconds 120
|
|
|
|
Push-Location $frontendDir
|
|
$env:PHASE2_WEB_URL = $webUrl
|
|
$env:PHASE2_API_URL = "$backendUrl/api/v1"
|
|
$env:PHASE2_HEALTH_URL = "$backendUrl/health"
|
|
$env:PHASE2_ARTIFACT_DIR = $artifactDir
|
|
node .\scripts\phase2-closed-loop-e2e.mjs
|
|
if ($LASTEXITCODE -ne 0) { throw "phase2 closed-loop e2e failed" }
|
|
Pop-Location
|
|
|
|
Write-Host "PHASE2_CLOSED_LOOP_PASS $artifactDir"
|
|
} finally {
|
|
if ($frontendProcess -and -not $frontendProcess.HasExited) {
|
|
Stop-Process -Id $frontendProcess.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
if ($backendProcess -and -not $backendProcess.HasExited) {
|
|
Stop-Process -Id $backendProcess.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
Stop-Listener -Port $FrontendPort
|
|
if ($runLocalBackend) {
|
|
Stop-Listener -Port $BackendPort
|
|
}
|
|
}
|