build(deps): bump tar from 7.5.9 to removed in the npm_and_yarn group across 1 directory #951
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Test | |
| permissions: | |
| contents: read | |
| statuses: write | |
| checks: write | |
| env: | |
| COVERAGE_MIN_PERCENT: "95.0" | |
| on: | |
| push: | |
| branches-ignore: | |
| - main | |
| pull_request: | |
| branches: [ main, develop ] | |
| workflow_dispatch: | |
| # Relay required check statuses for release commits (chore(release): ... [skip ci]). | |
| # Those commits skip the push trigger, but the Semantic Release workflow already built | |
| # and tested the code. Once that workflow finishes, this trigger fires and the | |
| # relay-release-status job posts the required check runs for the release commit SHA. | |
| workflow_run: | |
| workflows: ["Semantic Release"] | |
| types: [completed] | |
| branches: [main, develop] | |
| jobs: | |
| build: | |
| if: github.event_name != 'workflow_run' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| global-json-file: global.json | |
| - name: Restore dependencies | |
| run: dotnet restore | |
| - name: Build | |
| env: | |
| CI: true | |
| run: dotnet build --configuration Release --no-restore | |
| - name: Test | |
| run: dotnet test --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage" | |
| - name: Enforce coverage threshold | |
| id: coverage_gate | |
| shell: bash | |
| run: | | |
| mapfile -t coverage_files < <(find . -type f -name 'coverage.cobertura.xml' -print | sort) | |
| if (( ${#coverage_files[@]} == 0 )); then | |
| echo "No coverage.cobertura.xml found." | |
| exit 1 | |
| fi | |
| coverage_files_csv="$(IFS=,; echo "${coverage_files[*]}")" | |
| echo "coverage_files=${coverage_files_csv}" >> "$GITHUB_OUTPUT" | |
| echo "Found ${#coverage_files[@]} coverage report(s)." | |
| total_lines_covered=0 | |
| total_lines_valid=0 | |
| for coverage_file in "${coverage_files[@]}"; do | |
| lines_covered="$(awk ' | |
| /<coverage / { | |
| if (match($0, /lines-covered="[0-9]+"/)) { | |
| value = substr($0, RSTART, RLENGTH) | |
| sub(/lines-covered="/, "", value) | |
| sub(/"$/, "", value) | |
| print value | |
| exit | |
| } | |
| } | |
| ' "$coverage_file")" | |
| lines_valid="$(awk ' | |
| /<coverage / { | |
| if (match($0, /lines-valid="[0-9]+"/)) { | |
| value = substr($0, RSTART, RLENGTH) | |
| sub(/lines-valid="/, "", value) | |
| sub(/"$/, "", value) | |
| print value | |
| exit | |
| } | |
| } | |
| ' "$coverage_file")" | |
| if [[ -z "$lines_covered" || -z "$lines_valid" ]]; then | |
| echo "Could not read lines-covered/lines-valid from $coverage_file" | |
| exit 1 | |
| fi | |
| total_lines_covered=$((total_lines_covered + lines_covered)) | |
| total_lines_valid=$((total_lines_valid + lines_valid)) | |
| done | |
| if (( total_lines_valid == 0 )); then | |
| echo "Could not calculate coverage: lines-valid sum is zero." | |
| exit 1 | |
| fi | |
| coverage_percent="$(awk "BEGIN { printf \"%.2f\", ($total_lines_covered / $total_lines_valid) * 100 }")" | |
| echo "Line coverage: ${coverage_percent}% (required: >= ${COVERAGE_MIN_PERCENT}%)" | |
| echo "coverage_percent=${coverage_percent}" >> "$GITHUB_OUTPUT" | |
| awk "BEGIN { exit !($coverage_percent >= $COVERAGE_MIN_PERCENT) }" || { | |
| echo "Coverage gate failed." | |
| exit 1 | |
| } | |
| - name: Upload coverage reports to Codecov | |
| if: always() && steps.coverage_gate.outputs.coverage_files != '' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop') | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ${{ steps.coverage_gate.outputs.coverage_files }} | |
| disable_search: true | |
| override_branch: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }} | |
| override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | |
| override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }} | |
| fail_ci_if_error: true | |
| - name: Post codecov/patch status | |
| if: always() | |
| continue-on-error: true | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| COMMIT_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | |
| run: | | |
| outcome="${{ steps.coverage_gate.outcome }}" | |
| if [[ "$outcome" == "success" ]]; then | |
| state="success" | |
| description="Coverage ${{ steps.coverage_gate.outputs.coverage_percent }}% >= ${COVERAGE_MIN_PERCENT}%" | |
| elif [[ "$outcome" == "failure" ]]; then | |
| state="failure" | |
| description="Coverage gate failed (< ${COVERAGE_MIN_PERCENT}%)" | |
| else | |
| echo "Coverage gate was skipped (build/test failed earlier); not posting status." | |
| exit 0 | |
| fi | |
| # Wait for the Codecov webhook to post its own codecov/patch status | |
| # before we post ours, so the GitHub Actions status is always last | |
| # (required by the develop branch ruleset integration_id constraint). | |
| for i in $(seq 1 24); do | |
| codecov_bot_status=$(gh api "repos/${{ github.repository }}/commits/${COMMIT_SHA}/statuses" \ | |
| --jq '[.[] | select(.context == "codecov/patch" and (.creator.login == "codecov[bot]"))][0].state // empty' 2>/dev/null || true) | |
| if [[ -n "$codecov_bot_status" ]]; then | |
| echo "Codecov webhook status detected (${codecov_bot_status}) after ~$((i * 10))s" | |
| break | |
| fi | |
| echo "Waiting for Codecov webhook... (attempt $i/24)" | |
| sleep 10 | |
| done | |
| gh api "repos/${{ github.repository }}/statuses/${COMMIT_SHA}" \ | |
| --method POST \ | |
| -f state="${state}" \ | |
| -f context="codecov/patch" \ | |
| -f description="${description}" \ | |
| -f target_url="https://app.codecov.io/github/${{ github.repository }}" | |
| # Relays required check statuses for release commits that carry [skip ci]. | |
| # The Semantic Release workflow already built and tested the code; this job simply | |
| # forwards its outcome to the release commit SHA so PR required checks are satisfied. | |
| # | |
| # Why resolve the branch HEAD instead of using head_sha: | |
| # github.event.workflow_run.head_sha is the commit that *triggered* the Semantic | |
| # Release workflow. During that run, semantic-release creates and pushes a new | |
| # "chore(release): … [skip ci]" commit. By the time workflow_run fires, the branch | |
| # HEAD is that new release commit — which is the SHA that actually needs the required | |
| # checks. If the branch HEAD equals head_sha, semantic-release did not publish a | |
| # release this run and there is nothing to relay. | |
| relay-release-status: | |
| if: github.event_name == 'workflow_run' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Resolve release commit SHA | |
| id: resolve_sha | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| branch="${{ github.event.workflow_run.head_branch }}" | |
| trigger_sha="${{ github.event.workflow_run.head_sha }}" | |
| # Fetch the current HEAD of the branch — this will be the [skip ci] release | |
| # commit if semantic-release published a new version during the workflow run. | |
| current_sha=$(gh api "repos/${{ github.repository }}/git/refs/heads/${branch}" --jq '.object.sha') | |
| if [[ "$current_sha" == "$trigger_sha" ]]; then | |
| echo "Branch HEAD unchanged — semantic-release did not publish a release. Nothing to relay." | |
| echo "release_sha=" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Release commit detected: ${current_sha} (trigger was ${trigger_sha})" | |
| echo "release_sha=${current_sha}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Post build check run for release commit | |
| if: steps.resolve_sha.outputs.release_sha != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| sha="${{ steps.resolve_sha.outputs.release_sha }}" | |
| conclusion="${{ github.event.workflow_run.conclusion }}" | |
| if [[ "$conclusion" == "success" ]]; then | |
| check_conclusion="success" | |
| summary="Build and tests passed in Semantic Release workflow." | |
| else | |
| check_conclusion="failure" | |
| summary="Semantic Release workflow did not succeed (conclusion: ${conclusion})." | |
| fi | |
| gh api repos/${{ github.repository }}/check-runs \ | |
| --method POST \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -f name="build" \ | |
| -f head_sha="$sha" \ | |
| -f status="completed" \ | |
| -f conclusion="$check_conclusion" \ | |
| -f "output[title]=Build (relayed from Semantic Release)" \ | |
| -f "output[summary]=$summary" | |
| - name: Post codecov/patch status for release commit | |
| if: always() && steps.resolve_sha.outputs.release_sha != '' | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| sha="${{ steps.resolve_sha.outputs.release_sha }}" | |
| conclusion="${{ github.event.workflow_run.conclusion }}" | |
| if [[ "$conclusion" == "success" ]]; then | |
| state="success" | |
| description="Release commit — build verified by Semantic Release workflow" | |
| else | |
| state="failure" | |
| description="Semantic Release workflow did not succeed" | |
| fi | |
| gh api "repos/${{ github.repository }}/statuses/${sha}" \ | |
| --method POST \ | |
| -f state="${state}" \ | |
| -f context="codecov/patch" \ | |
| -f description="${description}" \ | |
| -f target_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" |