Skip to content

Publish NuGet Packages #2

Publish NuGet Packages

Publish NuGet Packages #2

Workflow file for this run

# GitHub Actions workflow for publishing NuGet packages using Trusted Publishing.
#
# This workflow automates the publishing process:
# 1. Triggers only after CI workflow passes on main branch
# 2. Uses the reusable build workflow to compile in Release configuration
# 3. Authenticates via OIDC (Trusted Publishing) - no long-lived API keys needed
# 4. Pushes packages to NuGet.org (skipping already-published versions)
# 5. Creates Git tags for each published package (v1.0.0 format)
#
# Triggers:
# - Automatically after CI workflow succeeds on main branch
# - When a GitHub Release is published
# - Manual workflow dispatch (for testing or re-publishing)
#
# Prerequisites:
# 1. Configure Trusted Publishing on NuGet.org:
# - Go to https://www.nuget.org/account/trustedpublishing
# - Add a new trusted publisher policy
# - Repository owner: <your-github-username-or-org>
# - Repository name: <your-repo-name>
# - Workflow file: .github/workflows/publish.yml
# - Select packages this policy applies to
#
# 2. Configure repository secret:
# - NUGET_USER: Your NuGet.org username
name: Publish NuGet Packages
on:
# Trigger after CI workflow completes on main branch
workflow_run:
workflows: ["CI"]
types: [completed]
branches: [main]
release:
types: [published]
# Allow manual trigger for testing or re-publishing
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (build and pack only, no push)'
required: false
default: false
type: boolean
# Restrict default permissions for security
permissions:
contents: write # Required for creating Git tags
id-token: write # Required for OIDC Trusted Publishing
# Allow only one concurrent publish to prevent race conditions
concurrency:
group: "nuget-publish"
cancel-in-progress: false
jobs:
# Build the solution in Release configuration
build:
# Only run if CI succeeded (for workflow_run trigger) or for other triggers
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
uses: ./.github/workflows/build.yml
with:
configuration: Release
upload_artifacts: true
# Pack and publish
publish:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write # Required for creating Git tags
id-token: write # Required for OIDC Trusted Publishing
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-output-Release
- name: Restore dependencies
run: dotnet restore Smtp2Go.NET.slnx
- name: Create NuGet packages
# Pack all projects that produce NuGet packages
# The version is determined by the <Version> property in each .csproj file
run: dotnet pack Smtp2Go.NET.slnx --configuration Release --no-build --output ./artifacts
- name: List generated packages
# Display the packages that were created for verification
run: |
echo "Generated packages:"
ls -la ./artifacts/*.nupkg
- name: NuGet Trusted Publishing Login
# Exchange GitHub OIDC token for a short-lived NuGet API key
# This eliminates the need for long-lived API key secrets
# Reference: https://github.com/NuGet/login
if: ${{ github.event.inputs.dry_run != 'true' }}
id: nuget-login
uses: nuget/login@v1
with:
user: ${{ secrets.NUGET_USER }}
- name: Push packages to NuGet.org
# Push all packages to NuGet.org
# --skip-duplicate ensures idempotency - already published versions are skipped
# The API key is obtained from the login step's output (valid for ~1 hour)
if: ${{ github.event.inputs.dry_run != 'true' }}
run: |
for package in ./artifacts/*.nupkg; do
echo "Pushing: $package"
dotnet nuget push "$package" \
--api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
done
- name: Create Git tags for published packages
# Create and push Git tags for each successfully published package
# Tag format: v{version} (e.g., v1.0.0)
if: ${{ github.event.inputs.dry_run != 'true' }}
run: |
# Configure Git for tagging
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Process each package and create corresponding tags
for package in ./artifacts/*.nupkg; do
# Extract filename without path (e.g., "Smtp2Go.NET.1.0.0.nupkg")
filename=$(basename "$package")
# Remove .nupkg extension
name_version="${filename%.nupkg}"
# Extract version from the package name
# Pattern: PackageName.Major.Minor.Patch.nupkg
# Use regex to extract version (last 3 dot-separated numbers)
if [[ "$name_version" =~ ([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?)$ ]]; then
version="${BASH_REMATCH[1]}"
tag="v${version}"
# Check if tag already exists (locally or remotely)
if git rev-parse "refs/tags/$tag" >/dev/null 2>&1; then
echo "Tag $tag already exists locally, skipping"
elif git ls-remote --tags origin "refs/tags/$tag" | grep -q "$tag"; then
echo "Tag $tag already exists on remote, skipping"
else
echo "Creating tag: $tag"
git tag -a "$tag" -m "Release $tag"
git push origin "$tag"
echo "Successfully created and pushed tag: $tag"
fi
else
echo "Could not extract version from: $filename"
fi
done
- name: Upload packages as artifacts
# Upload packages as workflow artifacts for inspection or manual deployment
uses: actions/upload-artifact@v4
with:
name: nuget-packages
path: ./artifacts/*.nupkg
retention-days: 30