diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c8534085..04760e1f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -730,7 +730,10 @@ jobs: - name: Generate frontend coverage badge if: success() run: | - mkdir -p .badges + # .badges/ is tracked on master, so the checkout already holds the + # old badge files; the shard upload would ship them as if this run + # had produced them (#2136). Start empty. + rm -rf .badges && mkdir .badges if [ -f .nyc_output/frontend-coverage.json ] || [ -f .nyc_output/e2e-coverage.json ]; then npx nyc report --reporter=text-summary --reporter=text 2>&1 | tee fe-report.txt FE_COVERAGE=$(grep 'Statements' fe-report.txt | head -1 | grep -oP '[\d.]+(?=%)' || echo "0") @@ -800,7 +803,9 @@ jobs: ls shards [ "$(ls -d shards/e2e-shard-* | wc -l)" -eq 3 ] || { echo "::error::expected results from 3 shards"; exit 1; } cat shards/e2e-shard-*/e2e-output.txt > e2e-output.txt - mkdir -p .badges + # Start empty, as in the shards: the checkout holds master's old + # .badges/ files, which would otherwise go out in e2e-badges (#2136). + rm -rf .badges && mkdir .badges cp shards/e2e-shard-*/.badges/*.json .badges/ 2>/dev/null || true # Aggregate per-suite PASS/FAIL across every test-*-e2e.js summary. # The previous regex (grep -oP '[0-9]+(?=/)' | tail -1) caught a @@ -1225,54 +1230,78 @@ jobs: # jobs, so ordering is unchanged. needs: [build-and-publish] runs-on: ubuntu-latest + # The badges go to their own `badges` branch (#2136). master is protected, + # so writing there took a PAT (BADGE_PUSH_TOKEN), which stopped working. + # The badges branch is unprotected, so the job's own GITHUB_TOKEN can + # write it. + permissions: + contents: write steps: - name: Checkout code uses: actions/checkout@v5 + with: + # The push below passes its own token; later steps need none. + persist-credentials: false + + # go-badges second, so its files win if e2e-badges ever carries go + # badges again. + - name: Download E2E badges + continue-on-error: true + uses: actions/download-artifact@v6 + with: + name: e2e-badges + path: badges-new/ - name: Download Go coverage badges continue-on-error: true uses: actions/download-artifact@v6 with: name: go-badges - path: .badges/ + path: badges-new/ - - name: Download E2E badges - continue-on-error: true - uses: actions/download-artifact@v6 - with: - name: e2e-badges - path: .badges/ - - - name: Publish coverage badges to repo + - name: Publish coverage badges to the badges branch continue-on-error: true env: - GH_TOKEN: ${{ secrets.BADGE_PUSH_TOKEN }} + GH_TOKEN: ${{ github.token }} run: | - # GITHUB_TOKEN cannot push to protected branches (required status checks). - # Use admin PAT (BADGE_PUSH_TOKEN) via GitHub Contents API instead. - for badge in .badges/*.json; do - FILENAME=$(basename "$badge") - FILEPATH=".badges/$FILENAME" - CONTENT=$(base64 -w0 "$badge") - CURRENT_SHA=$(gh api "repos/${{ github.repository }}/contents/$FILEPATH" --jq '.sha' 2>/dev/null || echo "") - if [ -n "$CURRENT_SHA" ]; then - gh api "repos/${{ github.repository }}/contents/$FILEPATH" \ - -X PUT \ - -f message="ci: update $FILENAME [skip ci]" \ - -f content="$CONTENT" \ - -f sha="$CURRENT_SHA" \ - -f branch="master" \ - --silent 2>&1 || echo "Failed to update $FILENAME" - else - gh api "repos/${{ github.repository }}/contents/$FILEPATH" \ - -X PUT \ - -f message="ci: update $FILENAME [skip ci]" \ - -f content="$CONTENT" \ - -f branch="master" \ - --silent 2>&1 || echo "Failed to create $FILENAME" - fi - done - echo "Badge publish complete" + # Start from what the badges branch holds now (master's .badges/ the + # first time) and overwrite only the badges this run produced, so a + # missing artifact never removes a badge. The branch is one orphan + # commit, force-pushed, so it never grows. + # Every step checks its own result: bash ignores set -e inside a + # function whose status is tested, as the `if` below does. + publish() { + ls badges-new/*.json >/dev/null 2>&1 || { echo "no badge artifacts were downloaded"; return 1; } + out=$(mktemp -d) || return 1 + # Exit 2 means the branch does not exist yet; any other failure + # must not be taken for that, or the badges this run did not + # produce would be reset. + git ls-remote --exit-code --heads origin badges >/dev/null + case $? in + 0) + git fetch -q --depth=1 origin badges || return 1 + git archive FETCH_HEAD | tar -x -C "$out" || return 1 + ;; + 2) + if ls .badges/*.json >/dev/null 2>&1; then + cp .badges/*.json "$out"/ || return 1 + fi + ;; + *) return 1 ;; + esac + cp badges-new/*.json "$out"/ || return 1 + cd "$out" || return 1 + git init -q -b badges || return 1 + git add -- *.json || return 1 + git -c user.name="github-actions[bot]" \ + -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \ + commit -q -m "ci: badges for ${GITHUB_SHA::8}" || return 1 + git push -q --force "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:badges || return 1 + ls -- *.json + } + if ! (publish); then + echo "::warning title=Badges not published::Publishing to the badges branch failed (see the step log); the README badges keep their previous values." + fi - name: Post deployment summary run: | diff --git a/README.md b/README.md index 7345d3d0..074aa0dc 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # CoreScope -[![Go Server Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Kpa-clawbot/CoreScope/master/.badges/go-server-coverage.json)](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml) -[![Go Ingestor Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Kpa-clawbot/CoreScope/master/.badges/go-ingestor-coverage.json)](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml) -[![E2E Tests](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Kpa-clawbot/CoreScope/master/.badges/e2e-tests.json)](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml) -[![Frontend Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Kpa-clawbot/CoreScope/master/.badges/frontend-coverage.json)](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml) +[![Go Server Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Kpa-clawbot/CoreScope/badges/go-server-coverage.json)](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml) +[![Go Ingestor Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Kpa-clawbot/CoreScope/badges/go-ingestor-coverage.json)](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml) +[![E2E Tests](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Kpa-clawbot/CoreScope/badges/e2e-tests.json)](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml) +[![Frontend Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Kpa-clawbot/CoreScope/badges/frontend-coverage.json)](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml) [![Deploy](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml/badge.svg)](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml) > High-performance mesh network analyzer powered by Go. Sub-millisecond packet queries, ~300 MB memory for 56K+ packets, real-time WebSocket broadcast, full channel decryption.