#!/usr/bin/env pwsh <# .SYNOPSIS Builds (and optionally tests) all module solutions under src/. .DESCRIPTION Discovers all *.sln files under src/ (excluding the root StellaOps.sln) and runs dotnet build on each. Pass -Test to also run dotnet test. .PARAMETER Test Also run dotnet test on each solution after building. .PARAMETER Configuration Build configuration. Defaults to Debug. .EXAMPLE .\scripts\build-all-solutions.ps1 .\scripts\build-all-solutions.ps1 -Test .\scripts\build-all-solutions.ps1 -Test -Configuration Release #> [CmdletBinding()] param( [switch]$Test, [switch]$StopRepoHostProcesses, [ValidateSet('Debug', 'Release')] [string]$Configuration = 'Debug' ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Continue' $repoRoot = Split-Path -Parent $PSScriptRoot $srcDir = Join-Path $repoRoot 'src' function Get-RepoRelativePath { param( [Parameter(Mandatory = $true)] [string]$Root, [Parameter(Mandatory = $true)] [string]$Path ) $normalizedRoot = [System.IO.Path]::GetFullPath($Root).TrimEnd('\', '/') $normalizedPath = [System.IO.Path]::GetFullPath($Path) if ($normalizedPath.StartsWith($normalizedRoot, [System.StringComparison]::OrdinalIgnoreCase)) { return $normalizedPath.Substring($normalizedRoot.Length).TrimStart('\', '/') } return $normalizedPath } function Test-RepoOwnedText { param( [Parameter(Mandatory = $true)] [string]$Root, [AllowNull()] [string]$Value ) if ([string]::IsNullOrWhiteSpace($Value)) { return $false } $normalizedRoot = [System.IO.Path]::GetFullPath($Root).TrimEnd('\', '/') $normalizedValue = $Value.Replace('/', '\') return $normalizedValue.IndexOf($normalizedRoot, [System.StringComparison]::OrdinalIgnoreCase) -ge 0 } function Test-IsWindowsPlatform { return [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( [System.Runtime.InteropServices.OSPlatform]::Windows) } function Stop-RepoHostProcesses { param( [Parameter(Mandatory = $true)] [string]$Root ) if (-not (Test-IsWindowsPlatform)) { return } $candidates = @(Get-CimInstance Win32_Process -Filter "Name = 'dotnet.exe' OR Name LIKE 'StellaOps.%'") $staleProcesses = @() foreach ($candidate in $candidates) { if ($candidate.ProcessId -eq $PID) { continue } $executablePath = "$($candidate.ExecutablePath)" $commandLine = "$($candidate.CommandLine)" $name = "$($candidate.Name)" $repoOwned = (Test-RepoOwnedText -Root $Root -Value $executablePath) -or (Test-RepoOwnedText -Root $Root -Value $commandLine) if (-not $repoOwned) { continue } $looksLikeService = $name -like 'StellaOps.*' -or $commandLine -match 'StellaOps\.[A-Za-z0-9_.-]+' if (-not $looksLikeService) { continue } $staleProcesses += [pscustomobject]@{ ProcessId = $candidate.ProcessId Name = $name ExecutablePath = $executablePath CommandLine = $commandLine } } $staleProcesses = @($staleProcesses | Sort-Object ProcessId -Unique) if ($staleProcesses.Count -eq 0) { Write-Host 'No repo-local Stella host processes detected.' -ForegroundColor DarkGray return } Write-Host "Stopping $($staleProcesses.Count) repo-local Stella host process(es) before build." -ForegroundColor Yellow foreach ($stale in $staleProcesses) { $location = if (-not [string]::IsNullOrWhiteSpace($stale.ExecutablePath)) { Get-RepoRelativePath -Root $Root -Path $stale.ExecutablePath } else { $stale.CommandLine } Write-Host " - [$($stale.ProcessId)] $($stale.Name) :: $location" -ForegroundColor DarkYellow Stop-Process -Id $stale.ProcessId -Force -ErrorAction Stop } Start-Sleep -Seconds 1 $remaining = @($staleProcesses | Where-Object { Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue }) if ($remaining.Count -gt 0) { $remainingIds = ($remaining | ForEach-Object { $_.ProcessId }) -join ', ' throw "Failed to stop repo-local Stella host processes: $remainingIds" } Write-Host 'Repo-local Stella host processes stopped.' -ForegroundColor Green } if ($StopRepoHostProcesses) { Stop-RepoHostProcesses -Root $repoRoot } $solutions = Get-ChildItem -Path $srcDir -Filter '*.sln' -Recurse | Where-Object { $_.Name -ne 'StellaOps.sln' -and $_.FullName -notmatch '[\\/](node_modules|bin|obj)[\\/]' } | Sort-Object FullName if ($solutions.Count -eq 0) { Write-Error 'No solution files found under src/.' exit 1 } Write-Host "Found $($solutions.Count) solution(s) to build." -ForegroundColor Cyan Write-Host '' $buildPass = @() $buildFail = @() $testPass = @() $testFail = @() $testSkipped = @() foreach ($sln in $solutions) { $rel = Get-RepoRelativePath -Root $repoRoot -Path $sln.FullName Write-Host "--- BUILD: $rel ---" -ForegroundColor Yellow dotnet build $sln.FullName --configuration $Configuration --nologo -v quiet if ($LASTEXITCODE -eq 0) { $buildPass += $rel } else { $buildFail += $rel Write-Host " FAILED" -ForegroundColor Red continue # skip test if build failed } if ($Test) { Write-Host "--- TEST: $rel ---" -ForegroundColor Yellow dotnet test $sln.FullName --configuration $Configuration --nologo --no-build -v quiet if ($LASTEXITCODE -eq 0) { $testPass += $rel } else { $testFail += $rel Write-Host " TEST FAILED" -ForegroundColor Red } } } Write-Host '' Write-Host '========== Summary ==========' -ForegroundColor Cyan Write-Host "Build passed : $($buildPass.Count)" -ForegroundColor Green if ($buildFail.Count -gt 0) { Write-Host "Build failed : $($buildFail.Count)" -ForegroundColor Red $buildFail | ForEach-Object { Write-Host " - $_" -ForegroundColor Red } } if ($Test) { Write-Host "Test passed : $($testPass.Count)" -ForegroundColor Green if ($testFail.Count -gt 0) { Write-Host "Test failed : $($testFail.Count)" -ForegroundColor Red $testFail | ForEach-Object { Write-Host " - $_" -ForegroundColor Red } } } if ($buildFail.Count -gt 0 -or $testFail.Count -gt 0) { exit 1 }