9ba6e3ec0a
Require the project virtual environment and wait for backend readiness before launching the overlay. Co-Authored-By: Claude <noreply@anthropic.com>
201 lines
6.1 KiB
PowerShell
201 lines
6.1 KiB
PowerShell
param(
|
|
[switch]$DryRun
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$repoRoot = Split-Path -Parent $PSScriptRoot
|
|
$overlayDir = Join-Path $repoRoot "overlay"
|
|
$dataDir = Join-Path $repoRoot "data"
|
|
$launcherLog = Join-Path $dataDir "startup-launcher.log"
|
|
$backendUrl = "http://127.0.0.1:8000/api/state"
|
|
|
|
if (-not (Test-Path $dataDir)) {
|
|
New-Item -ItemType Directory -Path $dataDir | Out-Null
|
|
}
|
|
|
|
function Write-LauncherLog {
|
|
param([string]$Message)
|
|
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
Add-Content -Path $launcherLog -Value "[$timestamp] $Message"
|
|
}
|
|
|
|
function Resolve-PythonPath {
|
|
$venvPython = Join-Path $repoRoot ".venv\Scripts\python.exe"
|
|
if (-not (Test-Path $venvPython)) {
|
|
throw "Project Python environment not found: $venvPython. Run: py -3.14 -m venv .venv"
|
|
}
|
|
|
|
return (Resolve-Path $venvPython).Path
|
|
}
|
|
|
|
function Resolve-NpmPath {
|
|
$npmCmd = Get-Command npm.cmd -ErrorAction SilentlyContinue
|
|
if ($null -ne $npmCmd) {
|
|
return $npmCmd.Source
|
|
}
|
|
|
|
throw "Could not find npm.cmd. Install Node.js/npm first."
|
|
}
|
|
|
|
function Test-PythonRuntime {
|
|
param([string]$PythonPath)
|
|
|
|
$output = & $PythonPath -c "import uvicorn, backend.main; print('backend imports OK')" 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
$details = ($output | Out-String).Trim()
|
|
throw "Backend Python runtime validation failed: $details"
|
|
}
|
|
|
|
Write-LauncherLog "Python runtime verified: $PythonPath"
|
|
}
|
|
|
|
function Start-HiddenProcess {
|
|
param(
|
|
[string]$Name,
|
|
[string]$FilePath,
|
|
[string[]]$Arguments,
|
|
[string]$WorkingDirectory,
|
|
[string]$StdOutPath,
|
|
[string]$StdErrPath
|
|
)
|
|
|
|
if ($DryRun) {
|
|
Write-LauncherLog "DRY RUN -> $Name | $FilePath $($Arguments -join ' ')"
|
|
return $null
|
|
}
|
|
|
|
$process = Start-Process `
|
|
-FilePath $FilePath `
|
|
-ArgumentList $Arguments `
|
|
-WorkingDirectory $WorkingDirectory `
|
|
-WindowStyle Hidden `
|
|
-RedirectStandardOutput $StdOutPath `
|
|
-RedirectStandardError $StdErrPath `
|
|
-PassThru
|
|
|
|
Write-LauncherLog "Started $Name process (PID $($process.Id))."
|
|
return $process
|
|
}
|
|
|
|
function Test-BackendReady {
|
|
try {
|
|
$response = Invoke-WebRequest -UseBasicParsing -Uri $backendUrl -Method Get -TimeoutSec 2 -ErrorAction Stop
|
|
return $response.StatusCode -eq 200
|
|
} catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Wait-ForBackendReady {
|
|
param(
|
|
[System.Diagnostics.Process]$Process,
|
|
[int]$TimeoutSeconds = 30
|
|
)
|
|
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
do {
|
|
if (Test-BackendReady) {
|
|
return $true
|
|
}
|
|
|
|
if ($null -ne $Process) {
|
|
$Process.Refresh()
|
|
if ($Process.HasExited) {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
Start-Sleep -Milliseconds 500
|
|
} while ((Get-Date) -lt $deadline)
|
|
|
|
return $false
|
|
}
|
|
|
|
function Is-BackendRunning {
|
|
$matches = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.Name -like "python*" -and
|
|
$_.CommandLine -and
|
|
$_.CommandLine -like "*backend.main:app*"
|
|
}
|
|
return ($matches | Measure-Object).Count -gt 0
|
|
}
|
|
|
|
function Is-OverlayRunning {
|
|
$matches = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.CommandLine -and
|
|
$_.CommandLine -like "*custom-streamdeck*overlay*"
|
|
}
|
|
return ($matches | Measure-Object).Count -gt 0
|
|
}
|
|
|
|
try {
|
|
$pythonPath = Resolve-PythonPath
|
|
$npmPath = Resolve-NpmPath
|
|
Write-LauncherLog "Launcher using Python: $pythonPath"
|
|
|
|
if (-not $DryRun) {
|
|
Test-PythonRuntime $pythonPath
|
|
}
|
|
|
|
if (-not (Test-Path (Join-Path $repoRoot "frontend\dist\index.html"))) {
|
|
Write-LauncherLog "Warning: frontend/dist is missing. Build frontend for production view."
|
|
}
|
|
|
|
$backendReady = Test-BackendReady
|
|
if ($backendReady) {
|
|
Write-LauncherLog "Backend already ready at $backendUrl. Skipping backend launch."
|
|
} elseif (Is-BackendRunning) {
|
|
Write-LauncherLog "Backend process exists but is not ready. Waiting for $backendUrl."
|
|
if (-not $DryRun -and -not (Wait-ForBackendReady -TimeoutSeconds 30)) {
|
|
throw "Existing backend process did not become ready at $backendUrl. See data/backend.stderr.log."
|
|
}
|
|
$backendReady = $true
|
|
Write-LauncherLog "Backend ready at $backendUrl."
|
|
} else {
|
|
$backendOut = Join-Path $dataDir "backend.stdout.log"
|
|
$backendErr = Join-Path $dataDir "backend.stderr.log"
|
|
$backendProcess = Start-HiddenProcess `
|
|
-Name "backend" `
|
|
-FilePath $pythonPath `
|
|
-Arguments @("-m", "uvicorn", "backend.main:app", "--host", "127.0.0.1", "--port", "8000") `
|
|
-WorkingDirectory $repoRoot `
|
|
-StdOutPath $backendOut `
|
|
-StdErrPath $backendErr
|
|
|
|
if (-not $DryRun -and -not (Wait-ForBackendReady -Process $backendProcess -TimeoutSeconds 30)) {
|
|
$stderrTail = if (Test-Path $backendErr) { (Get-Content $backendErr -Tail 10 | Out-String).Trim() } else { "No backend stderr log was created." }
|
|
Write-LauncherLog "Backend failed readiness check. stderr tail: $stderrTail"
|
|
throw "Backend did not become ready at $backendUrl."
|
|
}
|
|
|
|
$backendReady = $true
|
|
if (-not $DryRun) {
|
|
Write-LauncherLog "Backend ready at $backendUrl."
|
|
}
|
|
}
|
|
|
|
if (-not $backendReady -and -not $DryRun) {
|
|
throw "Overlay launch skipped because the backend is not ready."
|
|
}
|
|
|
|
if (Is-OverlayRunning) {
|
|
Write-LauncherLog "Overlay already running. Skipping overlay launch."
|
|
} else {
|
|
$overlayOut = Join-Path $dataDir "overlay.stdout.log"
|
|
$overlayErr = Join-Path $dataDir "overlay.stderr.log"
|
|
Start-HiddenProcess `
|
|
-Name "overlay" `
|
|
-FilePath $npmPath `
|
|
-Arguments @("run", "start") `
|
|
-WorkingDirectory $overlayDir `
|
|
-StdOutPath $overlayOut `
|
|
-StdErrPath $overlayErr
|
|
}
|
|
} catch {
|
|
Write-LauncherLog "Launcher failed: $($_.Exception.Message)"
|
|
throw
|
|
}
|