Skip to content
Merged
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
91 changes: 90 additions & 1 deletion .github/workflows/nuget.release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches: [ release ]

permissions:
contents: read
contents: write
packages: write
id-token: write

Expand All @@ -16,6 +16,72 @@ jobs:
steps:
- uses: actions/checkout@v4

# Deliberately before the build. A CHANGES.md section that is missing or
# still titled "Unreleased" fails the run here, while nothing has been
# published yet - rather than after the push to nuget.org, which cannot be
# taken back and whose version number cannot be reused.
- name: Resolve version and release notes
id: notes
run: |
set -euo pipefail

# No pipe into head: under `set -o pipefail` that only holds while the
# file has a single <PackageVersion>, and would start failing the day a
# second one appears. awk takes the first match and exits.
version=$(awk 'match($0, /<PackageVersion>[^<]+<\/PackageVersion>/) {
s = substr($0, RSTART, RLENGTH)
sub(/<PackageVersion>/, "", s)
sub(/<\/PackageVersion>/, "", s)
print s
exit
}' Xrpl/Xrpl.csproj)
if [ -z "$version" ]; then
echo "::error file=Xrpl/Xrpl.csproj::No <PackageVersion> found."
exit 1
fi

# Tags follow NuGet's own normalisation: 11.0.0.0 is published as
# 11.0.0, and v10.9.0 / v10.8.0 were tagged that way by hand. A
# non-zero fourth component is kept, because then it means something.
if [[ "$version" =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.0$ ]]; then
tag="v${BASH_REMATCH[1]}"
else
tag="v${version}"
fi

# From the "## <version> <date>" heading to the next "## " heading.
awk -v v="$version" '
$0 ~ "^## " v "( |$)" { found = 1; next }
found && /^## / { exit }
found { print }
' CHANGES.md > release-notes.md

if [ ! -s release-notes.md ]; then
echo "::error file=CHANGES.md::No section headed '## $version' - the release heading is not stamped, or the version does not match."
exit 1
fi

# GitHub caps release bodies at 125000 characters and this changelog runs
# long - 11.0.0.0 came to 98638 - so truncate rather than fail, and say
# so. By whole lines, not bytes: these notes carry non-ASCII characters
# and a byte cut would split one, handing the API a body that is not
# valid UTF-8. LC_ALL=C makes awk's length() count bytes, which is the
# unit the cap is expressed in.
limit=120000
if [ "$(wc -c < release-notes.md)" -gt "$limit" ]; then
LC_ALL=C awk -v limit="$limit" '
{ n += length($0) + 1; if (n > limit) exit; print }
' release-notes.md > release-notes.trimmed
printf '\n\n---\n\n*Truncated at %s characters. The full entry is in [CHANGES.md](https://github.com/%s/blob/release/CHANGES.md).*\n' \
"$limit" "$GITHUB_REPOSITORY" >> release-notes.trimmed
mv release-notes.trimmed release-notes.md
echo "::warning::Release notes truncated to $limit characters."
fi

echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "Release $tag from version $version, $(wc -c < release-notes.md) characters of notes."

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
Expand Down Expand Up @@ -53,3 +119,26 @@ jobs:

- name: Publish to NuGet.org
run: dotnet nuget push "**/*.nupkg" --skip-duplicate -s https://api.nuget.org/v3/index.json -k ${{ steps.nuget-login.outputs.NUGET_API_KEY }}

# Last, so a release only ever appears for packages that actually shipped.
# Skips an existing tag rather than failing: a re-run of this workflow
# should be able to finish, and --skip-duplicate above means the pushes
# are already idempotent.
- name: Publish GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.notes.outputs.tag }}
run: |
set -euo pipefail

if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG already exists - nothing to do."
exit 0
fi

gh release create "$TAG" \
--target "$GITHUB_SHA" \
--title "$TAG" \
--notes-file release-notes.md

echo "Created release $TAG."
Loading