From f038790caa46d7d7c4b0a16ca8ad6d540cd16ce7 Mon Sep 17 00:00:00 2001 From: urieljareth Date: Sat, 22 Aug 2026 11:26:17 -0600 Subject: [PATCH] feat: start-server.ps1 acepta -PythonExe y copys amigables --- scripts/start-server.ps1 | 225 +++++++++++++++++++++++++++++---------- 1 file changed, 167 insertions(+), 58 deletions(-) diff --git a/scripts/start-server.ps1 b/scripts/start-server.ps1 index f1c8e6f..91239b8 100644 --- a/scripts/start-server.ps1 +++ b/scripts/start-server.ps1 @@ -1,45 +1,117 @@ +param( + # Ruta absoluta o nombre del interprete Python que lanza uvicorn. + # doctor.ps1 la resuelve porque tras una instalacion fresca el PATH + # de esta sesion aun no ve el Python nuevo. + [string]$PythonExe = 'python' +) + $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot Set-Location -LiteralPath $root +# Force UTF-8 so accented messages render correctly even when launched +# from the .bat via `powershell -File` (PS5.1 uses the OEM codepage by default). +try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {} -# Robust browser opener — tries several Windows methods until one works. +# All console output is force-flushed so the user sees live progress even when +# the launcher is invoked from a .bat-launched powershell.exe window. +function Out-Line($msg, $color = $null) { + if ($color) { Write-Host $msg -ForegroundColor $color } + else { Write-Host $msg } + [Console]::Out.Flush() +} + +# Robust browser opener -- tries several Windows methods until one works. function Open-Browser($url) { $opened = $false - # Method 1: Start-Process with the URL (default protocol handler) try { Start-Process -FilePath $url; $opened = $true } catch {} - if (-not $opened) { - # Method 2: explorer.exe with the URL (opens default browser) - try { & explorer.exe $url; $opened = $true } catch {} - } - if (-not $opened) { - # Method 3: cmd `start` builtin - try { & cmd.exe /c start "" $url; $opened = $true } catch {} - } - if ($opened) { Write-Host " [ok] abriendo navegador -> $url" -ForegroundColor DarkGray } - else { Write-Host " [warn] no se pudo abrir el navegador. Abre manualmente: $url" -ForegroundColor Yellow } + if (-not $opened) { try { & explorer.exe $url; $opened = $true } catch {} } + if (-not $opened) { try { & cmd.exe /c start "" $url; $opened = $true } catch {} } + if ($opened) { Out-Line " [ok] navegador abierto -> $url" DarkGray } + else { Out-Line " [warn] no se pudo abrir el navegador automaticamente" Yellow } return $opened } -Write-Host "" -Write-Host " [yt-scraper] iniciando servidor local..." -ForegroundColor Cyan +# Fast TCP-connect probe. Replaces Invoke-WebRequest (which on Windows +# PowerShell 5.1 ignores -TimeoutSec for the connect phase and can hang +# 3-15 s per failed attempt to a closed port -- making a typical port +# scan silently take many minutes). +function Test-PortOpen($port, $timeoutMs = 350) { + $client = New-Object System.Net.Sockets.TcpClient + try { + $iar = $client.BeginConnect('127.0.0.1', $port, $null, $null) + if (-not $iar.AsyncWaitHandle.WaitOne($timeoutMs)) { return $false } + $client.EndConnect($iar) + return $true + } catch { return $false } + finally { $client.Close() } +} + +function Get-ServerHealth($port, $timeoutMs = 800) { + try { + $r = Invoke-WebRequest -Uri "http://127.0.0.1:$port/healthz" -UseBasicParsing -TimeoutSec ([Math]::Max(1, [int]($timeoutMs/1000))) + if ($r.Content -match 'ok') { return $true } + } catch {} + return $false +} + +function Is-PidAlive($processId) { + if (-not $processId) { return $false } + $p = Get-Process -Id ([int]$processId) -ErrorAction SilentlyContinue + return $null -ne $p +} + +function Show-State($label, $obj) { + Out-Line (" [{0}] puerto={1} pid={2} t={3:o}" -f $label, $obj.port, $obj.pid, (Get-Date)) +} + +Out-Line "" +Out-Line " [yt-scraper] iniciando servidor local..." Cyan if (-not (Test-Path '.run')) { New-Item -ItemType Directory -Path '.run' | Out-Null } -# Already running? +# --- (1) Check for an already-running server registered in server.info --- +# A bound port is ground truth that a server is live. The cmd-wrapper PID +# we recorded is a weak signal (cmd /c exits as soon as python takes over), +# so we DON'T require the PID to be alive -- only that the port responds. +$registeredPort = $null +$registeredPid = $null +$liveExisting = $false + if (Test-Path '.run\server.info') { $firstLine = (Get-Content '.run\server.info' -TotalCount 1) - $existingPort = ($firstLine -split '\s+')[0] - try { - $r = Invoke-WebRequest -Uri "http://localhost:$existingPort/healthz" -UseBasicParsing -TimeoutSec 2 - if ($r.Content -match 'ok') { - Write-Host " [ok] servidor ya activo en el puerto $existingPort" -ForegroundColor Green - Open-Browser "http://127.0.0.1:$existingPort" - exit 0 + $parts = $firstLine -split '\s+' + if ($parts.Count -ge 2) { + $registeredPort = $parts[0] + $registeredPid = $parts[1] + $portBusy = Test-PortOpen ([int]$registeredPort) 350 + $pidAlive = Is-PidAlive $registeredPid + if ($portBusy) { + # Port is bound and answering -- this IS the running server. + # Even if the recorded cmd-wrapper PID died, python is alive. + $liveExisting = $true + } else { + Out-Line " [info] Habia un registro viejo de otra sesion; limpiandolo..." Yellow + Remove-Item '.run\server.info' -Force -ErrorAction SilentlyContinue + if (-not $pidAlive) { + # also nuke any stray python on that port, just in case + Get-NetTCPConnection -State Listen -LocalPort ([int]$registeredPort) -ErrorAction SilentlyContinue | + ForEach-Object { try { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue } catch {} } + } } - } catch {} + } } -# Collect candidate free ports scanning from 8000. +if ($liveExisting) { + Out-Line " [ok] servidor ya activo en puerto $registeredPort" Green + Out-Line " URL: http://localhost:$registeredPort" + Out-Line " log: .run\server.log" + Out-Line " stop: stop-server.bat" + Open-Browser "http://127.0.0.1:$registeredPort" | Out-Null + Out-Line "" + exit 0 +} + +# --- (2) Find free ports (fast TcpListener probe, like the original) --- $candidates = @() foreach ($p in 8000..8100) { try { @@ -50,53 +122,90 @@ foreach ($p in 8000..8100) { } catch {} } if ($candidates.Count -eq 0) { - Write-Host " [error] no se encontro ningun puerto libre (8000-8100)" -ForegroundColor Red + Out-Line " [error] no se encontro ningun puerto libre (8000-8100)" Red exit 2 } - -# Launch uvicorn DETACHED in its own persistent minimized console window, logging to .run/server.log. -# The `cmd /c` window is an independent console: it survives this launcher exiting. +# --- (3) Launch uvicorn on each candidate until one sticks --- $launched = $false -$proc = $null $port = 0 +$proc = $null + foreach ($candidate in $candidates) { - # clear previous log Remove-Item '.run\server.log' -Force -ErrorAction SilentlyContinue - $cmdLine = '/c python -m uvicorn yt_scraper.webapp.app:app --host 127.0.0.1 --port ' + $candidate + ' --log-level info > .run\server.log 2>&1' - $proc = Start-Process -FilePath 'cmd.exe' -ArgumentList $cmdLine -WorkingDirectory $root -WindowStyle Minimized -PassThru - Set-Content -Path '.run\server.info' -Value "$candidate $($proc.Id) $(Get-Date -Format o)" -Encoding ASCII - - $ok = $false - for ($i = 0; $i -lt 40; $i++) { - Start-Sleep -Milliseconds 500 - try { - $r = Invoke-WebRequest -Uri "http://localhost:$candidate/healthz" -UseBasicParsing -TimeoutSec 2 - if ($r.Content -match 'ok') { $ok = $true; break } - } catch {} + Out-Line " [...] Encendiendo el servidor (intento $candidate)..." Gray + $cmdLine = '/c "' + $PythonExe + '" -m uvicorn yt_scraper.webapp.app:app --host 127.0.0.1 --port ' + $candidate + ' --log-level info > ".run\server.log" 2>&1' + try { + $proc = Start-Process -FilePath 'cmd.exe' -ArgumentList $cmdLine -WorkingDirectory $root -WindowStyle Minimized -PassThru + } catch { + Out-Line " [warn] Start-Process fallo en $candidate : $($_.Exception.Message)" Yellow + continue } - if ($ok) { $port = $candidate; $launched = $true; break } + Out-Line " El servidor corre en segundo plano. Registro: .run\server.log" - # failed on this port — show why from the log, then try the next candidate - Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue - Remove-Item '.run\server.info' -Force -ErrorAction SilentlyContinue + # Fast TCP-poll for the bound port. Bound == uvicorn listening, which + # is all the user needs to know "open this URL in a browser". + $boundAt = $null + $bound = $false + $sw = [System.Diagnostics.Stopwatch]::StartNew() + for ($i = 0; $i -lt 50; $i++) { + Start-Sleep -Milliseconds 200 + if (Test-PortOpen $candidate 250) { $bound = $true; $boundAt = $sw.Elapsed; break } + # show a tiny heartbeat every ~5 attempts + if ($i -in 5,15,30,45) { + Out-Line (" ... esperando puerto {0} ({1:0.0}s)" -f $candidate, $sw.Elapsed.TotalSeconds) DarkGray + } + } + + if (-not $bound) { + Out-Line " [warn] Este intento no respondio; probando el siguiente..." Yellow + # Show the actual reason from the log so the user can diagnose. + if (Test-Path '.run\server.log') { + $tail = Get-Content '.run\server.log' -Tail 6 -ErrorAction SilentlyContinue + if ($tail) { + Out-Line " (ultimas lineas del log):" DarkGray + $tail | ForEach-Object { Out-Line " $_" DarkGray } + } + } + try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch {} + try { Get-NetTCPConnection -State Listen -LocalPort $candidate -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue } } catch {} + continue + } + + # Port is bound. Reveal the URL RIGHT NOW so the user sees progress. + $port = $candidate + Set-Content -Path '.run\server.info' -Value "$port $($proc.Id) $(Get-Date -Format o)" -Encoding ASCII + + Out-Line " [ok] Servidor encendido." Green + Out-Line " URL: http://localhost:$port" + Out-Line " log: .run\server.log" + Out-Line " stop: stop-server.bat" + Out-Line " Confirmando que todo cargo bien..." DarkGray + + Open-Browser "http://127.0.0.1:$port" | Out-Null + + # Confirm /healthz (separate from TCP-bound so the URL appears first). + $healthAt = $null + for ($i = 0; $i -lt 25; $i++) { + Start-Sleep -Milliseconds 200 + if (Get-ServerHealth $port 800) { $healthAt = $sw.Elapsed; break } + } + if ($healthAt) { + Out-Line " [ok] /healthz OK (t=$([int]$healthAt.TotalSeconds)s)" Green + } else { + Out-Line " [warn] El servidor abrio pero tardo en responder; revisa .run\server.log" Yellow + } + Out-Line "" + $launched = $true + break } if (-not $launched) { - Write-Host " [error] no se pudo iniciar el servidor." -ForegroundColor Red + Out-Line " [error] no se pudo iniciar el servidor en ninguno de los puertos candidatos." Red if (Test-Path '.run\server.log') { - Write-Host " --- ultimo log del servidor ---" -ForegroundColor Yellow - Get-Content '.run\server.log' -Tail 25 | ForEach-Object { Write-Host " $_" } + Out-Line " --- ultimo log del servidor ---" Yellow + Get-Content '.run\server.log' -Tail 25 | ForEach-Object { Out-Line " $_" } } else { - Write-Host " instala dependencias: pip install -e `".[web]`"" -ForegroundColor Yellow + Out-Line " instala dependencias: pip install -e `".[web]`"" Yellow } exit 3 } - -Open-Browser "http://127.0.0.1:$port" -Write-Host "" -Write-Host " [ok] servidor activo" -ForegroundColor Green -Write-Host " URL: http://localhost:$port" -Write-Host " PID: $($proc.Id) (ventana minimizada 'cmd' independiente)" -Write-Host " log: .run\server.log" -Write-Host " stop: stop-server.bat" -Write-Host ""