feat: start-server.ps1 acepta -PythonExe y copys amigables

This commit is contained in:
urieljareth
2026-08-22 11:26:17 -06:00
parent a5fd39a3e6
commit f038790caa
+167 -58
View File
@@ -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' $ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot $root = Split-Path -Parent $PSScriptRoot
Set-Location -LiteralPath $root 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) { function Open-Browser($url) {
$opened = $false $opened = $false
# Method 1: Start-Process with the URL (default protocol handler)
try { Start-Process -FilePath $url; $opened = $true } catch {} try { Start-Process -FilePath $url; $opened = $true } catch {}
if (-not $opened) { if (-not $opened) { try { & explorer.exe $url; $opened = $true } catch {} }
# Method 2: explorer.exe with the URL (opens default browser) if (-not $opened) { try { & cmd.exe /c start "" $url; $opened = $true } catch {} }
try { & explorer.exe $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 }
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 }
return $opened return $opened
} }
Write-Host "" # Fast TCP-connect probe. Replaces Invoke-WebRequest (which on Windows
Write-Host " [yt-scraper] iniciando servidor local..." -ForegroundColor Cyan # 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 } 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') { if (Test-Path '.run\server.info') {
$firstLine = (Get-Content '.run\server.info' -TotalCount 1) $firstLine = (Get-Content '.run\server.info' -TotalCount 1)
$existingPort = ($firstLine -split '\s+')[0] $parts = $firstLine -split '\s+'
try { if ($parts.Count -ge 2) {
$r = Invoke-WebRequest -Uri "http://localhost:$existingPort/healthz" -UseBasicParsing -TimeoutSec 2 $registeredPort = $parts[0]
if ($r.Content -match 'ok') { $registeredPid = $parts[1]
Write-Host " [ok] servidor ya activo en el puerto $existingPort" -ForegroundColor Green $portBusy = Test-PortOpen ([int]$registeredPort) 350
Open-Browser "http://127.0.0.1:$existingPort" $pidAlive = Is-PidAlive $registeredPid
exit 0 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 = @() $candidates = @()
foreach ($p in 8000..8100) { foreach ($p in 8000..8100) {
try { try {
@@ -50,53 +122,90 @@ foreach ($p in 8000..8100) {
} catch {} } catch {}
} }
if ($candidates.Count -eq 0) { 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 exit 2
} }
# --- (3) Launch uvicorn on each candidate until one sticks ---
# 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.
$launched = $false $launched = $false
$proc = $null
$port = 0 $port = 0
$proc = $null
foreach ($candidate in $candidates) { foreach ($candidate in $candidates) {
# clear previous log
Remove-Item '.run\server.log' -Force -ErrorAction SilentlyContinue 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' Out-Line " [...] Encendiendo el servidor (intento $candidate)..." Gray
$proc = Start-Process -FilePath 'cmd.exe' -ArgumentList $cmdLine -WorkingDirectory $root -WindowStyle Minimized -PassThru $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'
Set-Content -Path '.run\server.info' -Value "$candidate $($proc.Id) $(Get-Date -Format o)" -Encoding ASCII try {
$proc = Start-Process -FilePath 'cmd.exe' -ArgumentList $cmdLine -WorkingDirectory $root -WindowStyle Minimized -PassThru
$ok = $false } catch {
for ($i = 0; $i -lt 40; $i++) { Out-Line " [warn] Start-Process fallo en $candidate : $($_.Exception.Message)" Yellow
Start-Sleep -Milliseconds 500 continue
try {
$r = Invoke-WebRequest -Uri "http://localhost:$candidate/healthz" -UseBasicParsing -TimeoutSec 2
if ($r.Content -match 'ok') { $ok = $true; break }
} catch {}
} }
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 # Fast TCP-poll for the bound port. Bound == uvicorn listening, which
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue # is all the user needs to know "open this URL in a browser".
Remove-Item '.run\server.info' -Force -ErrorAction SilentlyContinue $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) { 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') { if (Test-Path '.run\server.log') {
Write-Host " --- ultimo log del servidor ---" -ForegroundColor Yellow Out-Line " --- ultimo log del servidor ---" Yellow
Get-Content '.run\server.log' -Tail 25 | ForEach-Object { Write-Host " $_" } Get-Content '.run\server.log' -Tail 25 | ForEach-Object { Out-Line " $_" }
} else { } else {
Write-Host " instala dependencias: pip install -e `".[web]`"" -ForegroundColor Yellow Out-Line " instala dependencias: pip install -e `".[web]`"" Yellow
} }
exit 3 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 ""