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
141 changes: 141 additions & 0 deletions .github/actions/csharp-publish/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Copyright (c) 2026 Peaceful Studio OÜ
# SPDX-License-Identifier: Apache-2.0

name: CSharp Publish
description: >-
Build, test, pack and push .NET NuGet packages to nuget.org. Runs inline as
steps in the caller's job so the OIDC job_workflow_ref stays the caller's
workflow, enabling per-repo NuGet Trusted Publishing. The caller checks out
its own code and mints the short-lived API key (NuGet/login); this action
does neither.

inputs:
api-key:
description: 'Short-lived nuget.org API key minted by the caller''s NuGet/login step.'
required: true
version_override:
description: 'Override version (leave empty for auto-calculated).'
required: false
include_symbols:
description: 'Generate and publish .snupkg symbol packages alongside .nupkg.'
required: false
default: 'true'
working-directory:
description: 'Directory holding the solution to restore/build/test/pack (repo root by default).'
required: false
default: '.'
test-filter:
description: 'Optional dotnet test --filter expression (e.g. Category!=Integration). Empty runs every test.'
required: false
default: ''

runs:
using: composite
steps:
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'

- name: Get version info
id: version
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
VERSION_OVERRIDE: ${{ inputs.version_override }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
EVENT_NAME: ${{ github.event_name }}
RUN_NUMBER: ${{ github.run_number }}
run: |
set -euo pipefail

if [ -n "$VERSION_OVERRIDE" ]; then
echo "version=$VERSION_OVERRIDE" >> "$GITHUB_OUTPUT"
echo "Using override version: $VERSION_OVERRIDE"
exit 0
fi

if [ "$EVENT_NAME" = "release" ]; then
VERSION="${RELEASE_TAG#v}"
if [ -z "$VERSION" ]; then
echo "::error::release event fired but RELEASE_TAG is empty; cannot determine version" >&2
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Using release tag version: $VERSION"
exit 0
fi

if ! BASE_VERSION=$(grep -oP '(?<=<Version>)[^<]+' Directory.Build.props 2>/dev/null); then
echo "::error::Directory.Build.props is missing or has no <Version> element; cannot compute a pre-release version." >&2
exit 1
fi

SHORT_SHA=$(git rev-parse --short HEAD)
BRANCH_NAME="${GITHUB_REF##*/}"
BRANCH_NAME="${BRANCH_NAME//[^0-9a-zA-Z]/-}"
if [ "$BRANCH_NAME" = "prod" ]; then
FULL_VERSION="$BASE_VERSION"
else
FULL_VERSION="${BASE_VERSION}-${BRANCH_NAME}.${RUN_NUMBER}.${SHORT_SHA}"
fi
echo "version=$FULL_VERSION" >> "$GITHUB_OUTPUT"
echo "Calculated version: $FULL_VERSION"

- name: Restore dependencies
shell: bash
working-directory: ${{ inputs.working-directory }}
run: dotnet restore

- name: Build
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
PACKAGE_VERSION: ${{ steps.version.outputs.version }}
run: dotnet build --configuration Release --no-restore -p:Version="$PACKAGE_VERSION" -p:ContinuousIntegrationBuild=true

- name: Test
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
TEST_FILTER: ${{ inputs.test-filter }}
run: |
set -euo pipefail
if [ -n "$TEST_FILTER" ]; then
dotnet test --configuration Release --no-build --filter "$TEST_FILTER"
else
dotnet test --configuration Release --no-build
fi

- name: Pack NuGet packages
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
PACKAGE_VERSION: ${{ steps.version.outputs.version }}
INCLUDE_SYMBOLS: ${{ inputs.include_symbols }}
run: |
dotnet pack --configuration Release --no-build \
-p:PackageVersion="$PACKAGE_VERSION" \
-p:IncludeSymbols="$INCLUDE_SYMBOLS" \
-p:SymbolPackageFormat=snupkg \
-o "$GITHUB_WORKSPACE/output/nuget"

- name: Upload packages as artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: nuget-packages
path: |
./output/nuget/*.nupkg
./output/nuget/*.snupkg
if-no-files-found: ignore
retention-days: 30

- name: Push to nuget.org
shell: bash
env:
NUGET_API_KEY: ${{ inputs.api-key }}
run: |
set -euo pipefail
summary="${GITHUB_STEP_SUMMARY:-/dev/null}"
echo "## nuget.org push" >> "$summary"
"${{ github.action_path }}/../../../scripts/push-nuget.sh" ./output/nuget | tee -a "$summary"
6 changes: 5 additions & 1 deletion .github/workflows/csharp-publish-public.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) 2026 Peaceful Studio OÜ
# SPDX-License-Identifier: Apache-2.0

name: CSharp Publish (public)
name: CSharp Publish (public) [DEPRECATED]

on:
workflow_call:
Expand Down Expand Up @@ -41,6 +41,10 @@ jobs:
run:
shell: bash
steps:
- name: Deprecation notice
run: |
echo "::warning::csharp-publish-public.yaml is DEPRECATED. As a reusable workflow it runs the OIDC job in peacefulstudio/github-actions, so job_workflow_ref is always stamped 'github-actions' and a per-repo nuget Trusted Publishing policy can never match. Switch to the composite action peacefulstudio/github-actions/.github/actions/csharp-publish and mint the OIDC key (NuGet/login) in your own job."

- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `.github/actions/csharp-publish` composite action — builds, tests, packs and pushes .NET NuGet packages to nuget.org, enabling NuGet Trusted Publishing (OIDC) for consumer repos. The caller checks out its own code and mints the short-lived API key via `NuGet/login` in its own job, then invokes the action with `steps: - uses: peacefulstudio/github-actions/.github/actions/csharp-publish@v1`, passing `api-key`. Because the action runs inline as steps in the caller's job, `job_workflow_ref` stays the caller's publish workflow, so a per-repo nuget Trusted Publishing policy anchored on the consumer repo matches. Inputs: `api-key` (required), `version_override`, `include_symbols` (default `true`), `working-directory` (default `.`), `test-filter` (default empty).
- `working-directory` (default `.`) and `test-filter` (default empty) inputs on `csharp-publish-public.yaml`, matching the names used by `csharp-ci.yaml`. `working-directory` runs the restore/build/test/pack steps from a sub-path (the pack output stays at `$GITHUB_WORKSPACE/output/nuget` so the root-level push step is unaffected), letting repos whose solution lives below the root — e.g. `canton-localnet`'s `csharp/` — use the reusable workflow. `test-filter` passes a `dotnet test --filter` expression (e.g. `Category!=Integration`) to exclude tests that need live infrastructure. Both default to the previous behaviour, so existing callers are bit-for-bit unaffected.

### Deprecated

- **BREAKING for trusted publishing.** `csharp-publish-public.yaml` reusable workflow is deprecated. As a reusable workflow it runs the OIDC job in `peacefulstudio/github-actions`, so the `job_workflow_ref` claim is always stamped with `github-actions` and never the caller — a per-repo nuget Trusted Publishing policy anchored on the consumer repo can therefore never match (confirmed by a live HTTP 401). Consumers must switch to the `.github/actions/csharp-publish` composite action and mint the OIDC key (`NuGet/login`) in their own job.

### Changed

- **BREAKING.** `csharp-publish-public.yaml` now publishes to nuget.org via NuGet Trusted Publishing (short-lived OIDC token exchanged for a temporary API key through `NuGet/login`) instead of long-lived API keys. The four `NUGET_API_KEY_*` secrets (`NUGET_API_KEY_CANTON`, `NUGET_API_KEY_DAML`, `NUGET_API_KEY_SPLICE`, `NUGET_API_KEY_PEACEFUL`) are removed. Callers must instead provide an organization secret `NUGET_USER` (the nuget.org profile name), grant `permissions: id-token: write`, and register a nuget.org Trusted Publishing policy (**Workflow File** = `csharp-publish-public.yaml` — the reusable file, not the caller; **Environment** = `nuget-publish`). `scripts/route-nuget-push.sh` is renamed to `scripts/push-nuget.sh`; per-owner key routing is removed since one user/key now pushes every package.
Expand Down