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