feat: add centralized reusable GitHub workflows #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Reusable workflow for publishing .NET NuGet packages | ||
|
Check failure on line 1 in .github/workflows/dotnet-nuget-publish.yml
|
||
| # This workflow handles package creation, versioning, and publishing to NuGet.org | ||
| name: Publish NuGet Package | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| # Project files to pack (semicolon-separated for multiple) | ||
| project-paths: | ||
| description: 'Project file paths to pack (semicolon-separated)' | ||
| required: true | ||
| type: string | ||
| # .NET SDK version | ||
| dotnet-version: | ||
| description: '.NET SDK version' | ||
| required: false | ||
| type: string | ||
| default: '8.0.x' | ||
| # Build configuration | ||
| build-configuration: | ||
| description: 'Build configuration' | ||
| required: false | ||
| type: string | ||
| default: 'Release' | ||
| # Package version (if not specified, uses version from project) | ||
| package-version: | ||
| description: 'Package version override' | ||
| required: false | ||
| type: string | ||
| default: '' | ||
| # Whether to include symbols | ||
| include-symbols: | ||
| description: 'Include symbol packages' | ||
| required: false | ||
| type: boolean | ||
| default: true | ||
| # Whether to include source | ||
| include-source: | ||
| description: 'Include source in packages' | ||
| required: false | ||
| type: boolean | ||
| default: true | ||
| # NuGet source URL | ||
| nuget-source: | ||
| description: 'NuGet source URL' | ||
| required: false | ||
| type: string | ||
| default: 'https://api.nuget.org/v3/index.json' | ||
| # Whether to push to NuGet (false for dry run) | ||
| push-to-nuget: | ||
| description: 'Push packages to NuGet' | ||
| required: false | ||
| type: boolean | ||
| default: true | ||
| # Whether to validate package before push | ||
| validate-package: | ||
| description: 'Validate package before publishing' | ||
| required: false | ||
| type: boolean | ||
| default: true | ||
| secrets: | ||
| # NuGet API key for publishing | ||
| nuget-api-key: | ||
| description: 'NuGet API key' | ||
| required: false | ||
| jobs: | ||
| publish-nuget: | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| package-version: ${{ steps.version.outputs.version }} | ||
| packages-created: ${{ steps.pack.outputs.packages }} | ||
| steps: | ||
| # Checkout repository code | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 # Full history for version calculation | ||
| # Setup .NET SDK | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: ${{ inputs.dotnet-version }} | ||
| # Cache NuGet packages | ||
| - name: Cache NuGet packages | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ~/.nuget/packages | ||
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-nuget- | ||
| # Determine package version | ||
| - name: Determine package version | ||
| id: version | ||
| run: | | ||
| # Use provided version or extract from tag/project | ||
| if [ -n "${{ inputs.package-version }}" ]; then | ||
| VERSION="${{ inputs.package-version }}" | ||
| elif [ -n "${{ github.ref_name }}" ] && [[ "${{ github.ref_name }}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+ ]]; then | ||
| # Extract version from tag (remove 'v' prefix if present) | ||
| VERSION="${{ github.ref_name }}" | ||
| VERSION="${VERSION#v}" | ||
| else | ||
| # Try to extract version from first project file | ||
| FIRST_PROJECT=$(echo "${{ inputs.project-paths }}" | cut -d';' -f1) | ||
| if [ -f "$FIRST_PROJECT" ]; then | ||
| VERSION=$(grep -oPm1 "(?<=<Version>)[^<]+" "$FIRST_PROJECT" || echo "1.0.0") | ||
| else | ||
| VERSION="1.0.0" | ||
| fi | ||
| fi | ||
| echo "version=$VERSION" >> $GITHUB_OUTPUT | ||
| echo "Package version: $VERSION" | ||
| # Restore dependencies | ||
| - name: Restore dependencies | ||
| run: | | ||
| # Split project paths and restore each | ||
| IFS=';' read -ra PROJECTS <<< "${{ inputs.project-paths }}" | ||
| for project in "${PROJECTS[@]}"; do | ||
| if [ -f "$project" ]; then | ||
| echo "Restoring $project" | ||
| dotnet restore "$project" | ||
| else | ||
| echo "Warning: Project file not found: $project" | ||
| fi | ||
| done | ||
| # Build projects | ||
| - name: Build projects | ||
| run: | | ||
| # Split project paths and build each | ||
| IFS=';' read -ra PROJECTS <<< "${{ inputs.project-paths }}" | ||
| for project in "${PROJECTS[@]}"; do | ||
| if [ -f "$project" ]; then | ||
| echo "Building $project" | ||
| dotnet build "$project" \ | ||
| --configuration ${{ inputs.build-configuration }} \ | ||
| --no-restore \ | ||
| -p:Version=${{ steps.version.outputs.version }} | ||
| fi | ||
| done | ||
| # Pack NuGet packages | ||
| - name: Pack NuGet packages | ||
| id: pack | ||
| run: | | ||
| # Create output directory | ||
| mkdir -p ./nupkgs | ||
| # Track created packages | ||
| PACKAGES="" | ||
| # Split project paths and pack each | ||
| IFS=';' read -ra PROJECTS <<< "${{ inputs.project-paths }}" | ||
| for project in "${PROJECTS[@]}"; do | ||
| if [ -f "$project" ]; then | ||
| echo "Packing $project" | ||
| # Base pack command | ||
| PACK_CMD="dotnet pack \"$project\" \ | ||
| --configuration ${{ inputs.build-configuration }} \ | ||
| --no-build \ | ||
| --output ./nupkgs \ | ||
| -p:PackageVersion=${{ steps.version.outputs.version }}" | ||
| # Add symbol package options if requested | ||
| if [ "${{ inputs.include-symbols }}" = "true" ]; then | ||
| PACK_CMD="$PACK_CMD -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg" | ||
| fi | ||
| # Add source inclusion if requested | ||
| if [ "${{ inputs.include-source }}" = "true" ]; then | ||
| PACK_CMD="$PACK_CMD -p:IncludeSource=true" | ||
| fi | ||
| # Execute pack command | ||
| eval $PACK_CMD | ||
| # Track package name | ||
| PROJECT_NAME=$(basename "$project" .csproj) | ||
| PACKAGES="$PACKAGES $PROJECT_NAME" | ||
| fi | ||
| done | ||
| # List created packages | ||
| echo "Created packages:" | ||
| ls -la ./nupkgs/ | ||
| echo "packages=$PACKAGES" >> $GITHUB_OUTPUT | ||
| # Validate packages if requested | ||
| - name: Validate NuGet packages | ||
| if: inputs.validate-package | ||
| run: | | ||
| # Install NuGet Package Explorer CLI or use dotnet validate | ||
| for package in ./nupkgs/*.nupkg; do | ||
| if [[ ! "$package" =~ \.symbols\.nupkg$ ]] && [[ ! "$package" =~ \.snupkg$ ]]; then | ||
| echo "Validating $package" | ||
| # Basic validation - check package can be read | ||
| dotnet nuget locals all --clear | ||
| dotnet nuget push "$package" --source local --skip-duplicate || true | ||
| # Check package metadata | ||
| unzip -l "$package" | head -20 | ||
| fi | ||
| done | ||
| # Upload packages as artifacts | ||
| - name: Upload NuGet packages | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: nuget-packages | ||
| path: ./nupkgs/ | ||
| retention-days: 7 | ||
| # Push to NuGet if requested and API key is provided | ||
| - name: Push to NuGet | ||
| if: inputs.push-to-nuget && secrets.nuget-api-key != '' | ||
| run: | | ||
| # Push each package to NuGet | ||
| for package in ./nupkgs/*.nupkg; do | ||
| # Skip symbol packages (they're pushed automatically with main package) | ||
| if [[ ! "$package" =~ \.symbols\.nupkg$ ]] && [[ ! "$package" =~ \.snupkg$ ]]; then | ||
| echo "Pushing $package to NuGet" | ||
| dotnet nuget push "$package" \ | ||
| --api-key "${{ secrets.nuget-api-key }}" \ | ||
| --source "${{ inputs.nuget-source }}" \ | ||
| --skip-duplicate | ||
| # Give NuGet time to process between pushes | ||
| sleep 2 | ||
| fi | ||
| done | ||
| # Summary | ||
| - name: Package summary | ||
| run: | | ||
| echo "## 📦 NuGet Package Publishing Summary" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY | ||
| echo "**Configuration:** ${{ inputs.build-configuration }}" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "### Packages Created:" >> $GITHUB_STEP_SUMMARY | ||
| for package in ./nupkgs/*.nupkg; do | ||
| if [[ ! "$package" =~ \.symbols\.nupkg$ ]] && [[ ! "$package" =~ \.snupkg$ ]]; then | ||
| echo "- $(basename $package)" >> $GITHUB_STEP_SUMMARY | ||
| fi | ||
| done | ||
| if [ "${{ inputs.push-to-nuget }}" = "true" ] && [ -n "${{ secrets.nuget-api-key }}" ]; then | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "✅ Packages published to NuGet.org" >> $GITHUB_STEP_SUMMARY | ||
| else | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "⚠️ Packages created but not published (dry run or missing API key)" >> $GITHUB_STEP_SUMMARY | ||
| fi | ||