param( [switch]$SkipNpmInstall, [switch]$SkipDotnetRestore, [switch]$SkipBuild ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Invoke-Step { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][scriptblock]$Action ) Write-Host "==> $Name" & $Action if ($LASTEXITCODE -ne 0) { throw "Step failed: $Name" } } $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $repoRoot = Split-Path -Parent $scriptDir Push-Location $repoRoot try { if (-not $SkipNpmInstall) { Invoke-Step -Name "Install frontend tooling (npm install)" -Action { npm install } } Invoke-Step -Name "Lint frontend" -Action { npm run lint } Invoke-Step -Name "Check frontend formatting" -Action { npm run format:check } if (-not $SkipDotnetRestore) { Invoke-Step -Name "Restore .NET solution" -Action { dotnet restore GameList.sln } } if (-not $SkipBuild) { Invoke-Step -Name "Build .NET solution (warnings as errors)" -Action { dotnet build GameList.sln --no-restore -warnaserror } } Invoke-Step -Name "Run tests" -Action { if ($SkipBuild) { dotnet test GameList.Tests/GameList.Tests.csproj --verbosity normal --collect:"XPlat Code Coverage" } else { dotnet test GameList.Tests/GameList.Tests.csproj --no-build --verbosity normal --collect:"XPlat Code Coverage" } } Invoke-Step -Name "Enforce coverage thresholds" -Action { pwsh ./scripts/check-coverage.ps1 -MinLineRate 0.90 -MinBranchRate 0.70 } Write-Host "CI checks passed." } finally { Pop-Location }