<# .SYNOPSIS CSM 开发环境一键启动脚本 .DESCRIPTION 并行启动后端服务、前端 dev server 和客户端。 Ctrl+C 一次停止所有进程。 .USAGE .\dev.ps1 # 启动全部 (server + web + client) .\dev.ps1 -NoClient # 只启动 server + web #> param( [switch]$NoClient ) $ErrorActionPreference = "Stop" $projectRoot = $PSScriptRoot if (-not $projectRoot) { $projectRoot = $PWD.Path } Write-Host "" Write-Host "======================================" -ForegroundColor Cyan Write-Host " CSM Dev Launcher" -ForegroundColor Cyan Write-Host "======================================" -ForegroundColor Cyan Write-Host "" # --- Ensure frontend dependencies --- if (-not (Test-Path "$projectRoot\web\node_modules")) { Write-Host "[web] Installing dependencies..." -ForegroundColor Yellow Push-Location "$projectRoot\web" npm install Pop-Location } # --- Ensure data directory --- $dataDir = Join-Path $projectRoot "data" if (-not (Test-Path $dataDir)) { New-Item -ItemType Directory -Path $dataDir | Out-Null Write-Host "[server] Created data directory" -ForegroundColor Green } # --- Build workspace first --- Write-Host "[build] Compiling workspace..." -ForegroundColor Yellow $buildOutput = cargo build --workspace 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host "[build] FAILED" -ForegroundColor Red Write-Host $buildOutput exit 1 } Write-Host "[build] OK" -ForegroundColor Green Write-Host "" # --- Track child processes --- $script:processes = [System.Collections.Generic.List[System.Diagnostics.Process]]::new() function Start-Proc { param( [string]$Name, [string]$FilePath, [string[]]$ArgumentList, [string]$WorkingDirectory = $projectRoot ) $proc = [System.Diagnostics.Process]::new() $proc.StartInfo.FileName = $FilePath $proc.StartInfo.ArgumentList.Clear() foreach ($arg in $ArgumentList) { $proc.StartInfo.ArgumentList.Add($arg) } $proc.StartInfo.WorkingDirectory = $WorkingDirectory $proc.StartInfo.UseShellExecute = $false $proc.StartInfo.RedirectStandardOutput = $false $proc.StartInfo.RedirectStandardError = $false $proc.Start() | Out-Null $script:processes.Add($proc) Write-Host " [$Name] PID $($proc.Id) started" -ForegroundColor Green } # --- Cleanup on exit --- $script:cleaningUp = $false function Cleanup { if ($script:cleaningUp) { return } $script:cleaningUp = $true Write-Host "" Write-Host "Stopping all services..." -ForegroundColor Yellow foreach ($p in $script:processes) { if (-not $p.HasExited) { try { $p.Kill($true) Write-Host " Stopped PID $($p.Id)" -ForegroundColor DarkGray } catch { # already exited } } } Write-Host "All services stopped." -ForegroundColor Yellow } # Register cleanup on script exit (Ctrl+C, terminal close) [Console]::TreatControlCAsInput = $false $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { Cleanup } try { # --- Start Server --- Write-Host "Starting services:" -ForegroundColor Cyan Start-Proc -Name "server" -FilePath "cargo" -ArgumentList "run", "-p", "csm-server" # Give server a moment to bind ports Start-Sleep -Milliseconds 1500 # --- Start Frontend --- Start-Proc -Name "web" -FilePath "npm" -ArgumentList "run", "dev" -WorkingDirectory "$projectRoot\web" # --- Start Client --- if (-not $NoClient) { Start-Sleep -Milliseconds 500 Start-Proc -Name "client" -FilePath "cargo" -ArgumentList "run", "-p", "csm-client" } Write-Host "" Write-Host "--------------------------------------" -ForegroundColor Cyan Write-Host " Server : http://localhost:9998" -ForegroundColor White Write-Host " Web UI : http://localhost:9997" -ForegroundColor White Write-Host " TCP : localhost:9999" -ForegroundColor White Write-Host "--------------------------------------" -ForegroundColor Cyan Write-Host "" Write-Host "Press Ctrl+C to stop all services." -ForegroundColor DarkGray Write-Host "" # --- Wait for any process to exit --- $handles = $script:processes | ForEach-Object { $_.Handle } $index = [System.Threading.WaitHandle]::WaitAny($handles) $exited = $script:processes[$index] $names = @("server", "web", "client") $name = if ($index -lt $names.Count) { $names[$index] } else { "unknown" } if ($exited.ExitCode -ne 0) { Write-Host "[$name] exited with code $($exited.ExitCode)" -ForegroundColor Red } } finally { Cleanup }