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
58 changes: 54 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: Release

on: workflow_dispatch
on:
push:
tags: ['v*']
workflow_dispatch:

env:
NuGetDirectory: ${{ github.workspace}}/nuget
Expand Down Expand Up @@ -47,7 +50,7 @@ jobs:
if-no-files-found: error
retention-days: 7
path: ${{ env.NuGetDirectory }}/*.nupkg

deploy:
runs-on: ubuntu-latest
needs: [ build ]
Expand All @@ -64,5 +67,52 @@ jobs:
run: |
foreach($file in (Get-ChildItem "${{ env.NuGetDirectory }}" -Recurse -Include *.nupkg)) {
dotnet nuget push $file --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate
}

}

github-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [ deploy ]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/download-artifact@v4
with:
name: nuget
path: ${{ env.NuGetDirectory }}

- name: Extract release notes from CHANGELOG.md
shell: bash
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
version="${tag#v}"
# Extract the section under "## [<version>]" up to (but not including)
# the next "## [" heading. Empty output means the CHANGELOG was not
# updated for this version — fail loudly so the maintainer notices.
awk -v ver="$version" '
$0 ~ "^## \\[" ver "\\]" { capture = 1; next }
capture && /^## \[/ { exit }
capture { print }
' CHANGELOG.md > release-notes.md
if [ ! -s release-notes.md ]; then
echo "::error::No CHANGELOG.md section found for [$version]. Move the [Unreleased] block to [$version] and re-tag." >&2
exit 1
fi
echo "--- Release notes for $tag ---"
cat release-notes.md

- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--notes-file release-notes.md \
${{ env.NuGetDirectory }}/*.nupkg
13 changes: 8 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,19 @@ git tag -l 'v*' --sort=-v:refname | head -1

### Cutting a release

Releases are automated. Pushing a `v`-prefixed tag fires `.github/workflows/release.yml`, which builds, packs, publishes to NuGet.org, and creates a matching GitHub Release with notes extracted from `CHANGELOG.md`.

```bash
# 1. Merge the release PR (which updates CHANGELOG.md)
# 2. Tag the merge commit on main
# 1. Move the CHANGELOG [Unreleased] block to [X.Y.Z] (with today's date if you
# want one — the workflow does not require a date), commit, and merge to main.
# 2. Tag the merge commit and push the tag.
git tag -a v1.2.0 -m "Release 1.2.0"
git push origin v1.2.0

# 3. Trigger the Release workflow via GitHub Actions UI (workflow_dispatch)
```

The `Release` workflow (`.github/workflows/release.yml`) builds, packs, and publishes to NuGet.org.
The workflow extracts the `## [X.Y.Z]` section of `CHANGELOG.md` and uses it as the GitHub Release body. If no matching section exists for the tag's version, the workflow fails loudly — the fix is to update `CHANGELOG.md` and re-tag.

`workflow_dispatch` is still wired up as a manual fallback for ad-hoc re-publishes (e.g. if a NuGet push fails partway through), but the normal flow is tag-push.

## Scope

Expand Down
Loading