param( [switch]$Force, [switch]$SkipPreflight, [switch]$SkipSmoke, [switch]$SkipBackendBuild, [switch]$SkipFrontendInstall, [string]$BindHost = "127.0.0.1" ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $BackendPort = 10232 $FrontendPort = 10231 $RepoDir = Split-Path -Parent $MyInvocation.MyCommand.Path $AppDir = Join-Path $RepoDir "eai_agentplatform" $BackendDir = Join-Path $AppDir "backend-go" $FrontendDir = Join-Path $AppDir "frontend" $BackendBin = Join-Path $BackendDir "bin\eai_agentplatform-server.exe" $DebugLogDir = Join-Path $RepoDir "debuglog" $null = New-Item -ItemType Directory -Force -Path $DebugLogDir $RunId = Get-Date -Format "yyyyMMdd_HHmmss" $BackendLog = Join-Path $DebugLogDir "backend_$RunId.log" $BackendErrLog = Join-Path $DebugLogDir "backend_$RunId.err.log" $FrontendLog = Join-Path $DebugLogDir "frontend_$RunId.log" $FrontendErrLog = Join-Path $DebugLogDir "frontend_$RunId.err.log" $BackendPidFile = Join-Path $DebugLogDir "backend.pid" $FrontendPidFile = Join-Path $DebugLogDir "frontend.pid" $KnownProcessNames = @("eai_agentplatform-server", "node", "npm", "npm.cmd") $StartedProcesses = @() function Write-Step { param([string]$Message) Write-Host "" Write-Host "==> $Message" -ForegroundColor Cyan } function Test-CommandExists { param([string]$Name) return $null -ne (Get-Command $Name -ErrorAction SilentlyContinue) } function Get-ProcessNameSafe { param([object]$Process) if ($null -ne $Process) { return $Process.ProcessName } return "unknown" } function Get-PortPids { param([int]$Port) try { $connections = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction Stop return @($connections | Select-Object -ExpandProperty OwningProcess -Unique) } catch { return @() } } function Resolve-Port { param( [int]$Port, [string]$Label ) $pids = @(Get-PortPids -Port $Port) if ($pids.Count -eq 0) { Write-Host (" [OK] {0} :{1} is free" -f $Label, $Port) return } Write-Host (" [!] {0} :{1} is in use:" -f $Label, $Port) foreach ($portPid in $pids) { $proc = Get-Process -Id $portPid -ErrorAction SilentlyContinue $name = Get-ProcessNameSafe -Process $proc Write-Host (" - {0} (PID {1})" -f $name, $portPid) } if (-not $Force) { throw ("{0} port :{1} is in use. Stop it manually or rerun with -Force." -f $Label, $Port) } foreach ($portPid in $pids) { $proc = Get-Process -Id $portPid -ErrorAction SilentlyContinue $name = Get-ProcessNameSafe -Process $proc if (($null -ne $proc) -and (($KnownProcessNames -contains $proc.ProcessName) -or ($name -like "eai_agentplatform-server*"))) { Stop-Process -Id $portPid -Force Write-Host (" stopped {0} (PID {1})" -f $name, $portPid) } else { throw ("Port :{0} is used by a non-project process ({1} / PID {2}). Aborting." -f $Port, $name, $portPid) } } } function Wait-Http { param( [string]$Url, [string]$Label, [int]$TimeoutSec = 60 ) $deadline = (Get-Date).AddSeconds($TimeoutSec) while ((Get-Date) -lt $deadline) { try { Invoke-WebRequest -UseBasicParsing -Uri $Url -TimeoutSec 2 | Out-Null Write-Host (" [OK] {0} is ready: {1}" -f $Label, $Url) return } catch { Start-Sleep -Seconds 1 } } throw ("{0} did not become ready within {1}s: {2}" -f $Label, $TimeoutSec, $Url) } function Start-TrackedProcess { param( [Parameter(Mandatory = $true)][string]$FilePath, [Parameter(Mandatory = $true)][string]$WorkingDirectory, [string[]]$ArgumentList = @(), [Parameter(Mandatory = $true)][string]$StdoutPath, [Parameter(Mandatory = $true)][string]$StderrPath ) $startParams = @{ FilePath = $FilePath WorkingDirectory = $WorkingDirectory RedirectStandardOutput = $StdoutPath RedirectStandardError = $StderrPath PassThru = $true } if ($null -ne $ArgumentList -and $ArgumentList.Count -gt 0) { $startParams.ArgumentList = $ArgumentList } $proc = Start-Process @startParams $script:StartedProcesses += $proc return $proc } function Cleanup-StartedProcesses { foreach ($proc in $StartedProcesses) { if (($null -ne $proc) -and (-not $proc.HasExited)) { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } } } try { Write-Host "==> eai_agentplatform Windows dev launcher" Write-Host (" repo : {0}" -f $RepoDir) Write-Host (" backend : {0} (port {1})" -f $BackendDir, $BackendPort) Write-Host (" frontend : {0} (port {1})" -f $FrontendDir, $FrontendPort) Write-Host (" bind host: {0}" -f $BindHost) if (-not $SkipPreflight) { Write-Step "Preflight" if (-not (Test-Path $BackendDir)) { throw ("Missing backend directory: {0}" -f $BackendDir) } if (-not (Test-Path $FrontendDir)) { throw ("Missing frontend directory: {0}" -f $FrontendDir) } if (-not (Test-CommandExists "go")) { throw "Missing go on PATH." } if (-not (Test-CommandExists "node")) { throw "Missing node on PATH." } if (-not (Test-CommandExists "npm.cmd")) { throw "Missing npm on PATH." } Write-Host " [OK] Preflight passed" } else { Write-Step "Preflight skipped" } Write-Step "Prepare dependencies" if (-not $SkipBackendBuild) { Write-Host " [.] Build backend" Push-Location $BackendDir try { & go build -o $BackendBin ".\cmd\server" } finally { Pop-Location } } elseif (-not (Test-Path $BackendBin)) { throw ("Backend binary not found: {0}" -f $BackendBin) } $NodeModulesDir = Join-Path $FrontendDir "node_modules" if (-not (Test-Path $NodeModulesDir)) { if ($SkipFrontendInstall) { throw "node_modules is missing and frontend install was skipped." } Write-Host " [.] Install frontend dependencies" Push-Location $FrontendDir try { & npm.cmd install } finally { Pop-Location } } else { Write-Host " [OK] Frontend dependencies already installed" } Write-Step "Check ports" Resolve-Port -Port $BackendPort -Label "backend" Resolve-Port -Port $FrontendPort -Label "frontend" Write-Step "Launch services" $env:PORT = [string]$BackendPort $backendProc = Start-TrackedProcess ` -FilePath $BackendBin ` -WorkingDirectory $BackendDir ` -StdoutPath $BackendLog ` -StderrPath $BackendErrLog Set-Content -Path $BackendPidFile -Value $backendProc.Id Wait-Http -Url ("http://127.0.0.1:{0}/api/health" -f $BackendPort) -Label "backend" $frontendProc = Start-TrackedProcess ` -FilePath "npm.cmd" ` -WorkingDirectory $FrontendDir ` -ArgumentList @("run", "dev", "--", "--host", $BindHost, "--port", [string]$FrontendPort) ` -StdoutPath $FrontendLog ` -StderrPath $FrontendErrLog Set-Content -Path $FrontendPidFile -Value $frontendProc.Id if ($BindHost -eq "0.0.0.0") { $frontendHealthUrl = ("http://localhost:{0}/" -f $FrontendPort) } else { $frontendHealthUrl = ("http://{0}:{1}/" -f $BindHost, $FrontendPort) } Wait-Http -Url $frontendHealthUrl -Label "frontend" if (-not $SkipSmoke) { Write-Step "Smoke login" $payload = @{ username = "admin"; password = "admin123" } | ConvertTo-Json try { $null = Invoke-RestMethod ` -Uri ("http://127.0.0.1:{0}/api/auth/login" -f $BackendPort) ` -Method Post ` -ContentType "application/json" ` -Body $payload ` -TimeoutSec 5 Write-Host " [OK] admin login passed" } catch { Write-Warning ("admin login failed: {0}" -f $_.Exception.Message) } } Write-Step "Done" Write-Host ("Frontend : http://localhost:{0}/" -f $FrontendPort) Write-Host ("Backend : http://127.0.0.1:{0}/" -f $BackendPort) Write-Host ("Backend log : {0}" -f $BackendLog) Write-Host ("Backend err : {0}" -f $BackendErrLog) Write-Host ("Frontend log : {0}" -f $FrontendLog) Write-Host ("Frontend err : {0}" -f $FrontendErrLog) Write-Host ("PIDs : backend={0} frontend={1}" -f $backendProc.Id, $frontendProc.Id) Write-Host "" Write-Host "Examples:" -ForegroundColor Yellow Write-Host " powershell -ExecutionPolicy Bypass -File .\start_dev_10231_10232.ps1" Write-Host " powershell -ExecutionPolicy Bypass -File .\start_dev_10231_10232.ps1 -Force" Write-Host " powershell -ExecutionPolicy Bypass -File .\start_dev_10231_10232.ps1 -SkipSmoke" } catch { Cleanup-StartedProcesses throw }