Skip to content

Automated NuGet Packaging and Release Workflow for Agent Protocol .NET SDK #120

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions packages/sdk/C#/.github/workflow/nuget-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Build and Publish NuGet Package

on:
workflow_dispatch:
push:
branches: [main, develop]
paths:
- "src/**"
- "Directory.*"
- "*.props"
- ".github/workflows/nuget-publish.yml"
- "GitVersion.yml"
pull_request:
branches: [main, develop]
paths:
- "src/**"
- "Directory.*"
- "*.props"
- ".github/workflows/nuget-publish.yml"
- "GitVersion.yml"

jobs:
build:
runs-on: ubuntu-latest
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true

steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required for GitVersion to work properly

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: "8.0.x"
source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json
env:
NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.10.2
with:
versionSpec: "5.x"

- name: Determine Version
id: gitversion
uses: gittools/actions/gitversion/execute@v0.10.2
with:
useConfigFile: true

- name: Display GitVersion outputs
run: |
echo "SemVer: ${{ steps.gitversion.outputs.semVer }}"
echo "NuGetVersionV2: ${{ steps.gitversion.outputs.nuGetVersionV2 }}"
echo "AssemblySemVer: ${{ steps.gitversion.outputs.assemblySemVer }}"
echo "FullSemVer: ${{ steps.gitversion.outputs.fullSemVer }}"

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --configuration Release --no-restore /p:Version=${{ steps.gitversion.outputs.assemblySemVer }}

- name: Run tests
run: dotnet test --no-build --verbosity normal --configuration Release

- name: Package NuGet
run: dotnet pack --configuration Release --no-build --output nupkgs /p:Version=${{ steps.gitversion.outputs.nuGetVersionV2 }} /p:PackageVersion=${{ steps.gitversion.outputs.nuGetVersionV2 }}

- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: nupkg
path: nupkgs/*.nupkg

# Publish to GitHub Packages only on main branch or when triggered manually
- name: Publish NuGet package to GitHub Packages
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
run: dotnet nuget push nupkgs/*.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json

# Create a GitHub release with tag when pushing to main
- name: Create Release
uses: actions/create-release@v1
if: github.ref == 'refs/heads/main'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.gitversion.outputs.semVer }}
release_name: Release v${{ steps.gitversion.outputs.semVer }}
draft: false
prerelease: false

# Publish to NuGet.org only for releases from main branch
- name: Publish to NuGet.org
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
run: dotnet nuget push nupkgs/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
81 changes: 81 additions & 0 deletions packages/sdk/C#/.github/workflow/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Create Release

on:
workflow_dispatch:
inputs:
releaseType:
description: "Release type (patch, minor, major)"
required: true
default: "patch"
type: choice
options:
- patch
- minor
- major

jobs:
create-release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_PAT }} # Personal access token with repo permissions

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: "8.0.x"

- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.10.2
with:
versionSpec: "5.x"

- name: Get current version
id: gitversion
uses: gittools/actions/gitversion/execute@v0.10.2
with:
useConfigFile: true

- name: Display current version
run: echo "Current version is ${{ steps.gitversion.outputs.semVer }}"

- name: Setup Git
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "github-actions@github.com"

- name: Determine new version
id: newversion
run: |
CURRENT_VERSION=${{ steps.gitversion.outputs.majorMinorPatch }}
RELEASE_TYPE=${{ github.event.inputs.releaseType }}

IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"

if [ "$RELEASE_TYPE" == "major" ]; then
NEW_VERSION="$((MAJOR + 1)).0.0"
elif [ "$RELEASE_TYPE" == "minor" ]; then
NEW_VERSION="$MAJOR.$((MINOR + 1)).0"
else # patch
NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"
fi

echo "newVersion=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "New version will be $NEW_VERSION"

- name: Create commit with version bump
run: |
echo "+semver: ${{ github.event.inputs.releaseType }}" > version-bump.txt
git add version-bump.txt
git commit -m "+semver: ${{ github.event.inputs.releaseType }}"
git push origin HEAD:main

- name: Create tag
run: |
git tag -a v${{ steps.newversion.outputs.newVersion }} -m "Release v${{ steps.newversion.outputs.newVersion }}"
git push origin v${{ steps.newversion.outputs.newVersion }}

# The actual release creation will be handled by the main workflow triggered by the tag push
117 changes: 117 additions & 0 deletions packages/sdk/C#/CI-CD-DOCUMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# CI/CD Pipeline Documentation

This document explains how the CI/CD pipeline works for this project, including versioning, building, and publishing NuGet packages.

## Overview

The project uses GitHub Actions for CI/CD with the following key components:

1. **GitVersion** - Handles automatic semantic versioning
2. **NuGet Package Build** - Builds and packages the library
3. **GitHub Packages** - Hosts NuGet packages for development
4. **NuGet.org** - Hosts public releases

## Workflow Files

The repository contains the following workflow files:

- `.github/workflows/nuget-publish.yml` - Main pipeline that builds, tests, versions, and publishes packages
- `.github/workflows/release.yml` - Dedicated workflow for creating releases (manually triggered)

## Versioning Strategy

We use GitVersion to automatically calculate semantic versions based on Git history and branching patterns:

- **GitVersion.yml** - Defines the versioning rules for different branches

### Branch-Based Versioning

Versions are automatically calculated based on the branch:

- **main**: Production releases with clean semantic versions (e.g., `1.2.3`)
- **develop**: Beta pre-releases (e.g., `1.3.0-beta.1`)
- **feature branches**: Alpha pre-releases (e.g., `1.3.0-alpha.feature-name.1`)
- **hotfix branches**: Release candidates (e.g., `1.2.4-rc.hotfix-name.1`)
- **PR branches**: Pre-release with PR number (e.g., `1.3.0-pr.123.1`)

### Commit Messages for Versioning

You can control version increments using special commit messages:

- `+semver: major` or `+semver: breaking` - Bump major version
- `+semver: minor` or `+semver: feature` - Bump minor version
- `+semver: patch` or `+semver: fix` - Bump patch version
- `+semver: none` or `+semver: skip` - Don't increment version

## Build Process

The build process follows these steps:

1. Checkout code with full history (required for GitVersion)
2. Set up .NET environment
3. Calculate version using GitVersion
4. Restore dependencies
5. Build with the version from GitVersion
6. Run tests
7. Package as NuGet with appropriate version
8. Upload package as artifact

## Publishing

Packages are published based on the branch and event:

- **GitHub Packages**:
- All packages from `main` branch
- All packages from manual workflow runs

- **NuGet.org**:
- Only packages from `main` branch
- Only for push events (not PRs)
- Requires `NUGET_API_KEY` secret

## Creating Releases

To create a new release:

1. Go to the "Actions" tab in GitHub
2. Select the "Create Release" workflow
3. Click "Run workflow"
4. Select the type of release (patch, minor, major)
5. Click "Run workflow" again

This will:

1. Create a commit with the selected semver increment
2. Push it to the main branch
3. Create a Git tag for the new version
4. Trigger the main workflow to build and publish the package

## Required Secrets

The pipeline requires the following GitHub secrets:

- `GITHUB_TOKEN` (automatically provided by GitHub)
- `NUGET_API_KEY` (for publishing to NuGet.org)
- `RELEASE_PAT` (for the release workflow to push to protected branches)

## NuGet Package Metadata

Package metadata is defined in `Directory.Build.props` including:

- Authors
- Description
- License
- Project URL
- Repository info
- Tags
- Icon
- README

## Debugging

If you encounter issues with the pipeline:

1. Check the GitHub Actions logs
2. Look for GitVersion output (shows calculated version)
3. Review the package artifacts for correct versioning
4. Ensure required secrets are configured
54 changes: 54 additions & 0 deletions packages/sdk/C#/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<Project>
<PropertyGroup>
<!-- Package Information -->
<Authors>Your Company Name</Authors>
<Company>Your Company Name</Company>
<Copyright>Copyright © $(Company) $([System.DateTime]::Now.Year)</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/$(GITHUB_REPOSITORY)</PackageProjectUrl>
<RepositoryUrl>https://github.com/$(GITHUB_REPOSITORY)</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>agent-protocol;client;api</PackageTags>
<Description>Client library for interacting with the Agent Protocol API</Description>

<!-- Build Configuration -->
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn>

<!-- Source Link -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>

<!-- NuGet Configuration -->
<IsPackable>true</IsPackable>
<PackageOutputPath>$(MSBuildThisFileDirectory)nupkgs</PackageOutputPath>
<VersionPrefix>1.0.0</VersionPrefix>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>

<ItemGroup>
<!-- Include README and icon in the package -->
<None Include="$(MSBuildThisFileDirectory)assets\icon.png" Pack="true" PackagePath="\" Visible="false" />
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" />

<!-- Source Link -->
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>

<!-- Deterministic builds for CI -->
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
</Project>
Loading