stop-local.ps1 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. param(
  2. [switch]$ForceKillPorts
  3. )
  4. $ErrorActionPreference = "Stop"
  5. Set-StrictMode -Version Latest
  6. $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
  7. $RuntimeDir = Join-Path $RepoRoot ".runtime"
  8. $ManagedNames = @("api", "web")
  9. $KnownPorts = @(3000, 3001, 3002)
  10. function Write-Step([string]$Message) {
  11. Write-Host "==> $Message" -ForegroundColor Cyan
  12. }
  13. function Write-WarnLine([string]$Message) {
  14. Write-Host "WARN: $Message" -ForegroundColor Yellow
  15. }
  16. function Get-PidFilePath([string]$Name) {
  17. return Join-Path $RuntimeDir "$Name.pid"
  18. }
  19. function Test-ProcessRunning([int]$ProcessId) {
  20. try {
  21. $null = Get-Process -Id $ProcessId -ErrorAction Stop
  22. return $true
  23. } catch {
  24. return $false
  25. }
  26. }
  27. function Get-ProcessCommandLine([int]$ProcessId) {
  28. try {
  29. $process = Get-CimInstance Win32_Process -Filter "ProcessId = $ProcessId"
  30. if ($null -eq $process -or $null -eq $process.CommandLine) {
  31. return ""
  32. }
  33. return [string]$process.CommandLine
  34. } catch {
  35. return ""
  36. }
  37. }
  38. function Test-SentAIProcess([int]$ProcessId) {
  39. $commandLine = Get-ProcessCommandLine $ProcessId
  40. if (-not $commandLine) {
  41. return $false
  42. }
  43. return $commandLine -like "*SentAI*" -or
  44. $commandLine -like "*@sentai*" -or
  45. $commandLine -like "*npm.cmd run dev:api*" -or
  46. $commandLine -like "*npm.cmd run dev:web*"
  47. }
  48. function Stop-ProcessTree([int]$ProcessId) {
  49. & taskkill /PID $ProcessId /T /F | Out-Null
  50. }
  51. function Get-ListenerOwners([int]$Port) {
  52. try {
  53. return @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction Stop |
  54. Select-Object -ExpandProperty OwningProcess -Unique)
  55. } catch {
  56. return @()
  57. }
  58. }
  59. foreach ($name in $ManagedNames) {
  60. $pidFile = Get-PidFilePath $name
  61. if (-not (Test-Path $pidFile)) {
  62. continue
  63. }
  64. $raw = (Get-Content $pidFile -ErrorAction SilentlyContinue | Select-Object -First 1)
  65. $managedPid = 0
  66. if ([int]::TryParse($raw, [ref]$managedPid) -and (Test-ProcessRunning $managedPid)) {
  67. Write-Step "Stopping managed $name process $managedPid."
  68. Stop-ProcessTree $managedPid
  69. }
  70. Remove-Item $pidFile -ErrorAction SilentlyContinue
  71. }
  72. foreach ($port in $KnownPorts) {
  73. [array]$owners = @(Get-ListenerOwners $port)
  74. foreach ($owner in $owners) {
  75. if ($ForceKillPorts -or (Test-SentAIProcess $owner)) {
  76. Write-WarnLine "Stopping remaining process $owner on port $port."
  77. Stop-ProcessTree $owner
  78. continue
  79. }
  80. Write-WarnLine "Port $port is still used by non-SentAI process $owner. Leaving it untouched."
  81. }
  82. }
  83. Start-Sleep -Seconds 2
  84. Write-Host ""
  85. foreach ($port in $KnownPorts) {
  86. [array]$owners = @(Get-ListenerOwners $port)
  87. if ($owners.Count -eq 0) {
  88. Write-Host "Port $port closed." -ForegroundColor Green
  89. } else {
  90. Write-Host "Port $port still in use by $($owners -join ', ')." -ForegroundColor Yellow
  91. }
  92. }