From 5b23fef41d388948aae9b535c0af640bd0e7e785 Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:58:33 -0400 Subject: [PATCH 1/4] Add Fix-BranchRuleset.ps1 and update Setup-BranchRuleset.ps1 - Add Fix-BranchRuleset.ps1: inspects, disables, and renames existing rulesets so Setup-BranchRuleset.ps1 can recreate them cleanly - Update Setup-BranchRuleset.ps1 to match current repo-template with correct repository name and status check names Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/Fix-BranchRuleset.ps1 | 233 ++++++++++++++++++++++++++++++++ scripts/Setup-BranchRuleset.ps1 | 46 ++++--- 2 files changed, 257 insertions(+), 22 deletions(-) create mode 100644 scripts/Fix-BranchRuleset.ps1 diff --git a/scripts/Fix-BranchRuleset.ps1 b/scripts/Fix-BranchRuleset.ps1 new file mode 100644 index 0000000..2947de0 --- /dev/null +++ b/scripts/Fix-BranchRuleset.ps1 @@ -0,0 +1,233 @@ +<# +.SYNOPSIS + Fixes branch rulesets by disabling existing ones and recreating with the correct configuration. + +.DESCRIPTION + This script inspects the existing branch rulesets for a repository, disables all of them, + and renames any ruleset named "Protect main branch" to "Protect main branch (old)" so that + Setup-BranchRuleset.ps1 can create a fresh ruleset without conflicts. + + The script presents a plan of all changes before executing and prompts for confirmation. + +.PARAMETER Repository + The repository in owner/repo format. If not provided, uses the current repository. + +.EXAMPLE + .\Fix-BranchRuleset.ps1 + Inspects and fixes rulesets for the current repository + +.EXAMPLE + .\Fix-BranchRuleset.ps1 -Repository "Chris-Wolfgang/my-repo" + Inspects and fixes rulesets for a specific repository + +.NOTES + Requires: GitHub CLI (gh) authenticated with admin permissions + Install gh: https://cli.github.com/ +#> + +[CmdletBinding()] +param( + [Parameter()] + [string]$Repository = "Chris-Wolfgang/ETL-Abstractions" +) + +# Check if gh CLI is installed +try { + $null = gh --version +} catch { + Write-Error "GitHub CLI (gh) is not installed or not in PATH." + Write-Host "Install from: https://cli.github.com/" -ForegroundColor Yellow + exit 1 +} + +# Check if authenticated +try { + $null = gh auth status 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Error "Not authenticated with GitHub CLI." + Write-Host "Run: gh auth login" -ForegroundColor Yellow + exit 1 + } +} catch { + Write-Error "Failed to check GitHub CLI authentication status." + exit 1 +} + +# Determine repository +if ($Repository -eq "Chris-Wolfgang/ETL-Abstractions" -or -not $Repository) { + Write-Host "Detecting current repository..." -ForegroundColor Cyan + try { + $repoInfo = gh repo view --json nameWithOwner | ConvertFrom-Json + $Repository = $repoInfo.nameWithOwner + Write-Host "Using repository: $Repository" -ForegroundColor Green + } catch { + if ($Repository -eq "Chris-Wolfgang/ETL-Abstractions") { + Write-Error "Could not detect repository. Please run the setup script first to replace placeholders, or specify -Repository parameter." + } else { + Write-Error "Could not detect repository. Please run from within a git repository or specify -Repository parameter." + } + exit 1 + } +} else { + Write-Host "Using specified repository: $Repository" -ForegroundColor Green +} + +# Fetch all rulesets +Write-Host "`nFetching existing rulesets..." -ForegroundColor Cyan + +try { + $rulesetsJson = gh api ` + -H "Accept: application/vnd.github+json" ` + -H "X-GitHub-Api-Version: 2022-11-28" ` + "/repos/$Repository/rulesets" ` + --paginate 2>&1 + + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to fetch rulesets: $rulesetsJson" + exit 1 + } + + $rulesets = $rulesetsJson | ConvertFrom-Json +} catch { + Write-Error "Failed to fetch rulesets: $($_.Exception.Message)" + exit 1 +} + +if (-not $rulesets -or $rulesets.Count -eq 0) { + Write-Host "No rulesets found for $Repository. Nothing to fix." -ForegroundColor Green + exit 0 +} + +# Build the plan +$plan = @() +$targetRulesetName = "Protect main branch" + +Write-Host "`nFound $($rulesets.Count) ruleset(s):" -ForegroundColor Cyan +Write-Host "" + +foreach ($ruleset in $rulesets) { + $status = if ($ruleset.enforcement -eq "disabled") { "disabled" } else { $ruleset.enforcement } + Write-Host " [$($ruleset.id)] $($ruleset.name) (enforcement: $status)" -ForegroundColor Gray + + $actions = @() + + # If this is the target name, rename it + if ($ruleset.name -eq $targetRulesetName) { + $actions += @{ + type = "rename" + description = "Rename '$($ruleset.name)' -> '$($ruleset.name) (old)'" + newName = "$($ruleset.name) (old)" + } + } + + # If not already disabled, disable it + if ($ruleset.enforcement -ne "disabled") { + $actions += @{ + type = "disable" + description = "Disable '$($ruleset.name)' (currently: $status)" + } + } + + if ($actions.Count -gt 0) { + $plan += @{ + ruleset = $ruleset + actions = $actions + } + } +} + +Write-Host "" + +# Present the plan +if ($plan.Count -eq 0) { + Write-Host "All rulesets are already disabled and none need renaming. Nothing to do." -ForegroundColor Green + exit 0 +} + +Write-Host "Planned changes:" -ForegroundColor Yellow +Write-Host "" + +$stepNumber = 1 +foreach ($item in $plan) { + foreach ($action in $item.actions) { + Write-Host " $stepNumber. $($action.description)" -ForegroundColor White + $stepNumber++ + } +} + +Write-Host "" + +# Prompt for confirmation +$response = Read-Host "Proceed with these changes? (y/N)" +if ($response -ne 'y' -and $response -ne 'Y') { + Write-Host "Cancelled. No changes were made." -ForegroundColor Yellow + exit 0 +} + +Write-Host "" + +# Execute the plan +$errors = 0 + +foreach ($item in $plan) { + $ruleset = $item.ruleset + $rulesetId = $ruleset.id + + # Build the update payload — apply rename and disable together in one API call + $updatePayload = @{} + + foreach ($action in $item.actions) { + switch ($action.type) { + "rename" { + $updatePayload["name"] = $action.newName + } + "disable" { + $updatePayload["enforcement"] = "disabled" + } + } + } + + if ($updatePayload.Count -gt 0) { + $descriptions = ($item.actions | ForEach-Object { $_.description }) -join " + " + Write-Host " Updating ruleset [$rulesetId]: $descriptions..." -ForegroundColor Cyan + + $jsonPayload = $updatePayload | ConvertTo-Json -Depth 5 + $tempFile = [System.IO.Path]::GetTempFileName() + $jsonPayload | Out-File -FilePath $tempFile -Encoding utf8NoBOM + + try { + $result = gh api ` + --method PUT ` + -H "Accept: application/vnd.github+json" ` + -H "X-GitHub-Api-Version: 2022-11-28" ` + "/repos/$Repository/rulesets/$rulesetId" ` + --input $tempFile 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host " Done." -ForegroundColor Green + } else { + Write-Host " Failed: $result" -ForegroundColor Red + $errors++ + } + } catch { + Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red + $errors++ + } finally { + if (Test-Path $tempFile) { + Remove-Item $tempFile -Force + } + } + } +} + +Write-Host "" + +if ($errors -gt 0) { + Write-Host "$errors action(s) failed. Review the errors above." -ForegroundColor Red + exit 1 +} else { + Write-Host "All changes applied successfully." -ForegroundColor Green + Write-Host "" + Write-Host "Next step: Run .\Setup-BranchRuleset.ps1 to create a fresh ruleset." -ForegroundColor Cyan + Write-Host "View rulesets at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan +} diff --git a/scripts/Setup-BranchRuleset.ps1 b/scripts/Setup-BranchRuleset.ps1 index 5110464..489cfde 100644 --- a/scripts/Setup-BranchRuleset.ps1 +++ b/scripts/Setup-BranchRuleset.ps1 @@ -54,7 +54,7 @@ [CmdletBinding()] param( [Parameter()] - [string]$Repository = "{{GITHUB_USERNAME}}/{{REPO_NAME}}", + [string]$Repository = "Chris-Wolfgang/ETL-Abstractions", [Parameter()] [string]$BranchName = "main" @@ -83,7 +83,7 @@ try { } # Determine repository -if ($Repository -eq "{{GITHUB_USERNAME}}/{{REPO_NAME}}" -or -not $Repository) { +if ($Repository -eq "Chris-Wolfgang/ETL-Abstractions" -or -not $Repository) { # Placeholders not replaced or no repository specified - auto-detect Write-Host "🔍 Detecting current repository..." -ForegroundColor Cyan try { @@ -91,7 +91,7 @@ if ($Repository -eq "{{GITHUB_USERNAME}}/{{REPO_NAME}}" -or -not $Repository) { $Repository = $repoInfo.nameWithOwner Write-Host "✅ Using repository: $Repository" -ForegroundColor Green } catch { - if ($Repository -eq "{{GITHUB_USERNAME}}/{{REPO_NAME}}") { + if ($Repository -eq "Chris-Wolfgang/ETL-Abstractions") { Write-Error "❌ Could not detect repository. Please run the setup script (pwsh ./scripts/setup.ps1) first to replace placeholders, or specify -Repository parameter." } else { Write-Error "❌ Could not detect repository. Please run from within a git repository or specify -Repository parameter." @@ -108,28 +108,33 @@ Write-Host "📌 Protected branch: $BranchName`n" -ForegroundColor Cyan # Check if ruleset already exists Write-Host "🔍 Checking for existing rulesets..." -ForegroundColor Yellow try { - $matchingRulesets = gh api ` + $rulesetOutput = gh api ` -H "Accept: application/vnd.github+json" ` -H "X-GitHub-Api-Version: 2022-11-28" ` "/repos/$Repository/rulesets" ` --paginate ` - --jq '.[] | select(.name == "Protect main branch")' | ConvertFrom-Json - - $existingRuleset = $matchingRulesets | Select-Object -First 1 - - if ($existingRuleset) { - Write-Host "✅ Ruleset 'Protect main branch' already exists!" -ForegroundColor Green - Write-Host " View it at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan - $response = Read-Host "`nDo you want to continue anyway? This may fail. (y/N)" - if ($response -ne 'y' -and $response -ne 'Y') { - Write-Host "Exiting." -ForegroundColor Yellow - exit 0 + --jq '.[] | select(.name == "Protect main branch")' 2>&1 + + if ($LASTEXITCODE -ne 0) { + Write-Warning "âš ī¸ Could not check for existing rulesets (API returned exit code $LASTEXITCODE). Continuing..." + } elseif ($rulesetOutput) { + $matchingRulesets = $rulesetOutput | ConvertFrom-Json + $existingRuleset = $matchingRulesets | Select-Object -First 1 + + if ($existingRuleset) { + Write-Host "✅ Ruleset 'Protect main branch' already exists!" -ForegroundColor Green + Write-Host " View it at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan + $response = Read-Host "`nDo you want to continue anyway? This may fail. (y/N)" + if ($response -ne 'y' -and $response -ne 'Y') { + Write-Host "Exiting." -ForegroundColor Yellow + exit 0 + } } } else { Write-Host "â„šī¸ Ruleset 'Protect main branch' does not exist yet." -ForegroundColor Gray } } catch { - Write-Warning "âš ī¸ Could not check for existing rulesets. Continuing..." + Write-Warning "âš ī¸ Could not check for existing rulesets: $($_.Exception.Message). Continuing..." } # Prompt for repository type @@ -195,7 +200,7 @@ $rulesetConfig = @{ @{ context = "Stage 2: Windows Tests (.NET 5.0-10.0, Framework 4.6.2-4.8.1)" }, @{ context = "Stage 3: macOS Tests (.NET 6.0-10.0)" }, @{ context = "Security Scan (DevSkim)" }, - @{ context = "Security Scan (CodeQL)" } + @{ context = "CodeQL Security Analysis / Security Scan (CodeQL) (csharp) (pull_request)" } ) } }, @@ -239,9 +244,6 @@ $rulesetConfig = @{ }, @{ type = "deletion" - }, - @{ - type = "update" } ) } @@ -251,7 +253,7 @@ $jsonConfig = $rulesetConfig | ConvertTo-Json -Depth 10 # Save to temporary file $tempFile = [System.IO.Path]::GetTempFileName() -$jsonConfig | Out-File -FilePath $tempFile -Encoding UTF8 +$jsonConfig | Out-File -FilePath $tempFile -Encoding utf8NoBOM try { Write-Host "🚀 Creating branch ruleset..." -ForegroundColor Cyan @@ -279,7 +281,7 @@ try { Write-Host " - Stage 2: Windows Tests (.NET 5.0-10.0, Framework 4.6.2-4.8.1)" -ForegroundColor DarkGray Write-Host " - Stage 3: macOS Tests (.NET 6.0-10.0)" -ForegroundColor DarkGray Write-Host " - Security Scan (DevSkim)" -ForegroundColor DarkGray - Write-Host " - Security Scan (CodeQL)" -ForegroundColor DarkGray + Write-Host " - CodeQL Security Analysis / Security Scan (CodeQL) (csharp) (pull_request)" -ForegroundColor DarkGray Write-Host " ✅ Branches must be up to date before merging" -ForegroundColor Gray Write-Host " ✅ Conversation resolution required before merging" -ForegroundColor Gray Write-Host " ✅ Stale reviews dismissed when new commits are pushed" -ForegroundColor Gray From 25d45bc9d6379278f68c3369fa307312d6eadd02 Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:38:16 -0400 Subject: [PATCH 2/4] Remove CodeQL from required_status_checks in Setup-BranchRuleset.ps1 CodeQL is already enforced via the code_scanning rule type, so the required_status_checks entry is redundant and blocks PRs when the check name doesn't match exactly. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/Setup-BranchRuleset.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/Setup-BranchRuleset.ps1 b/scripts/Setup-BranchRuleset.ps1 index 489cfde..181bfca 100644 --- a/scripts/Setup-BranchRuleset.ps1 +++ b/scripts/Setup-BranchRuleset.ps1 @@ -199,8 +199,7 @@ $rulesetConfig = @{ @{ context = "Stage 1: Linux Tests (.NET 5.0-10.0) + Coverage Gate" }, @{ context = "Stage 2: Windows Tests (.NET 5.0-10.0, Framework 4.6.2-4.8.1)" }, @{ context = "Stage 3: macOS Tests (.NET 6.0-10.0)" }, - @{ context = "Security Scan (DevSkim)" }, - @{ context = "CodeQL Security Analysis / Security Scan (CodeQL) (csharp) (pull_request)" } + @{ context = "Security Scan (DevSkim)" } ) } }, @@ -281,7 +280,6 @@ try { Write-Host " - Stage 2: Windows Tests (.NET 5.0-10.0, Framework 4.6.2-4.8.1)" -ForegroundColor DarkGray Write-Host " - Stage 3: macOS Tests (.NET 6.0-10.0)" -ForegroundColor DarkGray Write-Host " - Security Scan (DevSkim)" -ForegroundColor DarkGray - Write-Host " - CodeQL Security Analysis / Security Scan (CodeQL) (csharp) (pull_request)" -ForegroundColor DarkGray Write-Host " ✅ Branches must be up to date before merging" -ForegroundColor Gray Write-Host " ✅ Conversation resolution required before merging" -ForegroundColor Gray Write-Host " ✅ Stale reviews dismissed when new commits are pushed" -ForegroundColor Gray From 2dc74cfddcfcf323e226b1d4253e8f0e3ada04e2 Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:44:29 -0400 Subject: [PATCH 3/4] Update Fix-BranchRuleset.ps1 to auto-run Setup-BranchRuleset.ps1 on success Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/Fix-BranchRuleset.ps1 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/Fix-BranchRuleset.ps1 b/scripts/Fix-BranchRuleset.ps1 index 2947de0..4883e9b 100644 --- a/scripts/Fix-BranchRuleset.ps1 +++ b/scripts/Fix-BranchRuleset.ps1 @@ -228,6 +228,15 @@ if ($errors -gt 0) { } else { Write-Host "All changes applied successfully." -ForegroundColor Green Write-Host "" - Write-Host "Next step: Run .\Setup-BranchRuleset.ps1 to create a fresh ruleset." -ForegroundColor Cyan - Write-Host "View rulesets at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan + + # Invoke Setup-BranchRuleset.ps1 to create a fresh ruleset + $setupScript = Join-Path $PSScriptRoot "Setup-BranchRuleset.ps1" + if (Test-Path $setupScript) { + Write-Host "Running Setup-BranchRuleset.ps1 to create a fresh ruleset..." -ForegroundColor Cyan + Write-Host "" + & $setupScript -Repository $Repository + } else { + Write-Host "Setup-BranchRuleset.ps1 not found. Run it manually to create a fresh ruleset." -ForegroundColor Yellow + Write-Host "View rulesets at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan + } } From 43499367e60308bd2957e609840058cfc2c8e9d6 Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:55:27 -0400 Subject: [PATCH 4/4] Add -Confirm (-y) flag to Fix-BranchRuleset.ps1 to skip prompt Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/Fix-BranchRuleset.ps1 | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/scripts/Fix-BranchRuleset.ps1 b/scripts/Fix-BranchRuleset.ps1 index 4883e9b..37c68cd 100644 --- a/scripts/Fix-BranchRuleset.ps1 +++ b/scripts/Fix-BranchRuleset.ps1 @@ -12,9 +12,16 @@ .PARAMETER Repository The repository in owner/repo format. If not provided, uses the current repository. +.PARAMETER Confirm + Skip the confirmation prompt and proceed automatically. Alias: -y + .EXAMPLE .\Fix-BranchRuleset.ps1 - Inspects and fixes rulesets for the current repository + Inspects and fixes rulesets for the current repository with interactive confirmation + +.EXAMPLE + .\Fix-BranchRuleset.ps1 -y + Inspects and fixes rulesets without prompting for confirmation .EXAMPLE .\Fix-BranchRuleset.ps1 -Repository "Chris-Wolfgang/my-repo" @@ -28,7 +35,11 @@ [CmdletBinding()] param( [Parameter()] - [string]$Repository = "Chris-Wolfgang/ETL-Abstractions" + [string]$Repository = "Chris-Wolfgang/ETL-Abstractions", + + [Parameter()] + [Alias("y")] + [switch]$Confirm ) # Check if gh CLI is installed @@ -158,10 +169,14 @@ foreach ($item in $plan) { Write-Host "" # Prompt for confirmation -$response = Read-Host "Proceed with these changes? (y/N)" -if ($response -ne 'y' -and $response -ne 'Y') { - Write-Host "Cancelled. No changes were made." -ForegroundColor Yellow - exit 0 +if ($Confirm) { + Write-Host "Auto-confirmed via -Confirm flag." -ForegroundColor Green +} else { + $response = Read-Host "Proceed with these changes? (y/N)" + if ($response -ne 'y' -and $response -ne 'Y') { + Write-Host "Cancelled. No changes were made." -ForegroundColor Yellow + exit 0 + } } Write-Host ""