From d03eb9e8ca15468eede57042fb2a274ce6e5f5e2 Mon Sep 17 00:00:00 2001 From: Aaron Salisbury Date: Fri, 18 Sep 2026 22:28:06 -0500 Subject: [PATCH 1/2] Restore SemVer regex terminator --- .github/workflows/release.yml | 123 +++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b219000..680401a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,128 @@ jobs: Where-Object { $_ } | Select-Object -First 1).Trim() - if ($version -notmatch '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?) { + if ($version -notmatch '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)? + throw "Project version '$version' is not a valid semantic version." + } + + "version=$version" >> $env:GITHUB_OUTPUT + "tag=v$version" >> $env:GITHUB_OUTPUT + + Write-Host "Preparing AppToolkit $version." + + - name: Verify release tag does not already exist + shell: bash + run: | + if git ls-remote --exit-code --tags origin "refs/tags/${{ steps.version.outputs.tag }}" > /dev/null 2>&1; then + echo "::error::Tag ${{ steps.version.outputs.tag }} already exists." + exit 1 + fi + + # Cake remains the authoritative release build so local and CI releases + # use the same restore, image-processing, build, and packaging pipeline. + - name: Run Cake release build + working-directory: build/Build + run: dotnet run -- --configuration=Release + + - name: Validate NuGet packages + shell: bash + run: | + package="${PACKAGE_DIRECTORY}/${PACKAGE_ID}.${{ steps.version.outputs.version }}.nupkg" + symbols="${PACKAGE_DIRECTORY}/${PACKAGE_ID}.${{ steps.version.outputs.version }}.snupkg" + + test -f "$package" || { echo "::error::Expected package not found: $package"; exit 1; } + test -f "$symbols" || { echo "::error::Expected symbol package not found: $symbols"; exit 1; } + + shopt -s nullglob + packages=("${PACKAGE_DIRECTORY}"/*.nupkg) + symbols_packages=("${PACKAGE_DIRECTORY}"/*.snupkg) + + if (( ${#packages[@]} != 1 )); then + echo "::error::Expected exactly one .nupkg but found ${#packages[@]}." + exit 1 + fi + + if (( ${#symbols_packages[@]} != 1 )); then + echo "::error::Expected exactly one .snupkg but found ${#symbols_packages[@]}." + exit 1 + fi + + echo "Validated:" + echo " $package" + echo " $symbols" + + # Preserve the exact packages produced by the build job. The publish job + # downloads these artifacts rather than rebuilding the release. + - name: Upload release packages + uses: actions/upload-artifact@v7 + with: + name: AppToolkit-${{ steps.version.outputs.version }} + path: | + ${{ env.PACKAGE_DIRECTORY }}/*.nupkg + ${{ env.PACKAGE_DIRECTORY }}/*.snupkg + if-no-files-found: error + retention-days: 14 + + publish: + name: Publish release + if: inputs.publish + needs: build + runs-on: ubuntu-latest + # This protected environment is the deliberate approval boundary before + # anything can be published externally. + environment: nuget-production + + permissions: + contents: write + id-token: write + + steps: + - name: Check out release commit + uses: actions/checkout@v7 + with: + ref: ${{ github.sha }} + + - name: Set up .NET + uses: actions/setup-dotnet@v6 + with: + dotnet-version: 10.0.x + + - name: Download release packages + uses: actions/download-artifact@v8 + with: + name: AppToolkit-${{ needs.build.outputs.version }} + path: release-packages + + # NuGet Trusted Publishing exchanges GitHub's OIDC identity for a + # short-lived API key; no long-lived NuGet API key is stored in GitHub. + - name: Authenticate to NuGet + id: nuget + uses: NuGet/login@v1 + with: + user: ${{ vars.NUGET_USER }} + + # dotnet nuget push automatically publishes a matching .snupkg found + # beside the .nupkg, so the symbol package must not be pushed separately. + - name: Publish package to NuGet + shell: bash + run: | + dotnet nuget push "release-packages/${PACKAGE_ID}.${{ needs.build.outputs.version }}.nupkg" \ + --api-key "${{ steps.nuget.outputs.NUGET_API_KEY }}" \ + --source https://api.nuget.org/v3/index.json + + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + RELEASE_NOTES: ${{ inputs.release_notes }} + shell: bash + run: | + gh release create "${{ needs.build.outputs.tag }}" \ + "release-packages/${PACKAGE_ID}.${{ needs.build.outputs.version }}.nupkg" \ + "release-packages/${PACKAGE_ID}.${{ needs.build.outputs.version }}.snupkg" \ + --target "${{ github.sha }}" \ + --title "${{ needs.build.outputs.tag }} Release" \ + --notes "$RELEASE_NOTES" +) { throw "Project version '$version' is not a valid semantic version." } From 9c6fc9c18af43c2beb268889176892c4d3c8bf83 Mon Sep 17 00:00:00 2001 From: Aaron Salisbury Date: Fri, 18 Sep 2026 22:29:24 -0500 Subject: [PATCH 2/2] Correct and clean release SemVer validation --- .github/workflows/release.yml | 123 +--------------------------------- 1 file changed, 1 insertion(+), 122 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 680401a..3eb029e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,128 +64,7 @@ jobs: Where-Object { $_ } | Select-Object -First 1).Trim() - if ($version -notmatch '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)? - throw "Project version '$version' is not a valid semantic version." - } - - "version=$version" >> $env:GITHUB_OUTPUT - "tag=v$version" >> $env:GITHUB_OUTPUT - - Write-Host "Preparing AppToolkit $version." - - - name: Verify release tag does not already exist - shell: bash - run: | - if git ls-remote --exit-code --tags origin "refs/tags/${{ steps.version.outputs.tag }}" > /dev/null 2>&1; then - echo "::error::Tag ${{ steps.version.outputs.tag }} already exists." - exit 1 - fi - - # Cake remains the authoritative release build so local and CI releases - # use the same restore, image-processing, build, and packaging pipeline. - - name: Run Cake release build - working-directory: build/Build - run: dotnet run -- --configuration=Release - - - name: Validate NuGet packages - shell: bash - run: | - package="${PACKAGE_DIRECTORY}/${PACKAGE_ID}.${{ steps.version.outputs.version }}.nupkg" - symbols="${PACKAGE_DIRECTORY}/${PACKAGE_ID}.${{ steps.version.outputs.version }}.snupkg" - - test -f "$package" || { echo "::error::Expected package not found: $package"; exit 1; } - test -f "$symbols" || { echo "::error::Expected symbol package not found: $symbols"; exit 1; } - - shopt -s nullglob - packages=("${PACKAGE_DIRECTORY}"/*.nupkg) - symbols_packages=("${PACKAGE_DIRECTORY}"/*.snupkg) - - if (( ${#packages[@]} != 1 )); then - echo "::error::Expected exactly one .nupkg but found ${#packages[@]}." - exit 1 - fi - - if (( ${#symbols_packages[@]} != 1 )); then - echo "::error::Expected exactly one .snupkg but found ${#symbols_packages[@]}." - exit 1 - fi - - echo "Validated:" - echo " $package" - echo " $symbols" - - # Preserve the exact packages produced by the build job. The publish job - # downloads these artifacts rather than rebuilding the release. - - name: Upload release packages - uses: actions/upload-artifact@v7 - with: - name: AppToolkit-${{ steps.version.outputs.version }} - path: | - ${{ env.PACKAGE_DIRECTORY }}/*.nupkg - ${{ env.PACKAGE_DIRECTORY }}/*.snupkg - if-no-files-found: error - retention-days: 14 - - publish: - name: Publish release - if: inputs.publish - needs: build - runs-on: ubuntu-latest - # This protected environment is the deliberate approval boundary before - # anything can be published externally. - environment: nuget-production - - permissions: - contents: write - id-token: write - - steps: - - name: Check out release commit - uses: actions/checkout@v7 - with: - ref: ${{ github.sha }} - - - name: Set up .NET - uses: actions/setup-dotnet@v6 - with: - dotnet-version: 10.0.x - - - name: Download release packages - uses: actions/download-artifact@v8 - with: - name: AppToolkit-${{ needs.build.outputs.version }} - path: release-packages - - # NuGet Trusted Publishing exchanges GitHub's OIDC identity for a - # short-lived API key; no long-lived NuGet API key is stored in GitHub. - - name: Authenticate to NuGet - id: nuget - uses: NuGet/login@v1 - with: - user: ${{ vars.NUGET_USER }} - - # dotnet nuget push automatically publishes a matching .snupkg found - # beside the .nupkg, so the symbol package must not be pushed separately. - - name: Publish package to NuGet - shell: bash - run: | - dotnet nuget push "release-packages/${PACKAGE_ID}.${{ needs.build.outputs.version }}.nupkg" \ - --api-key "${{ steps.nuget.outputs.NUGET_API_KEY }}" \ - --source https://api.nuget.org/v3/index.json - - - name: Create GitHub release - env: - GH_TOKEN: ${{ github.token }} - RELEASE_NOTES: ${{ inputs.release_notes }} - shell: bash - run: | - gh release create "${{ needs.build.outputs.tag }}" \ - "release-packages/${PACKAGE_ID}.${{ needs.build.outputs.version }}.nupkg" \ - "release-packages/${PACKAGE_ID}.${{ needs.build.outputs.version }}.snupkg" \ - --target "${{ github.sha }}" \ - --title "${{ needs.build.outputs.tag }} Release" \ - --notes "$RELEASE_NOTES" -) { + if ($version -notmatch '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$') { throw "Project version '$version' is not a valid semantic version." }