From 94f3644cba4bc5e0b200d6b9be8be90868cfeb96 Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:56:54 -0400 Subject: [PATCH] Validate release tag matches csproj version before pack/publish Adds an early step in the validate-release job that fails the release if the GitHub release tag does not match any src csproj's (or ). Mirrors repo-template#320. Catches the silent failure where dotnet pack produces an old-version nupkg, dotnet nuget push gets a 409 Conflict, and --skip-duplicate suppresses the error so the workflow reports success while NuGet shows nothing new. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yaml | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 78ea27c..2593794 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,6 +23,60 @@ jobs: with: persist-credentials: false + - name: Validate release tag matches csproj version + shell: pwsh + env: + RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} + run: | + # Strip leading 'v' or 'v.' from the tag (v0.3.0 -> 0.3.0, v.0.1.0 -> 0.1.0) + $tagName = $env:RELEASE_TAG_NAME + $tagVersion = $tagName -replace '^v\.?', '' + Write-Host "Release tag: $tagName" -ForegroundColor Cyan + Write-Host "Expected version: $tagVersion" -ForegroundColor Cyan + Write-Host "" + + $srcCsprojs = @(Get-ChildItem -Path './src' -Recurse -Filter '*.csproj' -ErrorAction SilentlyContinue) + if ($srcCsprojs.Count -eq 0) { + Write-Warning "No src csprojs found - skipping version validation" + exit 0 + } + + # Collect and values from every src csproj + $found = @() + foreach ($proj in $srcCsprojs) { + try { + [xml]$xml = Get-Content $proj.FullName -Raw + $nodes = $xml.SelectNodes('//Version | //PackageVersion') + foreach ($node in $nodes) { + $v = $node.InnerText.Trim() + if ($v) { + $found += [pscustomobject]@{ Project = $proj.Name; Version = $v } + } + } + } catch { + Write-Warning "Failed to parse $($proj.Name): $($_.Exception.Message)" + } + } + + Write-Host "Versions found in src csprojs:" -ForegroundColor Cyan + foreach ($f in $found) { + Write-Host " $($f.Project): $($f.Version)" -ForegroundColor DarkGray + } + Write-Host "" + + if ($found.Count -eq 0) { + Write-Error "No or found in any src csproj" + exit 1 + } + + if ($found.Version -contains $tagVersion) { + Write-Host "Release tag matches at least one src csproj version" -ForegroundColor Green + } else { + $allVersions = ($found.Version | Sort-Object -Unique) -join ', ' + Write-Error "Release tag '$tagName' (version '$tagVersion') does not match any src csproj version. Found: $allVersions. Bump the csproj or correct the release tag before re-running." + exit 1 + } + - name: Setup .NET uses: actions/setup-dotnet@v4 with: