param( [Parameter(Mandatory = $true)] [string]$EnvFile, [string]$Workspace = "D:\workspace\vignette", [string]$ListenAddress = "0.0.0.0", [int]$Port = 9100, [string]$Python = "$env:LOCALAPPDATA\Programs\Python\Python311\python.exe" ) $ErrorActionPreference = "Stop" [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) $OutputEncoding = [System.Text.UTF8Encoding]::new($false) function Get-EnvValue { param([string]$Path, [string]$Key) foreach ($line in Get-Content -LiteralPath $Path -Encoding UTF8) { if ($line -match "^\s*$([Regex]::Escape($Key))\s*=(.*)$") { return $Matches[1].Trim().Trim('"').Trim("'") } } return "" } if (!(Test-Path -LiteralPath $EnvFile)) { throw "NAS preview env file not found: $EnvFile" } if (!(Test-Path -LiteralPath $Python)) { throw "Python 3.11 not found: $Python" } $apiDir = Join-Path $Workspace "apps\api" if (!(Test-Path -LiteralPath $apiDir)) { throw "API directory not found: $apiDir" } $secret = Get-EnvValue -Path $EnvFile -Key "ENGINE_GATEWAY_SHARED_SECRET" if ($secret.Length -lt 32 -or $secret.ToLowerInvariant().StartsWith("change-me")) { throw "ENGINE_GATEWAY_SHARED_SECRET must be a non-placeholder value of at least 32 characters" } $existing = Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -like "*uvicorn engine_gateway.gateway:app*" -and $_.CommandLine -like "*--port $Port*" } if ($existing) { throw "An engine gateway is already running on the requested preview port $Port" } $logDir = Join-Path $env:LOCALAPPDATA "Temp\vignette-nas-preview" New-Item -ItemType Directory -Path $logDir -Force | Out-Null $stamp = [DateTime]::UtcNow.ToString("yyyyMMddHHmmss") $outLog = Join-Path $logDir "engine-$Port-$stamp.out.log" $errLog = Join-Path $logDir "engine-$Port-$stamp.err.log" $env:ENGINE_GATEWAY_SHARED_SECRET = $secret $env:PYTHONUTF8 = "1" $process = Start-Process -WindowStyle Hidden -FilePath $Python ` -ArgumentList @("-X", "utf8", "-m", "uvicorn", "engine_gateway.gateway:app", "--host", $ListenAddress, "--port", "$Port") ` -WorkingDirectory $apiDir ` -RedirectStandardOutput $outLog ` -RedirectStandardError $errLog ` -PassThru Remove-Item Env:ENGINE_GATEWAY_SHARED_SECRET $deadline = (Get-Date).AddSeconds(30) $health = $null do { Start-Sleep -Milliseconds 500 if ($process.HasExited) { $detail = if (Test-Path -LiteralPath $errLog) { (Get-Content -LiteralPath $errLog -Encoding UTF8 -Tail 40) -join "`n" } else { "no stderr log" } throw "Preview engine gateway exited with $($process.ExitCode): $detail" } try { $health = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/health" -TimeoutSec 2 } catch { $health = $null } } while ($null -eq $health -and (Get-Date) -lt $deadline) if ($null -eq $health -or !$health.ok) { throw "Preview engine gateway health timed out on port $Port" } Write-Output "Preview engine gateway PID=$($process.Id) port=$Port" Write-Output "Health=$($health | ConvertTo-Json -Compress)" Write-Output "SecretValueEmitted=false" Write-Output "StdoutLog=$outLog" Write-Output "StderrLog=$errLog"