diff --git a/.github/workflows/nuget-dotnet.yml b/.github/workflows/nuget-dotnet.yml
index e6ab2ca..2b54748 100644
--- a/.github/workflows/nuget-dotnet.yml
+++ b/.github/workflows/nuget-dotnet.yml
@@ -2,11 +2,7 @@ name: Build and Publish CaeriusNet
on:
push:
- branches:
- - main
- pull_request:
- branches:
- - main
+ branches: [main]
workflow_dispatch:
inputs:
version_increment:
@@ -20,13 +16,21 @@ on:
- minor
- major
+# Minimal default permissions — each job declares only what it needs
+permissions:
+ contents: read
+
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_NOLOGO: true
+ DOTNET_VERSION: '10.0.x'
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
jobs:
+ # ── 1. Version bump ──────────────────────────────────────────────────────────
+ # Only runs when Dependabot merges a dependency-update PR to main.
+ # Commits an incremented patch version with [skip ci] to avoid retriggering.
bump-version:
name: Bump Version (Dependabot)
runs-on: ubuntu-latest
@@ -36,7 +40,7 @@ jobs:
outputs:
new_version: ${{ steps.bump.outputs.new_version }}
steps:
- - name: Checkout Repository
+ - name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -51,6 +55,7 @@ jobs:
NEW_VERSION=$(echo $VERSION | sed "s/\.[0-9]*$/.$NEW_PATCH/")
sed -i "s|$VERSION|$NEW_VERSION|g" Src/CaeriusNet.csproj
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
+ echo "🔖 Bumped version: $VERSION → $NEW_VERSION"
- name: Commit and Push Version Bump
run: |
@@ -60,34 +65,39 @@ jobs:
git commit -m "chore: bump version to ${{ steps.bump.outputs.new_version }} [skip ci]"
git push
+ # ── 2. Test & Validate ───────────────────────────────────────────────────────
+ # Uses always() + explicit skipped-result check to break skip-propagation from
+ # bump-version (which is skipped for non-Dependabot pushes).
test:
name: Test & Validate
runs-on: ubuntu-latest
needs: [bump-version]
if: always() && (needs.bump-version.result == 'success' || needs.bump-version.result == 'skipped')
+ permissions:
+ contents: read
steps:
- - name: Checkout Repository
+ - name: Checkout
uses: actions/checkout@v4
- - name: Setup .NET
+ - name: Setup .NET ${{ env.DOTNET_VERSION }}
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '10.0.x'
+ dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Cache NuGet packages
- uses: actions/cache@v5
+ uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.slnx') }}
restore-keys: nuget-${{ runner.os }}-
- - name: Restore Dependencies
+ - name: Restore
run: dotnet restore CaeriusNet.slnx
- name: Build
run: dotnet build CaeriusNet.slnx --configuration Release --no-restore
- - name: Run Tests
+ - name: Test
run: |
dotnet test CaeriusNet.slnx \
--configuration Release \
@@ -97,70 +107,192 @@ jobs:
--results-directory ./coverage \
--logger "console;verbosity=normal"
+ # ── 3. Build & Pack ──────────────────────────────────────────────────────────
+ # Produces .nupkg + .snupkg and uploads them as a workflow artifact.
+ # always() + explicit skip-check mirrors the pattern above to propagate correctly.
build:
name: Build and Pack
runs-on: ubuntu-latest
- needs: [test]
- if: always() && needs.test.result == 'success'
+ needs: [test, bump-version]
+ if: |
+ always() &&
+ needs.test.result == 'success' &&
+ (needs.bump-version.result == 'success' || needs.bump-version.result == 'skipped')
+ permissions:
+ contents: read
steps:
- - name: Checkout Repository
+ - name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- - name: Setup .NET
+ - name: Setup .NET ${{ env.DOTNET_VERSION }}
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '10.0.x'
+ dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Cache NuGet packages
- uses: actions/cache@v5
+ uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.slnx') }}
restore-keys: nuget-${{ runner.os }}-
- - name: Restore Dependencies
+ - name: Restore
run: dotnet restore Src/CaeriusNet.csproj
- - name: Build CaeriusNet
+ - name: Build
run: dotnet build Src/CaeriusNet.csproj --configuration Release --no-restore
- - name: Pack CaeriusNet (nupkg + snupkg)
- run: >
- dotnet pack Src/CaeriusNet.csproj
- --configuration Release
- --no-build
- --output ./packages
- -p:IncludeSymbols=true
- -p:SymbolPackageFormat=snupkg
+ - name: Pack (nupkg + snupkg)
+ run: |
+ dotnet pack Src/CaeriusNet.csproj \
+ --configuration Release \
+ --no-build \
+ --output ./packages \
+ -p:IncludeSymbols=true \
+ -p:SymbolPackageFormat=snupkg
- - name: Upload Package Artifacts
+ - name: Upload package artifacts
uses: actions/upload-artifact@v4
with:
name: caeriusnet-packages
path: ./packages/
retention-days: 7
- publish:
- name: Publish to NuGet
- needs: build
+ # ── 4a. Publish → NuGet.org ──────────────────────────────────────────────────
+ # always() + explicit needs-result checks override the transitive skip-propagation
+ # from bump-version. Without always() here, this job would be skipped every time
+ # bump-version is skipped (i.e., every non-Dependabot push to main).
+ #
+ # To upgrade to keyless OIDC publishing (recommended):
+ # 1. Create a Trusted Publishing policy on nuget.org for this repo/workflow
+ # 2. Replace the NUGET_API_KEY step with: uses: NuGet/login@v1
+ # 3. Pass ${{ steps.login.outputs.NUGET_API_KEY }} to dotnet nuget push
+ publish-nuget:
+ name: "Publish \u2192 NuGet.org"
runs-on: ubuntu-latest
- if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'
+ needs: [build, bump-version]
+ if: |
+ always() &&
+ needs.build.result == 'success' &&
+ (needs.bump-version.result == 'success' || needs.bump-version.result == 'skipped') &&
+ ((github.event_name == 'push' && github.ref == 'refs/heads/main') ||
+ github.event_name == 'workflow_dispatch')
+ permissions:
+ contents: read
+ id-token: write # Reserved for future NuGet OIDC trusted publishing
steps:
- - name: Download Package Artifacts
+ - name: Download package artifacts
uses: actions/download-artifact@v4
with:
name: caeriusnet-packages
path: ./packages
- - name: Publish CaeriusNet to NuGet
+ - name: Setup .NET ${{ env.DOTNET_VERSION }}
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Push .nupkg to NuGet.org
run: |
dotnet nuget push ./packages/*.nupkg \
- --api-key ${{ secrets.NUGET_API_KEY }} \
+ --api-key "${{ secrets.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
+
+ - name: Push .snupkg to NuGet.org
+ run: |
dotnet nuget push ./packages/*.snupkg \
- --api-key ${{ secrets.NUGET_API_KEY }} \
+ --api-key "${{ secrets.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
- --skip-duplicate || true
\ No newline at end of file
+ --skip-duplicate || true
+
+ # ── 4b. Publish → GitHub Packages ────────────────────────────────────────────
+ # Parallel to publish-nuget. Uses GITHUB_TOKEN (no extra secret needed).
+ publish-github:
+ name: "Publish \u2192 GitHub Packages"
+ runs-on: ubuntu-latest
+ needs: [build, bump-version]
+ if: |
+ always() &&
+ needs.build.result == 'success' &&
+ (needs.bump-version.result == 'success' || needs.bump-version.result == 'skipped') &&
+ ((github.event_name == 'push' && github.ref == 'refs/heads/main') ||
+ github.event_name == 'workflow_dispatch')
+ permissions:
+ packages: write
+ contents: read
+ steps:
+ - name: Download package artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: caeriusnet-packages
+ path: ./packages
+
+ - name: Setup .NET ${{ env.DOTNET_VERSION }}
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Push to GitHub Packages
+ run: |
+ dotnet nuget push ./packages/*.nupkg \
+ --api-key "${{ secrets.GITHUB_TOKEN }}" \
+ --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \
+ --skip-duplicate
+ dotnet nuget push ./packages/*.snupkg \
+ --api-key "${{ secrets.GITHUB_TOKEN }}" \
+ --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \
+ --skip-duplicate || true
+
+ # ── 5. Create GitHub Release ─────────────────────────────────────────────────
+ # Runs after both publish jobs. Creates a versioned release and attaches the
+ # .nupkg/.snupkg files as release assets. Skips gracefully if the release exists.
+ github-release:
+ name: Create GitHub Release
+ runs-on: ubuntu-latest
+ needs: [publish-nuget, publish-github, bump-version]
+ if: |
+ always() &&
+ needs.publish-nuget.result == 'success' &&
+ (needs.bump-version.result == 'success' || needs.bump-version.result == 'skipped') &&
+ ((github.event_name == 'push' && github.ref == 'refs/heads/main') ||
+ github.event_name == 'workflow_dispatch')
+ permissions:
+ contents: write
+ steps:
+ - name: Download package artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: caeriusnet-packages
+ path: ./packages
+
+ - name: Extract version from package
+ id: version
+ run: |
+ NUPKG=$(ls ./packages/CaeriusNet.*.nupkg | grep -v '\.symbols\.' | head -1)
+ VERSION=$(basename "$NUPKG" .nupkg | sed 's/^CaeriusNet\.//')
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
+ echo "📦 Package version: ${VERSION}"
+
+ - name: Create GitHub Release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ TAG="${{ steps.version.outputs.tag }}"
+ VERSION="${{ steps.version.outputs.version }}"
+
+ if gh release view "${TAG}" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
+ echo "ℹ️ Release ${TAG} already exists — skipping."
+ else
+ ASSETS=$(ls ./packages/*.nupkg ./packages/*.snupkg 2>/dev/null | tr '\n' ' ')
+ # shellcheck disable=SC2086
+ gh release create "${TAG}" \
+ --title "CaeriusNet ${VERSION}" \
+ --generate-notes \
+ --repo "$GITHUB_REPOSITORY" \
+ ${ASSETS}
+ echo "✅ Release ${TAG} created successfully."
+ fi
\ No newline at end of file