param( [switch]$ForceKillPorts ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path $RuntimeDir = Join-Path $RepoRoot ".runtime" $ManagedNames = @("api", "web") $KnownPorts = @(3000, 3001, 3002) function Write-Step([string]$Message) { Write-Host "==> $Message" -ForegroundColor Cyan } function Write-WarnLine([string]$Message) { Write-Host "WARN: $Message" -ForegroundColor Yellow } function Get-PidFilePath([string]$Name) { return Join-Path $RuntimeDir "$Name.pid" } function Test-ProcessRunning([int]$ProcessId) { try { $null = Get-Process -Id $ProcessId -ErrorAction Stop return $true } catch { return $false } } function Get-ProcessCommandLine([int]$ProcessId) { try { $process = Get-CimInstance Win32_Process -Filter "ProcessId = $ProcessId" if ($null -eq $process -or $null -eq $process.CommandLine) { return "" } return [string]$process.CommandLine } catch { return "" } } function Test-SentAIProcess([int]$ProcessId) { $commandLine = Get-ProcessCommandLine $ProcessId if (-not $commandLine) { return $false } return $commandLine -like "*SentAI*" -or $commandLine -like "*@sentai*" -or $commandLine -like "*npm.cmd run dev:api*" -or $commandLine -like "*npm.cmd run dev:web*" } function Stop-ProcessTree([int]$ProcessId) { & taskkill /PID $ProcessId /T /F | Out-Null } function Get-ListenerOwners([int]$Port) { try { return @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction Stop | Select-Object -ExpandProperty OwningProcess -Unique) } catch { return @() } } foreach ($name in $ManagedNames) { $pidFile = Get-PidFilePath $name if (-not (Test-Path $pidFile)) { continue } $raw = (Get-Content $pidFile -ErrorAction SilentlyContinue | Select-Object -First 1) $managedPid = 0 if ([int]::TryParse($raw, [ref]$managedPid) -and (Test-ProcessRunning $managedPid)) { Write-Step "Stopping managed $name process $managedPid." Stop-ProcessTree $managedPid } Remove-Item $pidFile -ErrorAction SilentlyContinue } foreach ($port in $KnownPorts) { [array]$owners = @(Get-ListenerOwners $port) foreach ($owner in $owners) { if ($ForceKillPorts -or (Test-SentAIProcess $owner)) { Write-WarnLine "Stopping remaining process $owner on port $port." Stop-ProcessTree $owner continue } Write-WarnLine "Port $port is still used by non-SentAI process $owner. Leaving it untouched." } } Start-Sleep -Seconds 2 Write-Host "" foreach ($port in $KnownPorts) { [array]$owners = @(Get-ListenerOwners $port) if ($owners.Count -eq 0) { Write-Host "Port $port closed." -ForegroundColor Green } else { Write-Host "Port $port still in use by $($owners -join ', ')." -ForegroundColor Yellow } }