Understanding GitHub Actions for NuGet Publishing: A Step-by-Step Workflow Breakdown
GitHub Actions enable automated workflows for building, testing, and deploying software. This post walks through a sample YAML workflow for publishing NuGet packages, explaining each part to help you implement similar CI/CD pipelines.
Workflow Triggers
The workflow named “publish” activates on:
- Manual dispatch (
workflow_dispatch
). - Pushes to the
master
branch. - Pull requests to any branch (
*
). - Releases when published.
This ensures builds on code changes, PRs, and releases.
Environment and Defaults
- Environment variables: Skip .NET first-time experience, suppress logos, set directories for NuGet and coverage.
- Defaults: Use PowerShell shell for cross-platform consistency.
Jobs Overview
The workflow has four jobs: create_nuget
, validate_nuget
, run_test
, and deploy
. They run on Ubuntu-latest for efficiency.
Job: create_nuget
- Checks out code with full history.
- Sets up .NET 8.0.x and 9.0.x.
- Installs MinVer for versioning.
- Builds and packs two projects: UniversalReportCore and UniversalReportCore.Ui.
- Uploads NuGet artifacts.
Job: validate_nuget
- Depends on
create_nuget
. - Downloads artifacts.
- Installs NuGet validator.
- Validates each package for compliance.
Job: run_test
- Checks out code.
- Sets up .NET.
- Installs Coverlet for coverage.
- Runs tests with coverage collection.
- Generates reports and uploads artifacts.
Job: deploy
- Runs only on release events.
- Depends on validation and tests.
- Downloads artifacts.
- Publishes packages to NuGet.org using API key, skipping duplicates.
Why This Workflow Matters
- Automation: Streamlines building, testing, and deploying, reducing manual errors.
- Validation: Ensures packages meet standards before release.
- Testing: Integrates coverage to maintain code quality.
- Conditional Deployment: Publishes only on releases for controlled rollout.
- Efficiency: Parallel jobs speed up processes.
- Security: Uses secrets for API keys.
Common Pitfalls and Fixes
- Missing Dependencies: Ensure .NET versions match project requirements. Fix: Specify exact versions in setup.
- Artifact Issues: Files not found. Fix: Verify paths in upload/download steps.
- Secret Mismanagement: Exposed keys. Fix: Use GitHub secrets.
- Version Conflicts: Inconsistent builds. Fix: Use tools like MinVer.
- Test Failures: Ignored coverage. Fix: Integrate reporting tools.
Practical Tips for Implementation
- Modularize Jobs: Break into independent tasks for parallelism.
- Use Artifacts: Pass data between jobs securely.
- Add Notifications: Include Slack/email on failures.
- Monitor Runs: Review logs for optimizations.
- Learn Actions: Explore marketplace for reusable steps.
Adopting such workflows enhances DevOps practices, ensuring reliable software delivery.
Code Listing
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: publish
on:
workflow_dispatch:
push:
branches:
- 'master'
pull_request:
branches:
- '*'
release:
types:
- published
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
DOTNET_NOLOGO: true
NuGetDirectory: $/nuget
CoverageReport: $/coverage
defaults:
run:
shell: pwsh
jobs:
create_nuget:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
9.0.x
8.0.x
- name: Install MinVer
run: dotnet tool install --global minver-cli
- name: Run MinVer to determine package version
run: minver
# Build & Pack UniversalReportCore
- name: Build UniversalReportCore
run: dotnet build UniversalReportCore --configuration Release
- name: Pack UniversalReportCore
run: dotnet pack UniversalReportCore --configuration Release --output $
# Build & Pack UniversalReportCore.Ui
- name: Build UniversalReportCore.Ui
run: dotnet build UniversalReportCore.Ui --configuration Release
- name: Pack UniversalReportCore.Ui
run: dotnet pack UniversalReportCore.Ui --configuration Release --output $
# Debug: List NuGet Packages
- name: Debug - List NuGet Files
run: ls -la $
- uses: actions/upload-artifact@v4
with:
name: nuget
if-no-files-found: error
retention-days: 7
path: $/*.nupkg
validate_nuget:
runs-on: ubuntu-latest
needs: [ create_nuget ]
steps:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
9.0.x
8.0.x
- uses: actions/download-artifact@v4
with:
name: nuget
path: $
- name: Install NuGet validator
run: dotnet tool update Meziantou.Framework.NuGetPackageValidation.Tool --global
- name: Validate package
run: |
Get-ChildItem "$/*.nupkg" | ForEach-Object { meziantou.validate-nuget-package $_.FullName }
run_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
9.0.x
8.0.x
- name: Install Coverlet for Code Coverage
run: dotnet tool install --global coverlet.console
- name: Run tests with code coverage
run: |
dotnet test UniversalReportCore.Tests --configuration Release --collect:"XPlat Code Coverage" --results-directory $
- name: Convert Coverage to Cobertura Format
run: |
dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator -reports:$/**/coverage.cobertura.xml -targetdir:$/html -reporttypes:Cobertura
- name: Upload Coverage Report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: $/html
deploy:
if: github.event_name == 'release'
runs-on: ubuntu-latest
needs: [ validate_nuget, run_test ]
steps:
- uses: actions/download-artifact@v4
with:
name: nuget
path: $
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
9.0.x
8.0.x
# Find and Push
- name: Publish NuGet package
run: |
foreach($file in (Get-ChildItem "$" -Recurse -Include *.nupkg)) {
dotnet nuget push $file --api-key "$" --source https://api.nuget.org/v3/index.json --skip-duplicate
}
shell: pwsh