@echo off setlocal set "AI_RESTART_ROOT=%~dp0" powershell -NoProfile -ExecutionPolicy Bypass -Command "$p='%~f0'; $s=Get-Content -LiteralPath $p -Raw; $m=':ps1'; $i=$s.LastIndexOf($m); if ($i -lt 0) { throw 'PowerShell payload marker not found.' }; Invoke-Expression $s.Substring($i + $m.Length)" set "EXIT_CODE=%ERRORLEVEL%" if "%~1"=="--no-pause" exit /b %EXIT_CODE% echo. if not "%EXIT_CODE%"=="0" echo [servers] Failed with exit code %EXIT_CODE%. pause exit /b %EXIT_CODE% :ps1 $ErrorActionPreference = "Stop" $root = (Resolve-Path -LiteralPath $env:AI_RESTART_ROOT).Path $logDir = Join-Path $root ".server-logs" New-Item -ItemType Directory -Force -Path $logDir | Out-Null $ports = @(8000, 8001, 8002, 8003, 5173, 5174) function Write-Step { param([string] $Message) Write-Host "[servers] $Message" } function Get-CommandPath { param([string[]] $Candidates) foreach ($candidate in $Candidates) { if ([string]::IsNullOrWhiteSpace($candidate)) { continue } if (Test-Path -LiteralPath $candidate) { return (Resolve-Path -LiteralPath $candidate).Path } $cmd = Get-Command $candidate -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } } return $null } function Test-PythonModule { param( [string] $PythonPath, [string] $ModuleName ) if ([string]::IsNullOrWhiteSpace($PythonPath)) { return $false } try { $proc = Start-Process ` -FilePath $PythonPath ` -ArgumentList @("-c", "import $ModuleName") ` -WindowStyle Hidden ` -Wait ` -PassThru return $proc.ExitCode -eq 0 } catch { return $false } } function Resolve-ProjectPython { param([string] $ProjectRoot) $projectVenvPython = Join-Path $ProjectRoot ".venv\Scripts\python.exe" if (Test-Path -LiteralPath $projectVenvPython) { return (Resolve-Path -LiteralPath $projectVenvPython).Path } $rootVenvPython = Join-Path $root ".venv\Scripts\python.exe" if (Test-Path -LiteralPath $rootVenvPython) { return (Resolve-Path -LiteralPath $rootVenvPython).Path } $candidates = @() if ($env:VIRTUAL_ENV) { $candidates += Join-Path $env:VIRTUAL_ENV "Scripts\python.exe" } $pyLauncher = Get-CommandPath @("py.exe", "py") if ($pyLauncher) { try { $resolvedFromPy = & $pyLauncher -c "import sys; print(sys.executable)" 2>$null if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($resolvedFromPy)) { $candidates += $resolvedFromPy.Trim() } } catch { } } $pathPython = Get-CommandPath @("python.exe", "python") if ($pathPython) { $candidates += $pathPython } $seen = @{} foreach ($candidate in $candidates) { if ([string]::IsNullOrWhiteSpace($candidate)) { continue } $resolved = $null if (Test-Path -LiteralPath $candidate) { $resolved = (Resolve-Path -LiteralPath $candidate).Path } else { $cmd = Get-Command $candidate -ErrorAction SilentlyContinue if ($cmd) { $resolved = $cmd.Source } } if (-not $resolved -or $seen.ContainsKey($resolved)) { continue } $seen[$resolved] = $true if (Test-PythonModule -PythonPath $resolved -ModuleName "uvicorn") { return $resolved } } return $null } function Resolve-SystemPython { $candidates = @() if ($env:VIRTUAL_ENV) { $candidates += Join-Path $env:VIRTUAL_ENV "Scripts\python.exe" } $pathPython = Get-CommandPath @("python.exe", "python") if ($pathPython) { $candidates += $pathPython } $pyLauncher = Get-CommandPath @("py.exe", "py") if ($pyLauncher) { try { $resolvedFromPy = & $pyLauncher -c "import sys; print(sys.executable)" 2>$null if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($resolvedFromPy)) { $candidates += $resolvedFromPy.Trim() } } catch { } } $seen = @{} foreach ($candidate in $candidates) { if ([string]::IsNullOrWhiteSpace($candidate)) { continue } $resolved = $null if (Test-Path -LiteralPath $candidate) { $resolved = (Resolve-Path -LiteralPath $candidate).Path } else { $cmd = Get-Command $candidate -ErrorAction SilentlyContinue if ($cmd) { $resolved = $cmd.Source } } if (-not $resolved -or $seen.ContainsKey($resolved)) { continue } $seen[$resolved] = $true return $resolved } return $null } function Ensure-ProjectVenv { param([string] $ProjectRoot) $venvPython = Join-Path $ProjectRoot ".venv\Scripts\python.exe" if (Test-PythonModule -PythonPath $venvPython -ModuleName "uvicorn") { return $venvPython } $bootstrapPython = Resolve-SystemPython if (-not $bootstrapPython) { Write-Step "No system Python was found for bootstrapping the backend venv." return $null } Write-Step "Bootstrapping backend virtual environment in $ProjectRoot\\.venv" if (-not (Test-Path -LiteralPath $venvPython)) { Invoke-LoggedCommand ` -FilePath $bootstrapPython ` -Arguments @("-m", "venv", ".venv") ` -WorkingDirectory $ProjectRoot ` -LogName "backend-venv-create.log" } Invoke-LoggedCommand ` -FilePath $venvPython ` -Arguments @("-m", "pip", "install", "--upgrade", "pip") ` -WorkingDirectory $ProjectRoot ` -LogName "backend-pip-upgrade.log" Invoke-LoggedCommand ` -FilePath $venvPython ` -Arguments @("-m", "pip", "install", "-e", ".") ` -WorkingDirectory $ProjectRoot ` -LogName "backend-pip-install.log" if (Test-PythonModule -PythonPath $venvPython -ModuleName "uvicorn") { return $venvPython } Write-Step "Backend venv was created, but uvicorn is still unavailable. Check .server-logs\\backend-*.log" return $null } function Read-DotEnv { param([string] $Path) $result = @{} if (-not (Test-Path -LiteralPath $Path)) { return $result } foreach ($line in Get-Content -LiteralPath $Path) { if ($line -match "^\s*$" -or $line -match "^\s*#") { continue } if ($line -match "^\s*([^#=\s]+)\s*=\s*(.*)\s*$") { $key = $matches[1] $value = $matches[2].Trim() if ( ($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'")) ) { $value = $value.Substring(1, $value.Length - 2) } $result[$key] = $value } } return $result } function Stop-ExistingServers { Write-Step "Stopping existing project servers." $currentPid = $PID $rootPattern = [regex]::Escape($root) $commandPattern = "uvicorn|ont_platform\.api|phase[0-9]_app|vite|npm(\.cmd)?\s+run\s+(dev|preview|start)" $matched = Get-CimInstance Win32_Process | Where-Object { if (-not $_.CommandLine) { return $false } if ($_.ProcessId -eq $currentPid) { return $false } $cmd = $_.CommandLine return ($cmd -match $commandPattern) -and ( $cmd -match $rootPattern -or $cmd -match "ontology_platform|ont_platform" ) } foreach ($proc in $matched) { Write-Step ("Killing process {0} ({1})" -f $proc.ProcessId, $proc.Name) Stop-Process -Id $proc.ProcessId -Force -ErrorAction SilentlyContinue } $listeners = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Where-Object { $ports -contains $_.LocalPort } | Select-Object -ExpandProperty OwningProcess -Unique foreach ($pidToStop in $listeners) { if ($pidToStop -and $pidToStop -ne $currentPid) { Write-Step ("Killing listener on configured dev port, pid {0}" -f $pidToStop) Stop-Process -Id $pidToStop -Force -ErrorAction SilentlyContinue } } Start-Sleep -Milliseconds 800 } function Quote-ProcessArguments { param([string[]] $Arguments) if (-not $Arguments) { return @() } $quoted = @() foreach ($arg in $Arguments) { if ($null -eq $arg) { continue } $text = [string] $arg if ($text.Length -eq 0) { $quoted += '""' continue } if ($text -match '\s' -and -not ($text.StartsWith('"') -and $text.EndsWith('"'))) { $escaped = $text -replace '"', '\"' $quoted += '"' + $escaped + '"' } else { $quoted += $text } } return $quoted } function Invoke-LoggedCommand { param( [string] $FilePath, [string[]] $Arguments, [string] $WorkingDirectory, [string] $LogName ) $logPath = Join-Path $logDir $LogName $stderrPath = Join-Path $logDir ($LogName -replace "\.log$", ".stderr.log") Write-Step ("Running {0} {1}" -f (Split-Path -Leaf $FilePath), ($Arguments -join " ")) $quotedArgs = Quote-ProcessArguments -Arguments $Arguments Remove-Item -LiteralPath $logPath, $stderrPath -Force -ErrorAction SilentlyContinue $proc = Start-Process ` -FilePath $FilePath ` -ArgumentList $quotedArgs ` -WorkingDirectory $WorkingDirectory ` -RedirectStandardOutput $logPath ` -RedirectStandardError $stderrPath ` -WindowStyle Hidden ` -Wait ` -PassThru $exitCode = $proc.ExitCode if ($exitCode -ne 0) { throw "Command failed with exit code $exitCode. See $logPath and $stderrPath" } } function Start-LoggedServer { param( [string] $Name, [int] $Port, [string] $FilePath, [string[]] $Arguments, [string] $WorkingDirectory, [hashtable] $Environment = @{} ) $stdout = Join-Path $logDir "$Name-stdout.log" $stderr = Join-Path $logDir "$Name-stderr.log" Remove-Item -LiteralPath $stdout, $stderr -Force -ErrorAction SilentlyContinue $previous = @{} foreach ($key in $Environment.Keys) { $previous[$key] = [Environment]::GetEnvironmentVariable($key, "Process") [Environment]::SetEnvironmentVariable($key, [string] $Environment[$key], "Process") } $quotedArgs = Quote-ProcessArguments -Arguments $Arguments try { $proc = Start-Process ` -FilePath $FilePath ` -ArgumentList $quotedArgs ` -WorkingDirectory $WorkingDirectory ` -RedirectStandardOutput $stdout ` -RedirectStandardError $stderr ` -WindowStyle Hidden ` -PassThru } finally { foreach ($key in $Environment.Keys) { [Environment]::SetEnvironmentVariable($key, $previous[$key], "Process") } } Write-Step ("Started {0}: pid {1}" -f $Name, $proc.Id) return [pscustomobject]@{ Name = $Name Port = $Port Pid = $proc.Id Stdout = $stdout Stderr = $stderr } } function Clear-ServerLogs { param([string] $Name) Remove-Item -LiteralPath ` (Join-Path $logDir "$Name-stdout.log"), ` (Join-Path $logDir "$Name-stderr.log") ` -Force -ErrorAction SilentlyContinue } function Wait-Port { param( [int] $Port, [int] $Seconds = 20 ) $deadline = (Get-Date).AddSeconds($Seconds) while ((Get-Date) -lt $deadline) { try { $client = [System.Net.Sockets.TcpClient]::new() $async = $client.BeginConnect("127.0.0.1", $Port, $null, $null) if ($async.AsyncWaitHandle.WaitOne(500)) { $client.EndConnect($async) $client.Close() return $true } $client.Close() } catch { Start-Sleep -Milliseconds 500 } } return $false } function Resolve-FrontendDevCommand { param( [string] $FrontendDir, [string] $NpmPath ) $node = Get-CommandPath @("node.exe", "node") $viteCli = Join-Path $FrontendDir "node_modules\vite\bin\vite.js" if ($node -and (Test-Path -LiteralPath $viteCli)) { return [pscustomobject]@{ FilePath = $node Arguments = @($viteCli, "--host", "127.0.0.1", "--port", "8000") } } return [pscustomobject]@{ FilePath = $NpmPath Arguments = @("run", "dev", "--", "--host", "127.0.0.1", "--port", "8000") } } function Start-Neo4jIfAvailable { $composeFile = Join-Path $root "docker-compose.neo4j.yml" if (-not (Test-Path -LiteralPath $composeFile)) { return } $docker = Get-CommandPath @("docker") if (-not $docker) { Write-Step "Docker was not found; skipping Neo4j compose service." return } try { Invoke-LoggedCommand -FilePath $docker -Arguments @("compose", "-f", $composeFile, "down") -WorkingDirectory $root -LogName "neo4j-down.log" Invoke-LoggedCommand -FilePath $docker -Arguments @("compose", "-f", $composeFile, "up", "-d") -WorkingDirectory $root -LogName "neo4j-up.log" Write-Step "Neo4j compose service requested." } catch { Write-Step "Neo4j compose restart skipped/failed. See .server-logs\\neo4j-*.log" } } Stop-ExistingServers Start-Neo4jIfAvailable $npm = Get-CommandPath @("npm.cmd", "npm") $frontendDir = Join-Path $root "ontology_platform\web\frontend" $frontendReady = $false if ((Test-Path -LiteralPath (Join-Path $frontendDir "package.json")) -and $npm) { if (-not (Test-Path -LiteralPath (Join-Path $frontendDir "node_modules"))) { Invoke-LoggedCommand -FilePath $npm -Arguments @("ci") -WorkingDirectory $frontendDir -LogName "frontend-npm-ci.log" } Invoke-LoggedCommand -FilePath $npm -Arguments @("run", "build") -WorkingDirectory $frontendDir -LogName "frontend-build.log" $frontendReady = $true } elseif (Test-Path -LiteralPath (Join-Path $frontendDir "package.json")) { Write-Step "npm was not found; skipping frontend rebuild." } $servers = @() $ontologyRoot = Join-Path $root "ontology_platform" $ontologyPython = Resolve-ProjectPython -ProjectRoot $ontologyRoot if ((-not $ontologyPython) -and (Test-Path -LiteralPath (Join-Path $ontologyRoot "pyproject.toml"))) { $ontologyPython = Ensure-ProjectVenv -ProjectRoot $ontologyRoot } if ((Test-Path -LiteralPath (Join-Path $ontologyRoot "ont_platform\api\main.py")) -and $ontologyPython) { $ontologyEnv = Read-DotEnv -Path (Join-Path $ontologyRoot ".env") $ontologyEnv["PHASE"] = "6" $ontologyEnv["HOST"] = "127.0.0.1" $ontologyEnv["PORT"] = "8001" $servers += Start-LoggedServer ` -Name "ontology-platform-8001" ` -Port 8001 ` -FilePath $ontologyPython ` -Arguments @("-m", "uvicorn", "ont_platform.api.main:app", "--host", "127.0.0.1", "--port", "8001") ` -WorkingDirectory $ontologyRoot ` -Environment $ontologyEnv } else { Clear-ServerLogs -Name "ontology-platform-8001" Write-Step "ontology_platform server entrypoint was not found, or no Python with uvicorn is available for this project." Write-Step "Create a venv under ontology_platform\\.venv (or .\\.venv) and install dependencies before rerunning." } if ($frontendReady) { $frontendDev = Resolve-FrontendDevCommand -FrontendDir $frontendDir -NpmPath $npm $servers += Start-LoggedServer ` -Name "ontology-frontend-8000" ` -Port 8000 ` -FilePath $frontendDev.FilePath ` -Arguments $frontendDev.Arguments ` -WorkingDirectory $frontendDir } $pidFile = Join-Path $logDir "server-pids.txt" $servers | ForEach-Object { "{0} pid={1} stdout={2} stderr={3}" -f $_.Name, $_.Pid, $_.Stdout, $_.Stderr } | Set-Content -LiteralPath $pidFile -Encoding UTF8 $missingPorts = @() foreach ($port in ($servers | Select-Object -ExpandProperty Port -Unique)) { if (Wait-Port -Port $port -Seconds 120) { Write-Step ("Port {0} is listening." -f $port) } else { Write-Step ("Port {0} did not open. Check logs in {1}" -f $port, $logDir) $missingPorts += $port } } if ($missingPorts.Count -gt 0) { throw "Some servers did not start: ports $($missingPorts -join ', ')" } Write-Host "" Write-Step "Done." if ($servers | Where-Object { $_.Port -eq 8000 }) { Write-Host " Ontology UI: http://127.0.0.1:8000/static/" } if ($servers | Where-Object { $_.Port -eq 8001 }) { Write-Host " Ontology API: http://127.0.0.1:8001/docs" } Write-Host " Logs: $logDir"