From 5e56c14c84588a3f3f444c44ba54dcc27a3b50c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 May 2026 07:03:19 +0000 Subject: [PATCH] chore(deps): bump dotnet/nbgv Bumps [dotnet/nbgv](https://github.com/dotnet/nbgv) from b944774b6878ef950cc14d1a72bf9c0ffafbb839 to 071d632702496c9f570398fbf453c6545c2cf2f0. - [Release notes](https://github.com/dotnet/nbgv/releases) - [Commits](https://github.com/dotnet/nbgv/compare/b944774b6878ef950cc14d1a72bf9c0ffafbb839...071d632702496c9f570398fbf453c6545c2cf2f0) --- updated-dependencies: - dependency-name: dotnet/nbgv dependency-version: 071d632702496c9f570398fbf453c6545c2cf2f0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 726 ++++++++++++++-------------- .github/workflows/pr-validation.yml | 438 ++++++++--------- 2 files changed, 582 insertions(+), 582 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b8eea80..260d1e7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,363 +1,363 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -env: - DOTNET_NOLOGO: true - TEST_RESULTS_DIR: artifacts/test-results - -jobs: - pr-checks: - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Restore - run: dotnet restore JD.AI.slnx - - - name: Build (Release) - run: > - dotnet build JD.AI.slnx - --configuration Release - --no-restore - /p:ContinuousIntegrationBuild=true - - - name: Verify formatting - run: > - dotnet format JD.AI.slnx - --severity warn - --verify-no-changes - - - name: Test with coverage - timeout-minutes: 15 - run: > - dotnet test JD.AI.slnx - --configuration Release - --no-build - --results-directory ${{ env.TEST_RESULTS_DIR }} - --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" - --collect:"XPlat Code Coverage" - --blame-hang-timeout 5m - -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" - - - name: Install ReportGenerator - if: always() - run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 - - - name: Combine coverage reports - if: always() - shell: bash - run: | - REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') - if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then - echo "No coverage files found — skipping report generation." - exit 0 - fi - reportgenerator \ - -reports:"$REPORTS" \ - -targetdir:"coverage-report" \ - -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ - -assemblyfilters:"+JD.AI*;-*Tests*" \ - -filefilters:"-**/*.Tests/*;-**/*Tests*/**" - echo "COVERAGE_SUMMARY<> $GITHUB_ENV - awk '/^[^ ]/ && NR>1 {exit} {print}' coverage-report/Summary.txt >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Upload coverage report - if: always() - uses: actions/upload-artifact@v7 - with: - name: coverage-report - path: coverage-report - - - name: Upload to Codecov - if: always() - continue-on-error: true - uses: codecov/codecov-action@v6 - with: - files: coverage-report/Cobertura.xml - disable_search: true - handle_no_reports_found: true - fail_ci_if_error: false - - - name: Enforce coverage floor - if: always() - shell: bash - env: - MIN_LINE_COVERAGE: "60" - run: | - if [ ! -f coverage-report/Cobertura.xml ]; then - echo "Coverage report missing." - exit 1 - fi - python - <<'PY' - import os - import xml.etree.ElementTree as ET - - min_cov = float(os.environ["MIN_LINE_COVERAGE"]) - rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 - if rate < min_cov: - raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") - print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") - PY - - - name: Add coverage summary to PR - if: always() && github.event_name == 'pull_request' - uses: marocchino/sticky-pull-request-comment@v3 - with: - recreate: true - message: | - ## Code Coverage - ``` - ${{ env.COVERAGE_SUMMARY }} - ``` - - release: - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - permissions: - contents: write - packages: write - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Restore - run: dotnet restore JD.AI.slnx - - - name: Determine version (NBGV) - id: nbgv - uses: dotnet/nbgv@b944774b6878ef950cc14d1a72bf9c0ffafbb839 # node24 (unreleased past v0.5.1; pin SHA until v0.5.2 ships) - with: - setAllVars: true - - - name: Build (Release) - run: > - dotnet build JD.AI.slnx - --configuration Release - --no-restore - /p:ContinuousIntegrationBuild=true - - - name: Test (Release) - timeout-minutes: 15 - run: > - dotnet test JD.AI.slnx - --configuration Release - --no-build - --results-directory ${{ env.TEST_RESULTS_DIR }} - --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" - --collect:"XPlat Code Coverage" - --blame-hang-timeout 5m - -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" - - - name: Install ReportGenerator - if: always() - run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 - - - name: Combine coverage reports - if: always() - shell: bash - run: | - REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') - if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then - echo "No coverage files found — skipping report generation." - exit 0 - fi - reportgenerator \ - -reports:"$REPORTS" \ - -targetdir:"coverage-report" \ - -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ - -assemblyfilters:"+JD.AI*;-*Tests*" \ - -filefilters:"-**/*.Tests/*;-**/*Tests*/**" - - - name: Upload coverage report - if: always() - uses: actions/upload-artifact@v7 - with: - name: coverage-report - path: coverage-report - - - name: Upload to Codecov - if: always() - uses: codecov/codecov-action@v6 - with: - files: coverage-report/Cobertura.xml - token: ${{ secrets.CODECOV_TOKEN }} - disable_search: true - handle_no_reports_found: true - fail_ci_if_error: false - - - name: Enforce coverage floor - if: always() - shell: bash - env: - MIN_LINE_COVERAGE: "60" - run: | - if [ ! -f coverage-report/Cobertura.xml ]; then - echo "Coverage report missing." - exit 1 - fi - python - <<'PY' - import os - import xml.etree.ElementTree as ET - - min_cov = float(os.environ["MIN_LINE_COVERAGE"]) - rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 - if rate < min_cov: - raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") - print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") - PY - - - name: Pack - run: > - dotnet pack JD.AI.slnx - --configuration Release - --no-build - --output ./artifacts - /p:ContinuousIntegrationBuild=true - - - name: Upload packages - uses: actions/upload-artifact@v7 - with: - name: nuget-packages - path: ./artifacts/*.nupkg - - - name: Push to NuGet.org - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} - run: | - if [ -n "$NUGET_API_KEY" ]; then - dotnet nuget push ./artifacts/*.nupkg \ - --api-key "$NUGET_API_KEY" \ - --source https://api.nuget.org/v3/index.json \ - --skip-duplicate - else - echo "Skipping NuGet.org push: API key not set." - fi - - - name: Push to GitHub Packages - run: | - dotnet nuget push "./artifacts/*.nupkg" \ - --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ - --api-key "${{ secrets.GITHUB_TOKEN }}" \ - --skip-duplicate - - - name: Create git tag - shell: bash - run: | - set -euo pipefail - TAG="v${NBGV_NuGetPackageVersion}" - if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists — skipping." - else - git tag "$TAG" - git push origin "$TAG" - echo "Created tag $TAG" - fi - - - name: Create GitHub Release - uses: softprops/action-gh-release@v3 - with: - tag_name: v${{ env.NBGV_NuGetPackageVersion }} - name: Release v${{ env.NBGV_NuGetPackageVersion }} - files: | - ./artifacts/*.nupkg - generate_release_notes: true - outputs: - version: ${{ env.NBGV_NuGetPackageVersion }} - - publish-binaries: - needs: release - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - strategy: - fail-fast: false - matrix: - include: - - rid: win-x64 - os: windows-latest - archive: zip - - rid: win-arm64 - os: windows-latest - archive: zip - - rid: linux-x64 - os: ubuntu-latest - archive: tar.gz - - rid: linux-arm64 - os: ubuntu-latest - archive: tar.gz - - rid: osx-x64 - os: macos-latest - archive: tar.gz - - rid: osx-arm64 - os: macos-latest - archive: tar.gz - runs-on: ${{ matrix.os }} - permissions: - contents: write - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Publish self-contained binary - env: - GITHUB_ACTIONS: "false" - run: > - dotnet publish src/JD.AI/JD.AI.csproj - --configuration Release - --runtime ${{ matrix.rid }} - --self-contained - -p:PublishSingleFile=true - -p:IncludeNativeLibrariesForSelfExtract=true - -p:ContinuousIntegrationBuild=true - --output ./publish - - - name: Archive (zip) - if: matrix.archive == 'zip' - shell: pwsh - run: Compress-Archive -Path ./publish/* -DestinationPath ./jdai-${{ matrix.rid }}.zip - - - name: Archive (tar.gz) - if: matrix.archive == 'tar.gz' - run: tar -czf ./jdai-${{ matrix.rid }}.tar.gz -C ./publish . - - - name: Upload to GitHub Release - uses: softprops/action-gh-release@v3 - with: - tag_name: v${{ needs.release.outputs.version }} - files: | - ./jdai-${{ matrix.rid }}.${{ matrix.archive }} +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +env: + DOTNET_NOLOGO: true + TEST_RESULTS_DIR: artifacts/test-results + +jobs: + pr-checks: + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Restore + run: dotnet restore JD.AI.slnx + + - name: Build (Release) + run: > + dotnet build JD.AI.slnx + --configuration Release + --no-restore + /p:ContinuousIntegrationBuild=true + + - name: Verify formatting + run: > + dotnet format JD.AI.slnx + --severity warn + --verify-no-changes + + - name: Test with coverage + timeout-minutes: 15 + run: > + dotnet test JD.AI.slnx + --configuration Release + --no-build + --results-directory ${{ env.TEST_RESULTS_DIR }} + --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" + --collect:"XPlat Code Coverage" + --blame-hang-timeout 5m + -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" + + - name: Install ReportGenerator + if: always() + run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 + + - name: Combine coverage reports + if: always() + shell: bash + run: | + REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') + if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then + echo "No coverage files found — skipping report generation." + exit 0 + fi + reportgenerator \ + -reports:"$REPORTS" \ + -targetdir:"coverage-report" \ + -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ + -assemblyfilters:"+JD.AI*;-*Tests*" \ + -filefilters:"-**/*.Tests/*;-**/*Tests*/**" + echo "COVERAGE_SUMMARY<> $GITHUB_ENV + awk '/^[^ ]/ && NR>1 {exit} {print}' coverage-report/Summary.txt >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-report + path: coverage-report + + - name: Upload to Codecov + if: always() + continue-on-error: true + uses: codecov/codecov-action@v6 + with: + files: coverage-report/Cobertura.xml + disable_search: true + handle_no_reports_found: true + fail_ci_if_error: false + + - name: Enforce coverage floor + if: always() + shell: bash + env: + MIN_LINE_COVERAGE: "60" + run: | + if [ ! -f coverage-report/Cobertura.xml ]; then + echo "Coverage report missing." + exit 1 + fi + python - <<'PY' + import os + import xml.etree.ElementTree as ET + + min_cov = float(os.environ["MIN_LINE_COVERAGE"]) + rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 + if rate < min_cov: + raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") + print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") + PY + + - name: Add coverage summary to PR + if: always() && github.event_name == 'pull_request' + uses: marocchino/sticky-pull-request-comment@v3 + with: + recreate: true + message: | + ## Code Coverage + ``` + ${{ env.COVERAGE_SUMMARY }} + ``` + + release: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Restore + run: dotnet restore JD.AI.slnx + + - name: Determine version (NBGV) + id: nbgv + uses: dotnet/nbgv@071d632702496c9f570398fbf453c6545c2cf2f0 # node24 (unreleased past v0.5.1; pin SHA until v0.5.2 ships) + with: + setAllVars: true + + - name: Build (Release) + run: > + dotnet build JD.AI.slnx + --configuration Release + --no-restore + /p:ContinuousIntegrationBuild=true + + - name: Test (Release) + timeout-minutes: 15 + run: > + dotnet test JD.AI.slnx + --configuration Release + --no-build + --results-directory ${{ env.TEST_RESULTS_DIR }} + --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" + --collect:"XPlat Code Coverage" + --blame-hang-timeout 5m + -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" + + - name: Install ReportGenerator + if: always() + run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 + + - name: Combine coverage reports + if: always() + shell: bash + run: | + REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') + if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then + echo "No coverage files found — skipping report generation." + exit 0 + fi + reportgenerator \ + -reports:"$REPORTS" \ + -targetdir:"coverage-report" \ + -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ + -assemblyfilters:"+JD.AI*;-*Tests*" \ + -filefilters:"-**/*.Tests/*;-**/*Tests*/**" + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-report + path: coverage-report + + - name: Upload to Codecov + if: always() + uses: codecov/codecov-action@v6 + with: + files: coverage-report/Cobertura.xml + token: ${{ secrets.CODECOV_TOKEN }} + disable_search: true + handle_no_reports_found: true + fail_ci_if_error: false + + - name: Enforce coverage floor + if: always() + shell: bash + env: + MIN_LINE_COVERAGE: "60" + run: | + if [ ! -f coverage-report/Cobertura.xml ]; then + echo "Coverage report missing." + exit 1 + fi + python - <<'PY' + import os + import xml.etree.ElementTree as ET + + min_cov = float(os.environ["MIN_LINE_COVERAGE"]) + rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 + if rate < min_cov: + raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") + print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") + PY + + - name: Pack + run: > + dotnet pack JD.AI.slnx + --configuration Release + --no-build + --output ./artifacts + /p:ContinuousIntegrationBuild=true + + - name: Upload packages + uses: actions/upload-artifact@v7 + with: + name: nuget-packages + path: ./artifacts/*.nupkg + + - name: Push to NuGet.org + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + if [ -n "$NUGET_API_KEY" ]; then + dotnet nuget push ./artifacts/*.nupkg \ + --api-key "$NUGET_API_KEY" \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate + else + echo "Skipping NuGet.org push: API key not set." + fi + + - name: Push to GitHub Packages + run: | + dotnet nuget push "./artifacts/*.nupkg" \ + --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ + --api-key "${{ secrets.GITHUB_TOKEN }}" \ + --skip-duplicate + + - name: Create git tag + shell: bash + run: | + set -euo pipefail + TAG="v${NBGV_NuGetPackageVersion}" + if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists — skipping." + else + git tag "$TAG" + git push origin "$TAG" + echo "Created tag $TAG" + fi + + - name: Create GitHub Release + uses: softprops/action-gh-release@v3 + with: + tag_name: v${{ env.NBGV_NuGetPackageVersion }} + name: Release v${{ env.NBGV_NuGetPackageVersion }} + files: | + ./artifacts/*.nupkg + generate_release_notes: true + outputs: + version: ${{ env.NBGV_NuGetPackageVersion }} + + publish-binaries: + needs: release + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + strategy: + fail-fast: false + matrix: + include: + - rid: win-x64 + os: windows-latest + archive: zip + - rid: win-arm64 + os: windows-latest + archive: zip + - rid: linux-x64 + os: ubuntu-latest + archive: tar.gz + - rid: linux-arm64 + os: ubuntu-latest + archive: tar.gz + - rid: osx-x64 + os: macos-latest + archive: tar.gz + - rid: osx-arm64 + os: macos-latest + archive: tar.gz + runs-on: ${{ matrix.os }} + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Publish self-contained binary + env: + GITHUB_ACTIONS: "false" + run: > + dotnet publish src/JD.AI/JD.AI.csproj + --configuration Release + --runtime ${{ matrix.rid }} + --self-contained + -p:PublishSingleFile=true + -p:IncludeNativeLibrariesForSelfExtract=true + -p:ContinuousIntegrationBuild=true + --output ./publish + + - name: Archive (zip) + if: matrix.archive == 'zip' + shell: pwsh + run: Compress-Archive -Path ./publish/* -DestinationPath ./jdai-${{ matrix.rid }}.zip + + - name: Archive (tar.gz) + if: matrix.archive == 'tar.gz' + run: tar -czf ./jdai-${{ matrix.rid }}.tar.gz -C ./publish . + + - name: Upload to GitHub Release + uses: softprops/action-gh-release@v3 + with: + tag_name: v${{ needs.release.outputs.version }} + files: | + ./jdai-${{ matrix.rid }}.${{ matrix.archive }} diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 24128cad..61d6b5b0 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -1,219 +1,219 @@ -name: PR Validation - -on: - pull_request: - branches: [main] - paths-ignore: - - '**.md' - - 'docs/**' - - '.vscode/**' - - '.editorconfig' - workflow_dispatch: - -env: - DOTNET_NOLOGO: true - TEST_RESULTS_DIR: artifacts/test-results - -permissions: - contents: read - pull-requests: write - checks: write - -jobs: - validate-pr: - name: Validate PR - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Determine version (NBGV) - id: nbgv - uses: dotnet/nbgv@b944774b6878ef950cc14d1a72bf9c0ffafbb839 # node24 (unreleased past v0.5.1; pin SHA until v0.5.2 ships) - with: - setAllVars: true - - - name: Restore - run: dotnet restore JD.AI.slnx - - - name: Build - run: > - dotnet build JD.AI.slnx - --configuration Release - --no-restore - /p:ContinuousIntegrationBuild=true - /p:Deterministic=true - - - name: Verify formatting - run: > - dotnet format JD.AI.slnx - --severity warn - --verify-no-changes - - - name: Run tests with coverage - run: > - dotnet test JD.AI.slnx - --configuration Release - --no-build - --results-directory ${{ env.TEST_RESULTS_DIR }} - --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" - --verbosity normal - --logger trx - --collect:"XPlat Code Coverage" - --blame-hang-timeout 5m - -- - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" - - - name: Install ReportGenerator - if: always() - run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 - - - name: Generate coverage report - if: always() - shell: bash - run: | - REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') - if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then - echo "No coverage files found — skipping report generation." - exit 0 - fi - reportgenerator \ - -reports:"$REPORTS" \ - -targetdir:"coverage-report" \ - -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ - -assemblyfilters:"+JD.AI*;-*Tests*" \ - -filefilters:"-**/*.Tests/*;-**/*Tests*/**" - - - name: Upload coverage report - if: always() - uses: actions/upload-artifact@v7 - with: - name: coverage-report - path: coverage-report - - - name: Enforce coverage floor - if: always() - shell: bash - env: - MIN_LINE_COVERAGE: "60" - run: | - if [ ! -f coverage-report/Cobertura.xml ]; then - echo "Coverage report missing." - exit 1 - fi - python - <<'PY' - import os - import xml.etree.ElementTree as ET - - min_cov = float(os.environ["MIN_LINE_COVERAGE"]) - rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 - if rate < min_cov: - raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") - print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") - PY - - - name: Coverage gate — check changed source files - if: always() && github.event_name == 'pull_request' - shell: bash - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - # Determine if this PR has a coverage-override label - LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "") - if echo "$LABELS" | grep -q "coverage-override"; then - echo "ℹ️ coverage-override label found — skipping coverage gate." - exit 0 - fi - - # Get changed runtime source files (not tests) - CHANGED_SRC=$(git diff --name-only origin/main...HEAD -- 'src/**/*.cs' 2>/dev/null || \ - git diff --name-only HEAD~1...HEAD -- 'src/**/*.cs' 2>/dev/null || echo "") - - if [ -z "$CHANGED_SRC" ]; then - echo "✅ No runtime source files changed — coverage gate passed." - exit 0 - fi - - echo "📁 Changed source files:" - echo "$CHANGED_SRC" - - # Check if test files were also changed - CHANGED_TESTS=$(git diff --name-only origin/main...HEAD -- 'tests/**/*.cs' 2>/dev/null || \ - git diff --name-only HEAD~1...HEAD -- 'tests/**/*.cs' 2>/dev/null || echo "") - - if [ -z "$CHANGED_TESTS" ]; then - echo "" - echo "⚠️ Runtime source files were modified but no test files changed." - echo " Please add or update tests for the changed code, or add the" - echo " 'coverage-override' label to this PR to bypass this check." - echo "" - echo "Changed files requiring tests:" - echo "$CHANGED_SRC" - exit 1 - fi - - echo "✅ Test files updated alongside source changes — coverage gate passed." - echo "Changed test files:" - echo "$CHANGED_TESTS" - - - name: Publish test results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - files: | - ${{ env.TEST_RESULTS_DIR }}/**/*.trx - check_name: Test Results - - - name: Dry-run NuGet packaging - run: | - echo "📦 Performing dry-run of NuGet packaging..." - mkdir -p ./dry-run-packages - dotnet pack JD.AI.slnx \ - --configuration Release \ - --no-build \ - --output ./dry-run-packages \ - /p:ContinuousIntegrationBuild=true - PACKAGE_COUNT=$(ls -1 ./dry-run-packages/*.nupkg 2>/dev/null | wc -l) - if [ "$PACKAGE_COUNT" -eq 0 ]; then - echo "❌ No packages were created" - exit 1 - fi - echo "📦 Packaged files:" - ls -lh ./dry-run-packages/ - - - name: Upload dry-run packages - uses: actions/upload-artifact@v7 - with: - name: dry-run-packages - path: ./dry-run-packages/*.nupkg - retention-days: 7 - - - name: PR Summary - if: always() - run: | - echo "## 🎯 PR Validation Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "✅ **Version**: $NBGV_NuGetPackageVersion" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📦 Packages" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - if ls ./dry-run-packages/*.nupkg 1> /dev/null 2>&1; then - echo "The following packages will be created on merge:" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - ls -1 ./dry-run-packages/*.nupkg | xargs -n1 basename >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - else - echo "⚠️ No packages were created during the dry-run." >> $GITHUB_STEP_SUMMARY - fi +name: PR Validation + +on: + pull_request: + branches: [main] + paths-ignore: + - '**.md' + - 'docs/**' + - '.vscode/**' + - '.editorconfig' + workflow_dispatch: + +env: + DOTNET_NOLOGO: true + TEST_RESULTS_DIR: artifacts/test-results + +permissions: + contents: read + pull-requests: write + checks: write + +jobs: + validate-pr: + name: Validate PR + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Determine version (NBGV) + id: nbgv + uses: dotnet/nbgv@071d632702496c9f570398fbf453c6545c2cf2f0 # node24 (unreleased past v0.5.1; pin SHA until v0.5.2 ships) + with: + setAllVars: true + + - name: Restore + run: dotnet restore JD.AI.slnx + + - name: Build + run: > + dotnet build JD.AI.slnx + --configuration Release + --no-restore + /p:ContinuousIntegrationBuild=true + /p:Deterministic=true + + - name: Verify formatting + run: > + dotnet format JD.AI.slnx + --severity warn + --verify-no-changes + + - name: Run tests with coverage + run: > + dotnet test JD.AI.slnx + --configuration Release + --no-build + --results-directory ${{ env.TEST_RESULTS_DIR }} + --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" + --verbosity normal + --logger trx + --collect:"XPlat Code Coverage" + --blame-hang-timeout 5m + -- + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" + + - name: Install ReportGenerator + if: always() + run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 + + - name: Generate coverage report + if: always() + shell: bash + run: | + REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') + if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then + echo "No coverage files found — skipping report generation." + exit 0 + fi + reportgenerator \ + -reports:"$REPORTS" \ + -targetdir:"coverage-report" \ + -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ + -assemblyfilters:"+JD.AI*;-*Tests*" \ + -filefilters:"-**/*.Tests/*;-**/*Tests*/**" + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-report + path: coverage-report + + - name: Enforce coverage floor + if: always() + shell: bash + env: + MIN_LINE_COVERAGE: "60" + run: | + if [ ! -f coverage-report/Cobertura.xml ]; then + echo "Coverage report missing." + exit 1 + fi + python - <<'PY' + import os + import xml.etree.ElementTree as ET + + min_cov = float(os.environ["MIN_LINE_COVERAGE"]) + rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 + if rate < min_cov: + raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") + print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") + PY + + - name: Coverage gate — check changed source files + if: always() && github.event_name == 'pull_request' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + # Determine if this PR has a coverage-override label + LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "") + if echo "$LABELS" | grep -q "coverage-override"; then + echo "ℹ️ coverage-override label found — skipping coverage gate." + exit 0 + fi + + # Get changed runtime source files (not tests) + CHANGED_SRC=$(git diff --name-only origin/main...HEAD -- 'src/**/*.cs' 2>/dev/null || \ + git diff --name-only HEAD~1...HEAD -- 'src/**/*.cs' 2>/dev/null || echo "") + + if [ -z "$CHANGED_SRC" ]; then + echo "✅ No runtime source files changed — coverage gate passed." + exit 0 + fi + + echo "📁 Changed source files:" + echo "$CHANGED_SRC" + + # Check if test files were also changed + CHANGED_TESTS=$(git diff --name-only origin/main...HEAD -- 'tests/**/*.cs' 2>/dev/null || \ + git diff --name-only HEAD~1...HEAD -- 'tests/**/*.cs' 2>/dev/null || echo "") + + if [ -z "$CHANGED_TESTS" ]; then + echo "" + echo "⚠️ Runtime source files were modified but no test files changed." + echo " Please add or update tests for the changed code, or add the" + echo " 'coverage-override' label to this PR to bypass this check." + echo "" + echo "Changed files requiring tests:" + echo "$CHANGED_SRC" + exit 1 + fi + + echo "✅ Test files updated alongside source changes — coverage gate passed." + echo "Changed test files:" + echo "$CHANGED_TESTS" + + - name: Publish test results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + ${{ env.TEST_RESULTS_DIR }}/**/*.trx + check_name: Test Results + + - name: Dry-run NuGet packaging + run: | + echo "📦 Performing dry-run of NuGet packaging..." + mkdir -p ./dry-run-packages + dotnet pack JD.AI.slnx \ + --configuration Release \ + --no-build \ + --output ./dry-run-packages \ + /p:ContinuousIntegrationBuild=true + PACKAGE_COUNT=$(ls -1 ./dry-run-packages/*.nupkg 2>/dev/null | wc -l) + if [ "$PACKAGE_COUNT" -eq 0 ]; then + echo "❌ No packages were created" + exit 1 + fi + echo "📦 Packaged files:" + ls -lh ./dry-run-packages/ + + - name: Upload dry-run packages + uses: actions/upload-artifact@v7 + with: + name: dry-run-packages + path: ./dry-run-packages/*.nupkg + retention-days: 7 + + - name: PR Summary + if: always() + run: | + echo "## 🎯 PR Validation Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "✅ **Version**: $NBGV_NuGetPackageVersion" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📦 Packages" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if ls ./dry-run-packages/*.nupkg 1> /dev/null 2>&1; then + echo "The following packages will be created on merge:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + ls -1 ./dry-run-packages/*.nupkg | xargs -n1 basename >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + else + echo "⚠️ No packages were created during the dry-run." >> $GITHUB_STEP_SUMMARY + fi