Skip to content

Make NuGet README packaging explicit per project #5

Make NuGet README packaging explicit per project

Make NuGet README packaging explicit per project #5

Workflow file for this run

name: Release
on:
push:
branches: [ main ]
workflow_dispatch:
env:
DOTNET_VERSION: '10.0.x'
NODE_VERSION: '22'
jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v5
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install Claude Code CLI
run: npm install --global @anthropic-ai/claude-code
- name: Verify Claude Code CLI
run: claude --version
- name: Verify unauthenticated Claude Code behavior
shell: bash
run: |
set -euo pipefail
sandbox="$RUNNER_TEMP/claude-empty-home"
output_file="$RUNNER_TEMP/claude-unauth-output.txt"
rm -rf "$sandbox"
mkdir -p "$sandbox/.config" "$sandbox/AppData/Roaming" "$sandbox/AppData/Local"
set +e
HOME="$sandbox" \
USERPROFILE="$sandbox" \
XDG_CONFIG_HOME="$sandbox/.config" \
APPDATA="$sandbox/AppData/Roaming" \
LOCALAPPDATA="$sandbox/AppData/Local" \
claude -p "health check" >"$output_file" 2>&1
exit_code=$?
set -e
cat "$output_file"
if [ "$exit_code" -eq 0 ]; then
echo "Expected unauthenticated Claude CLI invocation to fail in isolated profile."
exit 1
fi
grep -Eiq "Please run /login|Not logged in|Not authenticated|Invalid API key|claude login" "$output_file"
- name: Extract version from Directory.Build.props
id: version
run: |
VERSION=$(grep -oPm1 "(?<=<Version>)[^<]+" Directory.Build.props)
if [ -z "$VERSION" ]; then
echo "Failed to resolve package version from Directory.Build.props"
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Version from Directory.Build.props: $VERSION"
- name: Validate semantic version
run: |
VERSION="${{ steps.version.outputs.version }}"
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "Version '$VERSION' is not a valid semantic version."
exit 1
fi
- name: Restore dependencies
run: dotnet restore ManagedCode.ClaudeCodeSharpSDK.slnx
- name: Build
run: dotnet build ManagedCode.ClaudeCodeSharpSDK.slnx --configuration Release --warnaserror --no-restore
- name: Test (full solution)
run: dotnet test --solution ManagedCode.ClaudeCodeSharpSDK.slnx --configuration Release --no-build -- --treenode-filter "/*/*/*/*[RequiresClaudeAuth!=true]"
- name: Claude Code CLI smoke tests
run: dotnet test --project ClaudeCodeSharpSDK.Tests/ClaudeCodeSharpSDK.Tests.csproj --configuration Release --no-build -- --treenode-filter "/*/*/*/ClaudeCli_Smoke_*"
- name: Pack NuGet packages
run: |
dotnet pack ClaudeCodeSharpSDK/ClaudeCodeSharpSDK.csproj --configuration Release --no-build -p:IncludeSymbols=false -p:SymbolPackageFormat=snupkg --output ./artifacts
dotnet pack ClaudeCodeSharpSDK.Extensions.AI/ClaudeCodeSharpSDK.Extensions.AI.csproj --configuration Release --no-build -p:IncludeSymbols=false -p:SymbolPackageFormat=snupkg --output ./artifacts
- name: Validate packaged version
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.version }}"
mapfile -t packages < <(find ./artifacts -maxdepth 1 -type f -name '*.nupkg' | sort)
if [ "${#packages[@]}" -eq 0 ]; then
echo "No NuGet packages were created."
exit 1
fi
for package in "${packages[@]}"; do
file_name=$(basename "$package")
echo "Validating $file_name"
if [[ "$file_name" != *".${VERSION}.nupkg" ]]; then
echo "Package version mismatch: expected filename to end with .${VERSION}.nupkg"
exit 1
fi
done
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: nuget-packages
path: ./artifacts/*.nupkg
retention-days: 5
publish-nuget:
name: Publish to NuGet
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
outputs:
published: ${{ steps.publish.outputs.published }}
version: ${{ needs.build.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Download artifacts
uses: actions/download-artifact@v5
with:
name: nuget-packages
path: ./artifacts
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Publish to NuGet
id: publish
run: |
set +e
OUTPUT=""
PUBLISHED=false
for package in ./artifacts/*.nupkg; do
echo "Publishing $package..."
RESULT=$(dotnet nuget push "$package" \
--api-key ${{ secrets.NUGET_API_KEY }} \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate 2>&1)
EXIT_CODE=$?
echo "$RESULT"
OUTPUT="$OUTPUT$RESULT"
if [ $EXIT_CODE -eq 0 ]; then
echo "Successfully published $package"
PUBLISHED=true
elif echo "$RESULT" | grep -qi "already exists"; then
echo "Package already exists, skipping..."
else
echo "Failed to publish $package"
exit 1
fi
done
if [ "$PUBLISHED" = true ] || echo "$OUTPUT" | grep -q "Your package was pushed"; then
echo "published=true" >> "$GITHUB_OUTPUT"
echo "At least one package was successfully published"
else
echo "published=false" >> "$GITHUB_OUTPUT"
echo "No new packages were published (all already exist)"
fi
create-release:
name: Create GitHub Release and Tag
needs: publish-nuget
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && needs.publish-nuget.result == 'success'
permissions:
actions: read
contents: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.publish-nuget.outputs.version }}
TAG: v${{ needs.publish-nuget.outputs.version }}
PACKAGES_PUBLISHED: ${{ needs.publish-nuget.outputs.published }}
steps:
- name: Verify GitHub CLI
run: gh --version
- name: Resolve release state
id: state
shell: bash
run: |
set -euo pipefail
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
RELEASE_EXISTS=true
else
RELEASE_EXISTS=false
fi
SHOULD_SYNC=false
if [ "$PACKAGES_PUBLISHED" = "true" ] || [ "$RELEASE_EXISTS" != "true" ]; then
SHOULD_SYNC=true
fi
echo "release_exists=$RELEASE_EXISTS" >> "$GITHUB_OUTPUT"
echo "should_sync=$SHOULD_SYNC" >> "$GITHUB_OUTPUT"
echo "Release exists: $RELEASE_EXISTS"
echo "Should sync release: $SHOULD_SYNC"
- name: Download package artifacts
if: steps.state.outputs.should_sync == 'true'
shell: bash
run: |
set -euo pipefail
mkdir -p artifacts
gh run download "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" --name nuget-packages --dir artifacts
find artifacts -maxdepth 2 -type f -name '*.nupkg' -print
- name: Create or update GitHub Release
if: steps.state.outputs.should_sync == 'true'
shell: bash
run: |
set -euo pipefail
mapfile -t packages < <(find artifacts -maxdepth 2 -type f -name '*.nupkg' | sort)
if [ "${#packages[@]}" -eq 0 ]; then
echo "No NuGet package artifacts were downloaded."
exit 1
fi
NOTES=$(cat <<EOF
## NuGet Packages
- [ManagedCode.ClaudeCodeSharpSDK v$VERSION](https://www.nuget.org/packages/ManagedCode.ClaudeCodeSharpSDK/$VERSION)
- [ManagedCode.ClaudeCodeSharpSDK.Extensions.AI v$VERSION](https://www.nuget.org/packages/ManagedCode.ClaudeCodeSharpSDK.Extensions.AI/$VERSION)
EOF
)
if [ "${{ steps.state.outputs.release_exists }}" = "true" ]; then
mapfile -t existing_assets < <(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name')
for asset in "${existing_assets[@]}"; do
case "$asset" in
ManagedCode.ClaudeCodeSharpSDK*.nupkg)
keep_asset=false
for package in "${packages[@]}"; do
if [ "$asset" = "$(basename "$package")" ]; then
keep_asset=true
break
fi
done
if [ "$keep_asset" != "true" ]; then
gh release delete-asset "$TAG" "$asset" --repo "$GITHUB_REPOSITORY" --yes
fi
;;
esac
done
gh release upload "$TAG" "${packages[@]}" --repo "$GITHUB_REPOSITORY" --clobber
else
gh release create "$TAG" "${packages[@]}" \
--repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \
--title "$TAG" \
--generate-notes \
--notes "$NOTES"
fi
- name: Skip release sync
if: steps.state.outputs.should_sync != 'true'
run: echo "Release already exists and no new NuGet packages were published."