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" 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 (Test-Path $venvPython) { return $venvPython } $pythonCmd = Get-Command python -ErrorAction SilentlyContinue if ($null -ne $pythonCmd) { return $pythonCmd.Source } throw "Could not find Python. Install Python or create .venv first." } 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 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 } Start-Process ` -FilePath $FilePath ` -ArgumentList $Arguments ` -WorkingDirectory $WorkingDirectory ` -WindowStyle Hidden ` -RedirectStandardOutput $StdOutPath ` -RedirectStandardError $StdErrPath | Out-Null Write-LauncherLog "Started $Name." } 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 if (-not (Test-Path (Join-Path $repoRoot "frontend\dist\index.html"))) { Write-LauncherLog "Warning: frontend/dist is missing. Build frontend for production view." } if (Is-BackendRunning) { Write-LauncherLog "Backend already running. Skipping backend launch." } else { $backendOut = Join-Path $dataDir "backend.stdout.log" $backendErr = Join-Path $dataDir "backend.stderr.log" 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 (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 }