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
16 changes: 15 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
# to both ran every PR twice. A branch with no PR gets no run, which is the trade.
push:
branches: [master]
tags: ['v*']
pull_request:
workflow_dispatch:

Expand Down Expand Up @@ -302,7 +303,11 @@ jobs:
publish:
name: publish prerelease
needs: [build, wasm]
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request'
# Also on a v* tag, where the push to GitHub Packages is a no-op against an existing
# version but the packed artifact is what release.yml then sends to nuget.org.
if: >-
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
&& github.event_name != 'pull_request'
Comment on lines +308 to +310
runs-on: ubuntu-latest

steps:
Expand All @@ -319,5 +324,14 @@ jobs:
- name: Pack
run: dotnet pack ImpromptuInterface/ImpromptuInterface.csproj --configuration Release --output packages

# Before the push, not after: release.yml republishes exactly these to nuget.org when the
# commit is tagged, rather than packing again, so what ships is the artifact this run's
# tests passed against - and a failed push to GitHub Packages must not cost it that.
- name: Upload the packages
uses: actions/upload-artifact@v4
with:
name: nuget-packages
path: packages/*.nupkg

- name: Push to GitHub Packages
run: dotnet nuget push 'packages/*.nupkg' --source https://nuget.pkg.github.com/ekonbenefits/index.json --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
115 changes: 115 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Publishes to nuget.org when a v* tag is pushed, and creates the GitHub Release.
#
# It republishes the packages build.yml's own prerelease job produced for that commit rather
# than packing a second time, so what reaches nuget.org is the artifact that run's tests passed
# against - not a separately-produced, almost-certainly-identical set that never went through
# them. That is why it triggers off build.yml completing rather than off the tag directly.
#
# A workflow_run trigger is always read from the default branch, whatever is in the tag being
# released, so this file has to be on master before it will fire for any future tag.
#
# Authentication is nuget.org Trusted Publishing (OIDC): no long-lived API key in a secret,
# only NUGET_USER, which is a plain account name rather than a credential. It requires a
# Trusted Publishing policy on nuget.org for this exact repository AND this exact workflow
# filename - so renaming this file breaks publishing until the policy is updated to match.
name: release

# Not workflow_dispatch: every step here reads github.event.workflow_run, which a manual run
# does not have. Re-run this workflow from the failed run's own page instead.
on:
workflow_run:
workflows: [build]
types: [completed]

jobs:
publish:
name: publish to nuget.org
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
startsWith(github.event.workflow_run.head_branch, 'v')
runs-on: ubuntu-latest
permissions:
id-token: write # the OIDC token Trusted Publishing exchanges for a temporary key
contents: write # creating the Release
actions: read # reading the build run's artifact; an explicit map makes the rest none

steps:
# head_branch is the ref name for either kind of push, so a *branch* called "v-something"
# would otherwise satisfy the condition above. Publishing is irreversible; check.
- name: Verify the ref is a tag, still pointing at the commit that was tested
env:
GH_TOKEN: ${{ github.token }}
REF_NAME: ${{ github.event.workflow_run.head_branch }}
TESTED_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
if ! tag_sha=$(gh api "repos/${{ github.repository }}/git/ref/tags/$REF_NAME" --jq '.object.sha' 2>/dev/null); then
echo "::error::'$REF_NAME' is not a tag in this repository - refusing to publish."
exit 1
fi

# An annotated tag's ref points at the tag object; dereference to the commit.
type=$(gh api "repos/${{ github.repository }}/git/ref/tags/$REF_NAME" --jq '.object.type')
if [ "$type" = "tag" ]; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq '.object.sha')
fi

if [ "$tag_sha" != "$TESTED_SHA" ]; then
echo "::error::'$REF_NAME' now points at $tag_sha, but the tested build was $TESTED_SHA."
echo "The tag moved after the build; refusing to publish packages under it."
exit 1
fi
echo "Confirmed '$REF_NAME' is a tag at the tested commit $TESTED_SHA."

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

- name: Download the packages that run's tests passed against
uses: actions/download-artifact@v4
with:
name: nuget-packages
path: packages
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}

- name: Show what is about to be published
run: ls -l packages

- name: NuGet login (OIDC -> temporary API key)
uses: NuGet/login@v1
id: login
with:
user: ${{ secrets.NUGET_USER }}

- name: Push to nuget.org
shell: bash
run: |
set -e
for pkg in packages/*.nupkg; do
dotnet nuget push "$pkg" \
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
done

# Idempotent, so re-running a release after a partial failure works: --skip-duplicate
# covers the pushes, and this covers the Release.
- name: Create or update the GitHub Release
env:
GH_TOKEN: ${{ github.token }}
REF_NAME: ${{ github.event.workflow_run.head_branch }}
run: |
set -e
if gh release view "$REF_NAME" --repo "${{ github.repository }}" >/dev/null 2>&1; then
echo "Release $REF_NAME exists; refreshing its assets."
gh release upload "$REF_NAME" packages/*.nupkg \
--repo "${{ github.repository }}" --clobber
else
gh release create "$REF_NAME" packages/*.nupkg \
--repo "${{ github.repository }}" \
--title "$REF_NAME" \
--generate-notes \
--verify-tag
fi
Loading