diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index 1ddb712..af8a236 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -1,11 +1,11 @@
name: Benchmarks
on:
- # Auto-triggered when a GitHub Release is published (merging to main)
+ # Auto-triggered when a GitHub Release is published
release:
types: [published]
- # Manual trigger — restricted to 'production' environment (AriusII approval required)
+ # Manual trigger — restricted to 'Developpement' environment (AriusII approval)
workflow_dispatch:
inputs:
category:
@@ -24,13 +24,16 @@ on:
permissions:
contents: write # needed to commit results back to repo
+concurrency:
+ group: benchmark
+ cancel-in-progress: false
+
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'
- # SQL Server connection for benchmark service container
SA_PASSWORD: 'BenchmarkP@ss2026!'
jobs:
@@ -38,9 +41,8 @@ jobs:
name: Run Benchmarks (${{ github.event.inputs.category || 'all' }})
runs-on: ubuntu-latest
- # environment: production requires manual approval from AriusII for workflow_dispatch.
- # For release events, no approval gate is needed — runs automatically.
- environment: ${{ github.event_name == 'workflow_dispatch' && 'production' || '' }}
+ # Developpement environment for manual dispatch; no gate needed for automated release trigger.
+ environment: ${{ github.event_name == 'workflow_dispatch' && 'Developpement' || '' }}
# SQL Server 2022 service container for SQL benchmarks
services:
@@ -92,19 +94,22 @@ jobs:
echo "⏳ Waiting for SQL Server on port 1433..."
for i in $(seq 1 30); do
if nc -z -w 3 localhost 1433 2>/dev/null; then
- echo "✅ Port 1433 open (attempt $i) — waiting 3s for engine init..."
- sleep 3
+ echo "✅ Port 1433 open (attempt $i) — waiting 5s for engine init..."
+ sleep 5
exit 0
fi
echo " Attempt $i/30 — waiting 5s..."
sleep 5
done
- echo "❌ SQL Server port 1433 not available after 30 attempts (150s). Aborting."
+ echo "❌ SQL Server port 1433 not available after 30 attempts. Aborting."
exit 1
- name: Run Benchmarks
env:
BENCHMARK_SQL_CONNECTION: "Server=localhost,1433;Database=master;User Id=sa;Password=${{ env.SA_PASSWORD }};TrustServerCertificate=True"
+ # Explicit artifact path — read by BenchmarkConfig.cs via env var.
+ # InProcessEmitToolchain guarantees files are written here (no child process CWD ambiguity).
+ BENCHMARK_ARTIFACTS_PATH: ${{ github.workspace }}/BenchmarkDotNet.Artifacts
CI: 'true'
run: |
CATEGORY="${{ github.event.inputs.category || 'all' }}"
@@ -115,50 +120,54 @@ jobs:
--no-build \
-- "${CATEGORY}"
- - name: Diagnose benchmark artifact paths
+ - name: Verify artifact output
if: always()
run: |
- echo "=== CWD ==="
- pwd
- echo "=== BenchmarkDotNet.Artifacts (repo root) ==="
- find . -maxdepth 4 -name "*.json" -path "*/BenchmarkDotNet.Artifacts/*" 2>/dev/null || true
- find . -maxdepth 4 -name "*.md" -path "*/BenchmarkDotNet.Artifacts/*" 2>/dev/null || true
- echo "=== Benchmark/ subdirectory ==="
- ls -la Benchmark/BenchmarkDotNet.Artifacts/results/ 2>/dev/null || echo "⚠️ Directory not found: Benchmark/BenchmarkDotNet.Artifacts/results/"
-
- - name: Extract and copy results to documentation
+ ARTIFACTS="${{ github.workspace }}/BenchmarkDotNet.Artifacts/results"
+ echo "=== Configured artifact path ==="
+ echo "$ARTIFACTS"
+ echo "=== Directory listing ==="
+ ls -la "$ARTIFACTS" 2>/dev/null || echo "⚠️ Directory not found: $ARTIFACTS"
+ echo "=== GitHub-flavoured Markdown reports (*-report-github.md) ==="
+ find "$ARTIFACTS" -name "*-report-github.md" 2>/dev/null || echo " (none found)"
+ echo "=== JSON reports (*-report-full.json) ==="
+ find "$ARTIFACTS" -name "*-report-full.json" 2>/dev/null || echo " (none found)"
+
+ - name: Extract results into documentation
if: always()
run: |
- RESULTS_DIR="Benchmark/BenchmarkDotNet.Artifacts/results"
+ ARTIFACTS="${{ github.workspace }}/BenchmarkDotNet.Artifacts/results"
DOCS_DIR="Documentations/docs/benchmarks/results"
mkdir -p "$DOCS_DIR"
- if [ ! -d "$RESULTS_DIR" ] || [ -z "$(ls -A "$RESULTS_DIR" 2>/dev/null)" ]; then
- echo "⚠️ No artifacts at $RESULTS_DIR — skipping copy."
+ if [ ! -d "$ARTIFACTS" ] || [ -z "$(ls -A "$ARTIFACTS" 2>/dev/null)" ]; then
+ echo "⚠️ No artifacts at $ARTIFACTS — skipping extraction."
exit 0
fi
- echo "📂 Found artifacts:"
- ls -la "$RESULTS_DIR"
+ echo "📂 Artifact files:"
+ ls -la "$ARTIFACTS"
# Copy full JSON reports for data analysis
- cp "$RESULTS_DIR"/*.json "$DOCS_DIR/" 2>/dev/null && echo "✅ JSON files copied." || echo "ℹ️ No JSON files."
+ cp "$ARTIFACTS"/*.json "$DOCS_DIR/" 2>/dev/null && echo "✅ JSON files copied." || echo "ℹ️ No JSON files."
- # Extract table-only lines from GitHub Markdown reports
- # BenchmarkDotNet adds environment headers — awk keeps only '|' rows (the table)
- for md_file in "$RESULTS_DIR"/*-report.md; do
+ # MarkdownExporter.GitHub produces *-report-github.md files.
+ # Extract only table rows (lines starting with '|') to strip BDN environment headers.
+ for md_file in "$ARTIFACTS"/*-report-github.md; do
[ -f "$md_file" ] || continue
- # Strip namespace, keep ClassName: "CaeriusNet.Benchmark.Workshops.Benchs.Mapping.DtoMappingBench" → "DtoMappingBench"
- classname=$(basename "$md_file" "-report.md" | rev | cut -d'.' -f1 | rev)
+ # Strip namespace prefix, keep ClassName only:
+ # e.g. "CaeriusNet.Benchmark.Workshops.Benchs.Mapping.DtoMappingBench-report-github.md"
+ # → "DtoMappingBench"
+ classname=$(basename "$md_file" "-report-github.md" | rev | cut -d'.' -f1 | rev)
awk '/^\|/' "$md_file" > "$DOCS_DIR/${classname}.md"
- echo " ✅ Extracted table: ${classname}.md ($(wc -l < "$DOCS_DIR/${classname}.md") rows)"
+ echo " ✅ ${classname}.md ($(wc -l < "$DOCS_DIR/${classname}.md") rows)"
done
- echo "📋 Results directory:"
+ echo "📋 Documentation results directory:"
ls -la "$DOCS_DIR"
- name: Commit benchmark results to documentation
- # Only auto-commit on GitHub Release events (user-triggered) — never on workflow_dispatch
+ # Only auto-commit on GitHub Release events — never on manual workflow_dispatch
if: always() && github.event_name == 'release'
run: |
git config user.name "github-actions[bot]"
@@ -181,8 +190,8 @@ jobs:
with:
name: benchmark-reports-${{ github.run_number }}
path: |
- Benchmark/BenchmarkDotNet.Artifacts/results/*.json
- Benchmark/BenchmarkDotNet.Artifacts/results/*.md
+ ${{ github.workspace }}/BenchmarkDotNet.Artifacts/results/*-report-github.md
+ ${{ github.workspace }}/BenchmarkDotNet.Artifacts/results/*-report-full.json
Documentations/docs/benchmarks/results/
retention-days: 90
if-no-files-found: warn
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3117f54..63c447a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,6 +13,11 @@ on:
- 'Documentations/**'
- '*.md'
+# Cancel in-progress runs on the same branch/PR to avoid wasting runner minutes.
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
permissions:
contents: read
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 5699530..dd7f8c3 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -2,7 +2,7 @@ name: CodeQL Security Analysis
on:
push:
- branches: [main, 'feature/**']
+ branches: [main]
pull_request:
branches: [main]
schedule:
diff --git a/.github/workflows/github-pages-deploy.yml b/.github/workflows/github-pages-deploy.yml
index 4f0b307..a02eef9 100644
--- a/.github/workflows/github-pages-deploy.yml
+++ b/.github/workflows/github-pages-deploy.yml
@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
diff --git a/.github/workflows/nuget-dotnet.yml b/.github/workflows/nuget-dotnet.yml
index 115f06f..09204d8 100644
--- a/.github/workflows/nuget-dotnet.yml
+++ b/.github/workflows/nuget-dotnet.yml
@@ -1,22 +1,10 @@
-name: Build and Publish CaeriusNet
+name: Dependabot — Version Bump
on:
push:
branches: [main]
- workflow_dispatch:
- inputs:
- version_increment:
- description: 'Version increment type'
- required: false
- default: 'none'
- type: choice
- options:
- - none
- - patch
- - minor
- - major
-# Minimal default permissions — each job declares only what it needs
+# Minimal permissions — only needs to write back the version bump commit.
permissions:
contents: read
@@ -24,21 +12,17 @@ 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.
+ # Bumps the patch version whenever Dependabot merges a dependency-update PR to main.
+ # Publishing is handled exclusively by the manual release.yml workflow.
bump-version:
- name: Bump Version (Dependabot)
+ name: Bump Patch Version (Dependabot only)
runs-on: ubuntu-latest
- if: github.actor == 'dependabot[bot]' && github.event_name == 'push' && github.ref == 'refs/heads/main'
+ if: github.actor == 'dependabot[bot]'
permissions:
contents: write
- outputs:
- new_version: ${{ steps.bump.outputs.new_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -46,253 +30,22 @@ jobs:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- - name: Bump Patch Version
+ - name: Bump patch version
id: bump
run: |
VERSION=$(grep -oP '(?<=)\d+\.\d+\.\d+(?=)' Src/CaeriusNet.csproj)
- PATCH=$(echo $VERSION | cut -d. -f3)
- NEW_PATCH=$((PATCH + 1))
- 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"
+ MAJOR=$(echo "$VERSION" | cut -d. -f1)
+ MINOR=$(echo "$VERSION" | cut -d. -f2)
+ PATCH=$(echo "$VERSION" | cut -d. -f3)
+ NEW="${MAJOR}.${MINOR}.$((PATCH+1))"
+ sed -i "s|${VERSION}|${NEW}|g" Src/CaeriusNet.csproj
+ echo "new_version=$NEW" >> "$GITHUB_OUTPUT"
+ echo "🔖 Bumped: ${VERSION} → ${NEW}"
- - name: Commit and Push Version Bump
+ - name: Commit and push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Src/CaeriusNet.csproj
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
- uses: actions/checkout@v4
-
- - name: Setup .NET ${{ env.DOTNET_VERSION }}
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: ${{ env.DOTNET_VERSION }}
-
- - name: Cache NuGet packages
- uses: actions/cache@v5
- with:
- path: ~/.nuget/packages
- key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.slnx') }}
- restore-keys: nuget-${{ runner.os }}-
-
- - name: Restore
- run: dotnet restore CaeriusNet.slnx
-
- - name: Build
- run: dotnet build CaeriusNet.slnx --configuration Release --no-restore
-
- - name: Test
- run: |
- dotnet test CaeriusNet.slnx \
- --configuration Release \
- --no-restore \
- --no-build \
- --collect:"XPlat Code Coverage" \
- --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, bump-version]
- if: |
- always() &&
- needs.test.result == 'success' &&
- (needs.bump-version.result == 'success' || needs.bump-version.result == 'skipped')
- permissions:
- contents: read
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Setup .NET ${{ env.DOTNET_VERSION }}
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: ${{ env.DOTNET_VERSION }}
-
- - name: Cache NuGet packages
- uses: actions/cache@v5
- with:
- path: ~/.nuget/packages
- key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.slnx') }}
- restore-keys: nuget-${{ runner.os }}-
-
- - name: Restore
- run: dotnet restore Src/CaeriusNet.csproj
-
- - name: Build
- run: dotnet build Src/CaeriusNet.csproj --configuration Release --no-restore
-
- - 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
- uses: actions/upload-artifact@v4
- with:
- name: caeriusnet-packages
- path: ./packages/
- retention-days: 7
-
- # ── 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
- 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
- uses: actions/download-artifact@v8
- 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 .nupkg to NuGet.org
- run: |
- dotnet nuget push ./packages/*.nupkg \
- --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 }}" \
- --source https://api.nuget.org/v3/index.json \
- --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@v8
- 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@v8
- 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
+ git push
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..ff201b9
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,189 @@
+name: Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ version_bump:
+ description: 'Version bump type'
+ required: true
+ type: choice
+ options:
+ - patch
+ - minor
+ - major
+ release_notes:
+ description: 'Release notes (leave empty to auto-generate from commits)'
+ required: false
+ type: string
+
+# Only one release at a time — never cancel an in-progress release.
+concurrency:
+ group: release
+ cancel-in-progress: false
+
+permissions:
+ contents: write
+ packages: write
+ id-token: write
+
+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:
+ release:
+ name: Release → Production
+ runs-on: ubuntu-latest
+ environment: production
+
+ steps:
+ # ── Guard: must be triggered from main ─────────────────────────────────────
+ - name: Validate — main branch only
+ run: |
+ if [ "${{ github.ref }}" != "refs/heads/main" ]; then
+ echo "❌ Release must be triggered from the 'main' branch."
+ echo " Current ref: ${{ github.ref }}"
+ exit 1
+ fi
+ echo "✅ Running on main."
+
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Setup .NET ${{ env.DOTNET_VERSION }}
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v5
+ with:
+ path: ~/.nuget/packages
+ key: nuget-release-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.slnx') }}
+ restore-keys: nuget-release-${{ runner.os }}-
+
+ # ── Bump version in .csproj ─────────────────────────────────────────────────
+ - name: Bump version
+ id: bump
+ run: |
+ CURRENT=$(grep -oP '(?<=)\d+\.\d+\.\d+(?=)' Src/CaeriusNet.csproj)
+ MAJOR=$(echo "$CURRENT" | cut -d. -f1)
+ MINOR=$(echo "$CURRENT" | cut -d. -f2)
+ PATCH=$(echo "$CURRENT" | cut -d. -f3)
+
+ case "${{ github.event.inputs.version_bump }}" in
+ major) NEW="$((MAJOR+1)).0.0" ;;
+ minor) NEW="${MAJOR}.$((MINOR+1)).0" ;;
+ patch) NEW="${MAJOR}.${MINOR}.$((PATCH+1))" ;;
+ esac
+
+ sed -i "s|${CURRENT}|${NEW}|g" Src/CaeriusNet.csproj
+
+ echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
+ echo "new=$NEW" >> "$GITHUB_OUTPUT"
+ echo "tag=v${NEW}" >> "$GITHUB_OUTPUT"
+ echo "🔖 Version bump: ${CURRENT} → ${NEW} (${{ github.event.inputs.version_bump }})"
+
+ # ── Build & Test (gates on the new version) ────────────────────────────────
+ - name: Restore
+ run: dotnet restore CaeriusNet.slnx
+
+ - name: Build (Release)
+ run: |
+ dotnet build CaeriusNet.slnx \
+ --configuration Release \
+ --no-restore \
+ -p:TreatWarningsAsErrors=true \
+ -p:WarningLevel=4
+
+ - name: Test
+ run: |
+ dotnet test CaeriusNet.slnx \
+ --configuration Release \
+ --no-restore \
+ --no-build \
+ --logger "console;verbosity=normal"
+
+ # ── Pack ────────────────────────────────────────────────────────────────────
+ - name: Pack (nupkg + snupkg)
+ run: |
+ dotnet pack Src/CaeriusNet.csproj \
+ --configuration Release \
+ --no-build \
+ --output ./packages \
+ -p:IncludeSymbols=true \
+ -p:SymbolPackageFormat=snupkg
+
+ - name: List packages
+ run: ls -la ./packages/
+
+ # ── Publish → NuGet.org ────────────────────────────────────────────────────
+ - name: Publish → NuGet.org
+ run: |
+ dotnet nuget push ./packages/*.nupkg \
+ --api-key "${{ secrets.NUGET_API_KEY }}" \
+ --source https://api.nuget.org/v3/index.json \
+ --skip-duplicate
+ dotnet nuget push ./packages/*.snupkg \
+ --api-key "${{ secrets.NUGET_API_KEY }}" \
+ --source https://api.nuget.org/v3/index.json \
+ --skip-duplicate || true
+
+ # ── Publish → GitHub Packages ──────────────────────────────────────────────
+ - name: Publish → 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
+
+ # ── Commit version bump back to main ───────────────────────────────────────
+ - name: Commit version bump
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add Src/CaeriusNet.csproj
+ git commit -m "chore: release v${{ steps.bump.outputs.new }} [skip ci]"
+ git push
+ echo "✅ Version bump committed: ${{ steps.bump.outputs.new }}"
+
+ # ── Create GitHub Release (triggers benchmark.yml automatically) ───────────
+ - name: Create GitHub Release
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ TAG="${{ steps.bump.outputs.tag }}"
+ VERSION="${{ steps.bump.outputs.new }}"
+ NOTES="${{ github.event.inputs.release_notes }}"
+ ASSETS=$(ls ./packages/*.nupkg ./packages/*.snupkg 2>/dev/null | tr '\n' ' ')
+
+ if gh release view "${TAG}" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
+ echo "ℹ️ Release ${TAG} already exists — skipping."
+ else
+ if [ -n "$NOTES" ]; then
+ # shellcheck disable=SC2086
+ gh release create "${TAG}" \
+ --title "CaeriusNet ${VERSION}" \
+ --notes "${NOTES}" \
+ --repo "$GITHUB_REPOSITORY" \
+ ${ASSETS}
+ else
+ # shellcheck disable=SC2086
+ gh release create "${TAG}" \
+ --title "CaeriusNet ${VERSION}" \
+ --generate-notes \
+ --repo "$GITHUB_REPOSITORY" \
+ ${ASSETS}
+ fi
+ echo "✅ Release ${TAG} created — benchmark workflow will trigger automatically."
+ fi
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
index 1771cfc..de454fb 100644
--- a/.github/workflows/security.yml
+++ b/.github/workflows/security.yml
@@ -4,7 +4,7 @@ on:
pull_request:
branches: [main]
push:
- branches: [main, 'feature/**']
+ branches: [main]
schedule:
- cron: '0 4 * * 1' # Monday 04:00 UTC
diff --git a/Benchmark/Usings.cs b/Benchmark/Usings.cs
index 63a5434..a0a9638 100644
--- a/Benchmark/Usings.cs
+++ b/Benchmark/Usings.cs
@@ -11,6 +11,7 @@
global using BenchmarkDotNet.Exporters.Json;
global using BenchmarkDotNet.Jobs;
global using BenchmarkDotNet.Running;
+global using BenchmarkDotNet.Toolchains.InProcess.Emit;
global using Bogus;
global using CaeriusNet.Builders;
global using CaeriusNet.Mappers;
diff --git a/Benchmark/Workshops/BenchmarkConfig.cs b/Benchmark/Workshops/BenchmarkConfig.cs
index d0bcdab..1b2786d 100644
--- a/Benchmark/Workshops/BenchmarkConfig.cs
+++ b/Benchmark/Workshops/BenchmarkConfig.cs
@@ -2,8 +2,8 @@
///
/// BenchmarkDotNet configuration.
-/// In CI (env var CI=true): uses Job.Short (reduced warmup/iterations) to avoid timeout.
-/// Locally: uses default config with HTML + JSON exporters.
+/// CI (env CI=true): in-process execution, explicit artifact path, GitHub Markdown + JSON export.
+/// Local: default out-of-process execution, HTML + JSON export.
///
public class BenchmarkConfig : ManualConfig
{
@@ -14,19 +14,33 @@ public BenchmarkConfig()
"true",
StringComparison.OrdinalIgnoreCase);
- AddJob(isCI
- ? Job.Dry.WithWarmupCount(1).WithIterationCount(3)
- : Job.Default);
-
- AddExporter(JsonExporter.Full);
+ // Explicit path avoids non-deterministic CWD issues with child process spawning.
+ // BENCHMARK_ARTIFACTS_PATH is set by the CI workflow; falls back to CWD-relative default locally.
+ var artifactsPath = Environment.GetEnvironmentVariable("BENCHMARK_ARTIFACTS_PATH")
+ ?? Path.Combine(Directory.GetCurrentDirectory(), "BenchmarkDotNet.Artifacts");
+ WithArtifactsPath(artifactsPath);
if (isCI)
- AddExporter(MarkdownExporter.GitHub);
+ {
+ // InProcessEmitToolchain: runs benchmarks in the host process (no child process spawning).
+ // This guarantees artifacts are always written to the configured path above.
+ // WarmupCount=1, IterationCount=3 → real measurements, CI-fast.
+ AddJob(Job.Default
+ .WithToolchain(InProcessEmitToolchain.Instance)
+ .WithWarmupCount(1)
+ .WithIterationCount(3));
+
+ AddExporter(MarkdownExporter.GitHub); // → *-report-github.md (GitHub-flavoured markdown table)
+ AddExporter(JsonExporter.Full); // → *-report-full.json
+ }
else
+ {
+ AddJob(Job.Default);
AddExporter(HtmlExporter.Default);
+ AddExporter(JsonExporter.Full);
+ }
AddDiagnoser(MemoryDiagnoser.Default);
-
WithOptions(ConfigOptions.DisableOptimizationsValidator);
}
}
\ No newline at end of file