diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..34d2299 --- /dev/null +++ b/.github/workflows/publish.yml @@ -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 diff --git a/README.md b/README.md index 4b35953..28bef7d 100644 --- a/README.md +++ b/README.md @@ -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. --- diff --git a/docs/release-process.md b/docs/release-process.md index 056c591..7adf28c 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -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.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 `` and `` 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 -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.