Skip to content

Release Workflow

Release Workflow #11

Workflow file for this run

name: Release Workflow
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., 1.0.0 or v1.0.0)'
required: true
type: string
publish-to-nuget:
description: 'Publish to NuGet.org'
required: false
type: boolean
default: true
permissions:
contents: write
packages: write
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
jobs:
validate-release:
name: "Validate Release"
runs-on: ubuntu-latest
outputs:
should-publish: ${{ steps.validate.outputs.should-publish }}
tag-name: ${{ steps.validate.outputs.tag-name }}
version: ${{ steps.validate.outputs.version }}
is-manual: ${{ steps.validate.outputs.is-manual }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate Release
id: validate
run: |
if [ "${{ github.event_name }}" == "push" ] && [[ "${{ github.ref }}" == refs/tags/* ]]; then
# Triggered by tag push
TAG_NAME="${{ github.ref_name }}"
VERSION="${TAG_NAME#v}" # Remove 'v' prefix if present
SHOULD_PUBLISH="true"
IS_MANUAL="false"
echo "🏷️ Triggered by tag: $TAG_NAME"
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
# Triggered manually
INPUT_VERSION="${{ inputs.version }}"
# Ensure version has 'v' prefix for tag
if [[ "$INPUT_VERSION" == v* ]]; then
TAG_NAME="$INPUT_VERSION"
VERSION="${INPUT_VERSION#v}"
else
TAG_NAME="v$INPUT_VERSION"
VERSION="$INPUT_VERSION"
fi
SHOULD_PUBLISH="${{ inputs.publish-to-nuget }}"
IS_MANUAL="true"
echo "🖱️ Triggered manually with version: $VERSION"
else
echo "❌ Invalid trigger event: ${{ github.event_name }}"
exit 1
fi
# Validate version format (basic semver validation)
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ Invalid version format: $VERSION"
echo "Expected format: X.Y.Z[-prerelease][+build]"
exit 1
fi
echo "🏷️ Release tag: $TAG_NAME"
echo "🔢 Version: $VERSION"
echo "🚀 Should publish to NuGet: $SHOULD_PUBLISH"
echo "🖱️ Is manual trigger: $IS_MANUAL"
echo "tag-name=$TAG_NAME" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "should-publish=$SHOULD_PUBLISH" >> $GITHUB_OUTPUT
echo "is-manual=$IS_MANUAL" >> $GITHUB_OUTPUT
check-tag-exists:
needs: validate-release
name: "Check Tag Exists"
runs-on: ubuntu-latest
if: needs.validate-release.outputs.is-manual == 'true'
outputs:
tag-exists: ${{ steps.check.outputs.exists }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if tag exists
id: check
run: |
TAG_NAME="${{ needs.validate-release.outputs.tag-name }}"
if git rev-parse --verify "refs/tags/$TAG_NAME" >/dev/null 2>&1; then
echo "⚠️ Tag $TAG_NAME already exists"
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "✅ Tag $TAG_NAME does not exist"
echo "exists=false" >> $GITHUB_OUTPUT
fi
calculate-version:
needs: [validate-release, check-tag-exists]
name: "Calculate Version"
if: always() && needs.validate-release.result == 'success'
runs-on: ubuntu-latest
outputs:
final-version: ${{ steps.version-override.outputs.FINAL_VERSION }}
final-nuget-version: ${{ steps.version-override.outputs.FINAL_NUGET_VERSION }}
final-assembly-version: ${{ steps.version-override.outputs.FINAL_ASSEMBLY_VERSION }}
final-informational-version: ${{ steps.version-override.outputs.FINAL_INFORMATIONAL_VERSION }}
use-tag-version: ${{ steps.version-override.outputs.USE_TAG_VERSION }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v4.0.1
with:
versionSpec: '6.x'
- name: Determine Version
id: gitversion
uses: gittools/actions/gitversion/execute@v4.0.1
with:
overrideConfig: |
mode=ContinuousDeployment
tag-prefix=[vV]?
- name: Override Version for Pre-release Tags
id: version-override
run: |
# Get the validated version from the validate-release job
VALIDATED_VERSION="${{ needs.validate-release.outputs.version }}"
TAG_NAME="${{ needs.validate-release.outputs.tag-name }}"
# Check if this is a manual trigger or if we have a valid semver tag
if [[ "$VALIDATED_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
# This is a valid semver version, use it directly
echo "🏷️ Using validated version: $VALIDATED_VERSION"
echo "FINAL_VERSION=$VALIDATED_VERSION" >> $GITHUB_OUTPUT
echo "FINAL_NUGET_VERSION=$VALIDATED_VERSION" >> $GITHUB_OUTPUT
# For assembly version, remove pre-release and build metadata
ASSEMBLY_VERSION="${VALIDATED_VERSION%%[-+]*}"
echo "FINAL_ASSEMBLY_VERSION=$ASSEMBLY_VERSION" >> $GITHUB_OUTPUT
echo "FINAL_INFORMATIONAL_VERSION=$VALIDATED_VERSION" >> $GITHUB_OUTPUT
echo "USE_TAG_VERSION=true" >> $GITHUB_OUTPUT
else
# Use GitVersion output
echo "🔧 Using GitVersion output"
echo "FINAL_VERSION=${{ steps.gitversion.outputs.SemVer }}" >> $GITHUB_OUTPUT
echo "FINAL_NUGET_VERSION=${{ steps.gitversion.outputs.NuGetVersion }}" >> $GITHUB_OUTPUT
echo "FINAL_ASSEMBLY_VERSION=${{ steps.gitversion.outputs.AssemblySemVer }}" >> $GITHUB_OUTPUT
echo "FINAL_INFORMATIONAL_VERSION=${{ steps.gitversion.outputs.InformationalVersion }}" >> $GITHUB_OUTPUT
echo "USE_TAG_VERSION=false" >> $GITHUB_OUTPUT
fi
- name: Debug Version Calculation
run: |
echo "🔍 Version Calculation Debug Information:"
echo "Current Branch: $(git branch --show-current)"
echo "Current Commit: $(git rev-parse HEAD)"
echo "Tag: ${{ needs.validate-release.outputs.tag-name }}"
echo "Expected Version: ${{ needs.validate-release.outputs.version }}"
echo "Use Tag Version: ${{ steps.version-override.outputs.USE_TAG_VERSION }}"
echo ""
echo "GitVersion outputs:"
echo " SemVer: ${{ steps.gitversion.outputs.SemVer }}"
echo " NuGetVersion: ${{ steps.gitversion.outputs.NuGetVersion }}"
echo " AssemblySemVer: ${{ steps.gitversion.outputs.AssemblySemVer }}"
echo " InformationalVersion: ${{ steps.gitversion.outputs.InformationalVersion }}"
echo " PreReleaseTag: ${{ steps.gitversion.outputs.PreReleaseTag }}"
echo " PreReleaseNumber: ${{ steps.gitversion.outputs.PreReleaseNumber }}"
echo ""
echo "Final versions to use:"
echo " Version: ${{ steps.version-override.outputs.FINAL_VERSION }}"
echo " NuGet Version: ${{ steps.version-override.outputs.FINAL_NUGET_VERSION }}"
echo " Assembly Version: ${{ steps.version-override.outputs.FINAL_ASSEMBLY_VERSION }}"
echo " Informational Version: ${{ steps.version-override.outputs.FINAL_INFORMATIONAL_VERSION }}"
build:
needs: [validate-release, calculate-version]
name: "Build and Test (Release)"
if: always() && needs.validate-release.result == 'success' && needs.calculate-version.result == 'success'
strategy:
fail-fast: true
matrix:
include:
- dotnet: '8.0.x'
dotnet-framework: 'net8.0'
os: ubuntu-latest
- dotnet: '8.0.x'
dotnet-framework: 'net8.0'
os: windows-latest
- dotnet: '9.0.x'
dotnet-framework: 'net9.0'
os: ubuntu-latest
- dotnet: '9.0.x'
dotnet-framework: 'net9.0'
os: windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET ${{ matrix.dotnet }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet }}
- name: Debug Environment Variables
run: |
echo "🔍 Build Environment Debug:"
echo "GitVersion_SemVer: ${{ needs.calculate-version.outputs.final-version }}"
echo "GitVersion_NuGetVersion: ${{ needs.calculate-version.outputs.final-nuget-version }}"
echo "GitVersion_AssemblySemVer: ${{ needs.calculate-version.outputs.final-assembly-version }}"
echo "GitVersion_InformationalVersion: ${{ needs.calculate-version.outputs.final-informational-version }}"
- name: Restore dependencies
run: dotnet restore -p:TargetFramework=${{ matrix.dotnet-framework }}
- name: Build
run: dotnet build --no-restore -c Release -f ${{ matrix.dotnet-framework }}
env:
GitVersion_SemVer: ${{ needs.calculate-version.outputs.final-version }}
GitVersion_AssemblySemVer: ${{ needs.calculate-version.outputs.final-assembly-version }}
GitVersion_AssemblySemFileVer: ${{ needs.calculate-version.outputs.final-assembly-version }}
GitVersion_InformationalVersion: ${{ needs.calculate-version.outputs.final-informational-version }}
GitVersion_NuGetVersion: ${{ needs.calculate-version.outputs.final-nuget-version }}
- name: Test
run: dotnet test --no-build --verbosity normal -c Release -f ${{ matrix.dotnet-framework }} --collect:"XPlat Code Coverage" --results-directory ./coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: |
./coverage/**/coverage.cobertura.xml
flags: ${{ matrix.dotnet-framework }},${{ matrix.os }}
name: ${{ matrix.os }}-${{ matrix.dotnet-framework }}
fail_ci_if_error: false
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
create-tag:
needs: [validate-release, check-tag-exists, build]
name: "Create Tag"
runs-on: ubuntu-latest
if: needs.validate-release.outputs.is-manual == 'true' && needs.check-tag-exists.outputs.tag-exists == 'false'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push tag
run: |
TAG_NAME="${{ needs.validate-release.outputs.tag-name }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
echo "📝 Creating tag: $TAG_NAME"
git tag -a "$TAG_NAME" -m "Release $TAG_NAME"
echo "📤 Pushing tag: $TAG_NAME"
git push origin "$TAG_NAME"
echo "✅ Tag $TAG_NAME created and pushed successfully"
publish:
needs: [validate-release, calculate-version, build]
name: "Publish Packages (Release)"
if: always() && needs.build.result == 'success'
uses: ./.github/workflows/publish.yml
with:
publish-to-nuget: ${{ needs.validate-release.outputs.should-publish == 'true' }}
build-configuration: 'Release'
version: ${{ needs.calculate-version.outputs.final-version }}
nuget-version: ${{ needs.calculate-version.outputs.final-nuget-version }}
assembly-version: ${{ needs.calculate-version.outputs.final-assembly-version }}
informational-version: ${{ needs.calculate-version.outputs.final-informational-version }}
secrets: inherit
summary:
needs: [validate-release, calculate-version, publish, create-tag]
runs-on: ubuntu-latest
if: always()
steps:
- name: Release Summary
run: |
echo "## 🚀 Release Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ needs.validate-release.outputs.tag-name }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${{ needs.calculate-version.outputs.final-version }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| NuGet Version | \`${{ needs.calculate-version.outputs.final-nuget-version }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Assembly Version | \`${{ needs.calculate-version.outputs.final-assembly-version }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Used Tag Version | ${{ needs.calculate-version.outputs.use-tag-version == 'true' && '✅ Yes' || '❌ No (GitVersion)' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Trigger | ${{ needs.validate-release.outputs.is-manual == 'true' && '🖱️ Manual' || '🏷️ Tag Push' }} |" >> $GITHUB_STEP_SUMMARY
echo "| NuGet.org | ${{ needs.validate-release.outputs.should-publish == 'true' && '✅ Published' || '❌ Skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| GitHub Packages | ✅ Published |" >> $GITHUB_STEP_SUMMARY
echo "| Tag Created | ${{ needs.create-tag.result == 'success' && '✅ Yes' || (needs.create-tag.result == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📋 **Version Calculation Notes**:" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.calculate-version.outputs.use-tag-version }}" == "true" ]; then
echo "- ✅ **Tag-based versioning**: Used version from tag \`${{ needs.validate-release.outputs.tag-name }}\`" >> $GITHUB_STEP_SUMMARY
echo "- 🎯 **NuGet compatible**: Pre-release version format preserved" >> $GITHUB_STEP_SUMMARY
else
echo "- ⚙️ **GitVersion calculation**: Version calculated by GitVersion" >> $GITHUB_STEP_SUMMARY
echo "- ⚠️ **Note**: If you expected tag-based versioning, ensure tag format is valid semver" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "📋 **Test Execution Notes**:" >> $GITHUB_STEP_SUMMARY
echo "- **Ubuntu**: All tests executed" >> $GITHUB_STEP_SUMMARY
echo "- **Windows**: All tests executed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.validate-release.outputs.is-manual }}" == "true" ]; then
echo "🖱️ **Manual Release**: This release was triggered manually with version \`${{ needs.validate-release.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
else
echo "🏷️ **Tag Release**: This release was triggered by pushing tag \`${{ needs.validate-release.outputs.tag-name }}\`" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "📦 **Published Packages**: Check the [packages page](https://github.com/${{ github.repository }}/pkgs) for the published packages." >> $GITHUB_STEP_SUMMARY