stacks/dotnet/hooks/dev/Start-Dev.ps1
|
# Start-Dev.ps1 # Loads .env.local and starts the development environment param( [switch]$NoLayout ) . "$PSScriptRoot/Common.ps1" Import-Module "$PSScriptRoot/DevLayout.psm1" -Force -DisableNameChecking $repoRoot = Invoke-InProjectRoot $projectName = Get-ProjectName $sessionName = $projectName.ToLower() Write-Host "" Write-Host "$projectName Development Environment" -ForegroundColor White Write-Host ("=" * "$projectName Development Environment".Length) -ForegroundColor White Write-Host "" # Load environment variables $envFile = Join-Path $repoRoot ".env.local" if (Test-Path $envFile) { Write-Status "Loading .env.local file" -Type Info try { $envVars = Load-EnvFile -Path $envFile -Export Write-Status "Loaded $($envVars.Count) environment variables" -Type Success } catch { Write-Status "Failed to load .env.local: $_" -Type Error exit 1 } } else { Write-Status ".env.local file not found" -Type Warn Write-Status "Copy .env.example to .env.local and configure your settings" -Type Info } Write-Host "" # Stop any existing processes first (makes this idempotent) & "$PSScriptRoot\Stop-Dev.ps1" -Quiet # Auto-detect API project $apiProjectRelPath = Find-ApiProject -RepoRoot $repoRoot if (-not $apiProjectRelPath) { Write-Status "No *Api.csproj found under src/" -Type Error exit 1 } $apiProjectPath = Join-Path $repoRoot $apiProjectRelPath if (-not (Test-Path $apiProjectPath)) { Write-Status "API project not found at: $apiProjectPath" -Type Error exit 1 } Write-Status "Found API project: $apiProjectRelPath" -Type Info # Ensure logs directory exists $logsDir = Join-Path $repoRoot "logs" if (-not (Test-Path $logsDir)) { New-Item -ItemType Directory -Path $logsDir -Force | Out-Null } # Ensure data directory exists (for SQLite) $dataDir = Join-Path $repoRoot "data" if (-not (Test-Path $dataDir)) { New-Item -ItemType Directory -Path $dataDir -Force | Out-Null } # Read layout config for port and URL $layoutConfigPath = Join-Path $PSScriptRoot "layout.json" $port = 5000 $openUrl = "" if (Test-Path $layoutConfigPath) { $layoutConfig = Get-Content $layoutConfigPath -Raw | ConvertFrom-Json if ($layoutConfig.port) { $port = $layoutConfig.port } if ($layoutConfig.openUrl) { $openUrl = $layoutConfig.openUrl } } $apiUrl = "http://localhost:$port" $healthUrl = "$apiUrl/health" $browserUrl = "$apiUrl$openUrl" # Open dev layout (which starts the API via dotnet watch) $layoutResult = $null if (-not $NoLayout -and (Test-Path $layoutConfigPath) -and $layoutConfig.enabled) { # Build the terminal command $commonScript = Join-Path $PSScriptRoot "Common.ps1" $terminalCommand = @" . '$commonScript' `$envFile = '$envFile' if (Test-Path `$envFile) { Load-EnvFile -Path `$envFile -Export | Out-Null } Set-Location '$repoRoot' dotnet watch --project $apiProjectRelPath "@ Write-Status "Opening dev layout..." -Type Info $layoutResult = Open-DevLayout ` -Monitor $layoutConfig.monitor ` -Layout $layoutConfig.layout ` -Terminals @($terminalCommand) ` -Urls @($browserUrl) ` -SessionName $sessionName if ($layoutResult.status -eq "running") { Write-Status "Layout opened: $($layoutResult.terminals) terminal(s), $($layoutResult.browsers) browser(s)" -Type Success } } if ($NoLayout -or -not $layoutResult) { Write-Status "No layout - starting API directly..." -Type Info Write-Status "Logs will be written to: $logsDir" -Type Info # Build the startup command that loads env vars and runs the API $commonScript = Join-Path $PSScriptRoot "Common.ps1" $startupCommand = @" . '$commonScript' `$envFile = '$envFile' if (Test-Path `$envFile) { Load-EnvFile -Path `$envFile -Export | Out-Null } Set-Location '$repoRoot' dotnet watch --project $apiProjectRelPath "@ # Start API in a visible window $apiProcess = Start-Process -FilePath "pwsh" -ArgumentList @( "-NoExit", "-Command", $startupCommand ) -PassThru Write-Status "API window opened (PID: $($apiProcess.Id))" -Type Success # Save PID for cleanup $pidFile = Join-Path $repoRoot ".bot\.dev-pids.json" $pids = @{ api_pid = $apiProcess.Id started_at = (Get-Date).ToString('o') } $pids | ConvertTo-Json | Set-Content $pidFile -Force } # Wait for health endpoint (up to 30 seconds) Write-Host "" Write-Status "Waiting for API to start..." -Type Info $timeout = 30 $elapsed = 0 $healthCheckPassed = $false # Helper function to check if API port is listening function Test-ApiPortListening { param([int]$Port) $listener = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue return $null -ne $listener } $portWasListening = $false while ($elapsed -lt $timeout) { $isListening = Test-ApiPortListening -Port $port if ($isListening) { $portWasListening = $true } elseif ($portWasListening) { # Port was listening but stopped - app crashed after starting Write-Status "API stopped listening (app may have crashed)" -Type Error break } try { $response = Invoke-WebRequest -Uri $healthUrl -Method GET -TimeoutSec 2 -ErrorAction SilentlyContinue if ($response.StatusCode -eq 200) { $healthCheckPassed = $true break } } catch { # API not ready yet } Start-Sleep -Milliseconds 500 $elapsed += 0.5 } # Determine final status $finalStatus = "running" if ($healthCheckPassed) { Write-Status "API is healthy" -Type Success # Refresh browser windows now that API is ready if ($layoutResult -and $sessionName) { $refreshResult = Send-BrowserRefresh -SessionName $sessionName -Quiet if ($refreshResult.count -gt 0) { Write-Status "Refreshed $($refreshResult.count) browser window(s)" -Type Success } } } else { # Health check failed - determine why $isListening = Test-ApiPortListening -Port $port if ($portWasListening -and -not $isListening) { $finalStatus = "failed" Write-Status "API crashed after starting" -Type Error Write-Status "Check logs at: $logsDir" -Type Info } elseif (-not $portWasListening) { $finalStatus = "failed" Write-Status "API never started listening on port $port" -Type Error Write-Status "Check the terminal window for build/startup errors" -Type Info } else { # Port is listening but health check failed $finalStatus = "starting" Write-Status "API is listening but health check timed out" -Type Warn Write-Status "Check logs for errors: $logsDir" -Type Info } } Write-Host "" Write-Host " API: $apiUrl" -ForegroundColor Cyan Write-Host " Health: $healthUrl" -ForegroundColor Gray Write-Host " Logs: $logsDir" -ForegroundColor Gray Write-Host "" Write-Host " Use 'dev_stop' MCP tool or run Stop-Dev.ps1 to stop" -ForegroundColor Gray Write-Host "" # Return status for MCP tool consumption $result = @{ api_url = $apiUrl status = $finalStatus } if ($layoutResult) { $result.layout = $layoutResult } return $result |