Skip to content
Merged
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
130 changes: 130 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: Publish NuGet Package

on:
workflow_dispatch:
inputs:
tag:
description: Existing Git tag to publish from
required: true
type: string
package:
description: NuGet package to publish
required: true
type: choice
options:
- TokenGuard.Core
- TokenGuard.Extensions.OpenAI
- TokenGuard.Extensions.Anthropic

permissions: {}

concurrency:
group: nuget-publish-${{ inputs.package }}-${{ inputs.tag }}
cancel-in-progress: false

jobs:
validate:
name: Validate and pack
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout tagged source
uses: actions/checkout@v4
with:
ref: refs/tags/${{ inputs.tag }}

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x

- name: Select package project
id: package
shell: bash
env:
PACKAGE_ID: ${{ inputs.package }}
run: |
case "$PACKAGE_ID" in
TokenGuard.Core)
project="src/TokenGuard.Core/TokenGuard.Core.csproj"
;;
TokenGuard.Extensions.OpenAI)
project="src/TokenGuard.Extensions.OpenAI/TokenGuard.Extensions.OpenAI.csproj"
;;
TokenGuard.Extensions.Anthropic)
project="src/TokenGuard.Extensions.Anthropic/TokenGuard.Extensions.Anthropic.csproj"
;;
*)
echo "Unsupported package: $PACKAGE_ID" >&2
exit 1
;;
esac

echo "project=$project" >> "$GITHUB_OUTPUT"

- name: Restore solution
run: dotnet restore TokenGuard.sln --nologo

- name: Build solution
run: dotnet build TokenGuard.sln --configuration Release --no-restore --nologo

- name: Run test suites
run: dotnet test TokenGuard.sln --configuration Release --no-build --nologo

- name: Pack selected package
run: dotnet pack "${{ steps.package.outputs.project }}" --configuration Release --no-build --nologo --output artifacts/release

- name: Upload package artifacts
uses: actions/upload-artifact@v4
with:
name: nuget-package
path: |
artifacts/release/*.nupkg
artifacts/release/*.snupkg
if-no-files-found: error
retention-days: 1

publish:
name: Publish to nuget.org
needs: validate
runs-on: ubuntu-latest
environment: release
permissions:
contents: read
id-token: write

steps:
- name: Download package artifacts
uses: actions/download-artifact@v4
with:
name: nuget-package
path: artifacts/release

- name: NuGet login
id: login
uses: NuGet/login@v1
with:
user: ${{ vars.NUGET_USER }}

- name: Publish package
shell: bash
env:
NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY }}
run: |
dotnet nuget push artifacts/release/*.nupkg \
--source https://api.nuget.org/v3/index.json \
--api-key "$NUGET_API_KEY" \
--skip-duplicate \
--no-symbols

- name: Publish symbols
shell: bash
env:
NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY }}
run: |
dotnet nuget push artifacts/release/*.snupkg \
--source https://api.nuget.org/v3/index.json \
--api-key "$NUGET_API_KEY" \
--skip-duplicate
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,10 @@ dotnet build ./samples/Codexplorer/src/Codexplorer.csproj --nologo

Authoritative NuGet release runbook lives in [docs/release-process.md](docs/release-process.md).

Use that path for publishing `TokenGuard.Core`, `TokenGuard.Extensions.OpenAI`, and
`TokenGuard.Extensions.Anthropic`. It requires a passing
`.github/workflows/release-validation.yml` run on the exact commit being published and documents
versioning, secrets, package order, and `.snupkg` symbol publication.
Use that path to publish `TokenGuard.Core`, `TokenGuard.Extensions.OpenAI`, or
`TokenGuard.Extensions.Anthropic` from an existing Git tag. Publication runs manually through
GitHub Actions, publishes one selected package per run, and authenticates to nuget.org through
Trusted Publishing without a long-lived API key.

---

Expand Down
159 changes: 48 additions & 111 deletions docs/release-process.md
Original file line number Diff line number Diff line change
@@ -1,135 +1,72 @@
# NuGet Release Process

This document is the single authoritative path for publishing `TokenGuard.Core`,
`TokenGuard.Extensions.OpenAI`, and `TokenGuard.Extensions.Anthropic` to NuGet.
This document defines manual publication for `TokenGuard.Core`, `TokenGuard.Extensions.OpenAI`, and `TokenGuard.Extensions.Anthropic` through GitHub Actions and NuGet Trusted Publishing.

## Scope
## Release Model

- Publishes the three public NuGet packages from this repository.
- Uses the existing validation gate in `.github/workflows/release-validation.yml`.
- Assumes package functionality is already complete and release-ready.
- Each package owns its version in its `.csproj` file.
- Package versions do not need to match.
- One workflow run publishes one selected package.
- One existing Git tag identifies exact source commit.
- When one tag contains changes for multiple packages, run workflow once per changed package.
- Merges, pushes, and tag creation never publish automatically.

## Prerequisites
## One-Time Configuration

Before starting a release:
### GitHub

1. Work from a **clean checkout** of the exact commit you will publish. Do not publish from a dirty
working tree.
2. Use **.NET SDK 10.0+**.
3. Have a NuGet.org API key with push permission for all three package IDs.
4. Export the API key into your shell:
1. Create environment named `release` under repository settings.
2. Add required reviewer to `release` environment.
3. Add Actions repository variable `NUGET_USER` containing nuget.org username, not email address.

```bash
export NUGET_API_KEY="<nuget-api-key>"
```
### NuGet.org

5. Ensure the release version is already committed in all three project files:
- `src/TokenGuard.Core/TokenGuard.Core.csproj`
- `src/TokenGuard.Extensions.OpenAI/TokenGuard.Extensions.OpenAI.csproj`
- `src/TokenGuard.Extensions.Anthropic/TokenGuard.Extensions.Anthropic.csproj`
Create Trusted Publishing policy with:

## Versioning Expectations
- Repository owner: `svetstoykov`
- Repository: `TokenGuard`
- Workflow file: `publish.yml`
- Environment: `release`

- Use one shared SemVer version for all three packages in each release.
- Keep `TokenGuard.Core`, `TokenGuard.Extensions.OpenAI`, and `TokenGuard.Extensions.Anthropic`
on the same version number.
- Commit version changes before publishing. Do not override package versions only at pack time.
- Stable releases use versions without a prerelease suffix. If a prerelease is needed, use the same
prerelease suffix on all three packages.
No long-lived NuGet API key belongs in GitHub secrets.

## Required Validation Gate
## Prepare Release

Publish only from a commit that already has a **passing** run of
`.github/workflows/release-validation.yml` on GitHub. That workflow is the required pre-release
gate and validates:
1. Update `<Version>` and `<PackageReleaseNotes>` in package project being released.
2. Update `CHANGELOG.md` for package release.
3. Merge release changes.
4. Create and push Git tag pointing to exact commit to publish.
5. Confirm `.github/workflows/release-validation.yml` passed for tagged commit.

- `dotnet restore TokenGuard.sln --nologo`
- `dotnet build TokenGuard.sln --configuration Release --no-restore --nologo`
- `dotnet test TokenGuard.sln --configuration Release --no-build --nologo`
- `dotnet pack` for all three public packages
Package projects:

If the workflow is not green for the exact commit being published, stop and fix validation first.
- `src/TokenGuard.Core/TokenGuard.Core.csproj`
- `src/TokenGuard.Extensions.OpenAI/TokenGuard.Extensions.OpenAI.csproj`
- `src/TokenGuard.Extensions.Anthropic/TokenGuard.Extensions.Anthropic.csproj`

## Release Procedure
## Publish Package

### 1. Publish release metadata changes
1. Open repository Actions page.
2. Select **Publish NuGet Package** workflow.
3. Select **Run workflow**.
4. Enter existing tag.
5. Select package to publish.
6. Start workflow.
7. Confirm validation job restores, builds, tests, packs, and uploads package artifacts.
8. Approve `release` environment deployment.
9. Confirm publication job completes.

Prepare and merge the release commit to `main` with the intended package version already set in all
three `.csproj` files.
Workflow publishes selected `.nupkg` and matching `.snupkg`. Package version comes from selected project file.

Optional but recommended:
## Retry Behavior

- update package release notes in the same release commit
- create an annotated Git tag such as `v1.2.3` on the validated commit after merge
Package versions on nuget.org remain immutable. Workflow uses duplicate-safe pushes, allowing same tag and package selection to be rerun after partial failure. Existing package artifacts are skipped; missing artifacts are pushed.

### 2. Confirm the validated commit

Confirm the exact `main` commit or release tag you will publish already passed
`.github/workflows/release-validation.yml`.

### 3. Pack from that exact commit

From a clean checkout of the validated commit:

```bash
git checkout <validated-commit-or-tag>
rm -rf artifacts/release
dotnet restore TokenGuard.sln --nologo
dotnet build TokenGuard.sln --configuration Release --no-restore --nologo
dotnet test TokenGuard.sln --configuration Release --no-build --nologo
dotnet pack src/TokenGuard.Core/TokenGuard.Core.csproj --configuration Release --no-build --nologo --output artifacts/release
dotnet pack src/TokenGuard.Extensions.OpenAI/TokenGuard.Extensions.OpenAI.csproj --configuration Release --no-build --nologo --output artifacts/release
dotnet pack src/TokenGuard.Extensions.Anthropic/TokenGuard.Extensions.Anthropic.csproj --configuration Release --no-build --nologo --output artifacts/release
```

This local pack step must match the validated commit and must not introduce uncommitted tracked-file
changes.

### 4. Publish packages to NuGet

Publish in this order:

1. `TokenGuard.Core`
2. `TokenGuard.Extensions.OpenAI`
3. `TokenGuard.Extensions.Anthropic`

`TokenGuard.Core` goes first because both extension packages depend on it. After `TokenGuard.Core`
is published, the two extension packages can be pushed in either order, but this runbook uses the
order above every time.

Push both the primary package and its matching symbol package for each project:

```bash
dotnet nuget push artifacts/release/TokenGuard.Core.*.nupkg --source https://api.nuget.org/v3/index.json --api-key "$NUGET_API_KEY"
dotnet nuget push artifacts/release/TokenGuard.Core.*.snupkg --source https://api.nuget.org/v3/index.json --api-key "$NUGET_API_KEY"

dotnet nuget push artifacts/release/TokenGuard.Extensions.OpenAI.*.nupkg --source https://api.nuget.org/v3/index.json --api-key "$NUGET_API_KEY"
dotnet nuget push artifacts/release/TokenGuard.Extensions.OpenAI.*.snupkg --source https://api.nuget.org/v3/index.json --api-key "$NUGET_API_KEY"

dotnet nuget push artifacts/release/TokenGuard.Extensions.Anthropic.*.nupkg --source https://api.nuget.org/v3/index.json --api-key "$NUGET_API_KEY"
dotnet nuget push artifacts/release/TokenGuard.Extensions.Anthropic.*.snupkg --source https://api.nuget.org/v3/index.json --api-key "$NUGET_API_KEY"
```

## Symbols and Source Handling

- `src/Directory.Build.props` enables symbol package generation for public packages.
- Each `dotnet pack` command produces the primary `.nupkg` and a matching `.snupkg`.
- Release publication includes both artifacts for every package.
- No separate source-only package is published as part of this process.
If Trusted Publishing login fails, verify exact owner, repository, workflow filename, environment, and `NUGET_USER` values against NuGet policy.

## Post-Publish Checks

After pushing all six artifacts:

1. Verify all three package versions appear on NuGet.org.
2. Verify each package version has its matching symbol package accepted by NuGet.org.
3. Verify package READMEs render correctly on the NuGet package pages.
4. If you created a release tag, push that tag if it has not already been pushed.

## If Something Fails

- If validation fails, do not publish. Fix the repository, rerun validation, and publish a newly
validated commit.
- If package push fails before all three packages are published, inspect which packages already
exist on NuGet.org before retrying.
- Do not change code locally and publish without recommitting and rerunning the validation gate.
1. Verify selected package version appears on nuget.org.
2. Verify symbol package finishes validation.
3. Verify package README and release notes render correctly.
4. If another package changed at same tag, run workflow again and select that package.
Loading