From 44c7e1401500b9870f6d05a9542f961256f58dc1 Mon Sep 17 00:00:00 2001 From: Aaron Salisbury Date: Fri, 18 Sep 2026 22:20:48 -0500 Subject: [PATCH] Fix PowerShell SemVer regex escaping --- .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 f433e0f..21ae57d 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." }