#!/usr/bin/env pwsh # regenerate-solution.ps1 - Regenerate StellaOps.sln without duplicate projects # # This script: # 1. Backs up the existing solution # 2. Creates a new solution # 3. Adds all .csproj files, skipping duplicates # 4. Preserves solution folders where possible param( [string]$SolutionPath = "src/StellaOps.sln", [switch]$DryRun ) $ErrorActionPreference = "Stop" # Canonical locations for test projects (in priority order) # Later entries win when there are duplicates $canonicalPatterns = @( # Module-local tests (highest priority) "src/*/__Tests/*/*.csproj", "src/*/__Libraries/__Tests/*/*.csproj", "src/__Libraries/__Tests/*/*.csproj", # Cross-module integration tests "src/__Tests/Integration/*/*.csproj", "src/__Tests/__Libraries/*/*.csproj", # Category-based cross-module tests "src/__Tests/chaos/*/*.csproj", "src/__Tests/security/*/*.csproj", "src/__Tests/interop/*/*.csproj", "src/__Tests/parity/*/*.csproj", "src/__Tests/reachability/*/*.csproj", # Single global tests "src/__Tests/*/*.csproj" ) Write-Host "=== Solution Regeneration Script ===" -ForegroundColor Cyan Write-Host "Solution: $SolutionPath" Write-Host "Dry Run: $DryRun" Write-Host "" # Find all .csproj files Write-Host "Finding all project files..." -ForegroundColor Yellow $allProjects = Get-ChildItem -Path "src" -Filter "*.csproj" -Recurse | Where-Object { $_.FullName -notmatch "\\obj\\" -and $_.FullName -notmatch "\\bin\\" } Write-Host "Found $($allProjects.Count) project files" # Build a map of project name -> list of paths $projectMap = @{} foreach ($proj in $allProjects) { $name = $proj.BaseName if (-not $projectMap.ContainsKey($name)) { $projectMap[$name] = @() } $projectMap[$name] += $proj.FullName } # Find duplicates $duplicates = $projectMap.GetEnumerator() | Where-Object { $_.Value.Count -gt 1 } Write-Host "" Write-Host "Found $($duplicates.Count) projects with duplicate names:" -ForegroundColor Yellow foreach ($dup in $duplicates) { Write-Host " $($dup.Key):" -ForegroundColor Red foreach ($path in $dup.Value) { Write-Host " - $path" } } # Select canonical path for each project function Get-CanonicalPath { param([string[]]$Paths) # Prefer module-local __Tests over global __Tests $moduleTests = $Paths | Where-Object { $_ -match "src\\[^_][^\\]+\\__Tests\\" } if ($moduleTests.Count -gt 0) { return $moduleTests[0] } # Prefer __Libraries/__Tests $libTests = $Paths | Where-Object { $_ -match "__Libraries\\__Tests\\" } if ($libTests.Count -gt 0) { return $libTests[0] } # Prefer __Tests over non-__Tests location in same parent $testsPath = $Paths | Where-Object { $_ -match "\\__Tests\\" } if ($testsPath.Count -gt 0) { return $testsPath[0] } # Otherwise, take first return $Paths[0] } # Build final project list $finalProjects = @() foreach ($entry in $projectMap.GetEnumerator()) { $canonical = Get-CanonicalPath -Paths $entry.Value $finalProjects += $canonical } Write-Host "" Write-Host "Final project count: $($finalProjects.Count)" -ForegroundColor Green if ($DryRun) { Write-Host "" Write-Host "=== DRY RUN - No changes made ===" -ForegroundColor Magenta Write-Host "Would add the following projects to solution:" $finalProjects | ForEach-Object { Write-Host " $_" } exit 0 } # Backup existing solution $backupPath = "$SolutionPath.bak" if (Test-Path $SolutionPath) { Copy-Item $SolutionPath $backupPath -Force Write-Host "Backed up existing solution to $backupPath" -ForegroundColor Gray } # Create new solution Write-Host "" Write-Host "Creating new solution..." -ForegroundColor Yellow $slnDir = Split-Path $SolutionPath -Parent $slnName = [System.IO.Path]::GetFileNameWithoutExtension($SolutionPath) # Remove old solution if (Test-Path $SolutionPath) { Remove-Item $SolutionPath -Force } # Create fresh solution Push-Location $slnDir dotnet new sln -n $slnName --force 2>$null Pop-Location # Add projects in batches (dotnet sln add can handle multiple) Write-Host "Adding projects to solution..." -ForegroundColor Yellow $added = 0 $failed = 0 foreach ($proj in $finalProjects) { try { $result = dotnet sln $SolutionPath add $proj 2>&1 if ($LASTEXITCODE -eq 0) { $added++ if ($added % 50 -eq 0) { Write-Host " Added $added projects..." -ForegroundColor Gray } } else { Write-Host " Failed to add: $proj" -ForegroundColor Red $failed++ } } catch { Write-Host " Error adding: $proj - $_" -ForegroundColor Red $failed++ } } Write-Host "" Write-Host "=== Summary ===" -ForegroundColor Cyan Write-Host "Projects added: $added" -ForegroundColor Green Write-Host "Projects failed: $failed" -ForegroundColor $(if ($failed -gt 0) { "Red" } else { "Green" }) Write-Host "" Write-Host "Solution regenerated at: $SolutionPath" # Verify Write-Host "" Write-Host "Verifying solution..." -ForegroundColor Yellow $verifyResult = dotnet build $SolutionPath --no-restore -t:ValidateSolutionConfiguration 2>&1 if ($LASTEXITCODE -eq 0) { Write-Host "Solution validation passed!" -ForegroundColor Green } else { Write-Host "Solution validation had issues - check manually" -ForegroundColor Yellow }