Skip to content

Release Workflow

Release Workflow #4

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
build:
needs: [validate-release, check-tag-exists]
name: "Build and Test (Release)"
# Always build and test when validation succeeded, regardless of tag existence
if: always() && needs.validate-release.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: 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: |
next-version=${{ needs.validate-release.outputs.version }}
- name: Setup .NET ${{ matrix.dotnet }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet }}
- 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: ${{ steps.gitversion.outputs.SemVer }}
GitVersion_AssemblySemVer: ${{ steps.gitversion.outputs.AssemblySemVer }}
GitVersion_AssemblySemFileVer: ${{ steps.gitversion.outputs.AssemblySemFileVer }}
GitVersion_InformationalVersion: ${{ steps.gitversion.outputs.InformationalVersion }}
GitVersion_NuGetVersion: ${{ steps.gitversion.outputs.NuGetVersion }}
- name: Test
run: dotnet test --no-build --verbosity normal -c Release -f ${{ matrix.dotnet-framework }} /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[*.XUnit]*"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: |
**/coverage.opencover.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, 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'
summary:
needs: [validate-release, 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.validate-release.outputs.version }}\` |" >> $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 "📋 **Test Execution Notes**:" >> $GITHUB_STEP_SUMMARY
echo "- **Ubuntu**: All tests 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