mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-15 05:39:54 +00:00
ci: publish the README badges to a badges branch with GITHUB_TOKEN (#2202)
Fixes #2136. ## Problem The README badges are stale: `e2e-tests` shows 821 passed (1161 when #2136 was filed), both Go coverage badges have not changed since 2026-03-30 and frontend coverage since June. There are two causes. 1. **The PAT.** The `publish` job wrote the badge JSON to `master` through the Contents API with `BADGE_PUSH_TOKEN`, a PAT that could push past the branch protection on `master`. That token returns 401: run 38360160932 on 2026-10-13 logs `gh: Bad credentials (HTTP 401)` for every badge. The step is `continue-on-error` and only echoed `Failed to update`, so every run stayed green. 2. **Old badges inside `e2e-badges`.** `.badges/` is tracked on `master`, so every e2e shard checkout and the aggregate checkout already hold the old files. The shard upload and the aggregate `cp` shipped them in `e2e-badges`, and `publish` downloaded `e2e-badges` after `go-badges`, so the old values overwrote the fresh ones. In run 38407143126, `go-badges` holds 85.8% and 77.1%, while `e2e-badges` holds 85.4%, 70.2% and frontend 36.64%, the same as `master`'s `.badges/`. Fixing only the token would have left three of the four README badges frozen. ## Change 1. `5f430966` `ci:` the `publish` job pushes the badges to an unprotected `badges` branch with its own `GITHUB_TOKEN`. - Job-level `permissions: contents: write`. No other job gains a permission. The checkout uses `persist-credentials: false`, since the push passes its own token. - The branch is a single orphan commit, force-pushed, so it does not grow. Master push runs are already serialised by the workflow `concurrency` group. - Each run starts from the branch as it is and overwrites only the badges it downloaded, so a missing artifact keeps those badges at their previous value. The first time, when `git ls-remote --exit-code` reports the branch absent (exit 2), it starts from `master`'s `.badges/`. - No downloaded artifacts, or any other `ls-remote`, fetch, extract, commit or push failure, ends in a `::warning::` in the run summary and leaves the branch alone. The step stays `continue-on-error`, so a badge problem never turns the pipeline red. - `go-badges` is downloaded after `e2e-badges`, so its files win. 2. `2134c1e5` `docs:` the four README badge URLs read `.../CoreScope/badges/<name>.json` instead of `.../master/.badges/<name>.json`. 3. `47e57482` `ci:` the frontend-coverage step in the e2e shards and the aggregate badge step start from an empty `.badges/`, so `e2e-badges` carries only what the run produced. This also stops shard 3's old `frontend-coverage.json` from replacing the one shard 2 generates. No PAT is involved any more, so nothing expires. ## Testing - The publish step script, taken from the YAML, run against a local bare repository standing in for `origin`: | Run | Downloaded | Result on `badges` | |---|---|---| | 1, branch absent | a, b | a, b new; c from `.badges/`; one commit | | 2 | c only | a, b kept from run 1; c new; still one commit | | 3 | none | `::warning::`, branch unchanged, step exit 0 | | 4, `origin` unreachable for the read | a | `::warning::`, branch unchanged, step exit 0 | | 5, push to a missing remote | a | `::warning::`, branch unchanged, step exit 0 | - The artifacts of run 38407143126 land flat in the download path, so `badges-new/*.json` matches them. - `shellcheck` at style level on the step script: no findings. - `actionlint` 1.7.7 (shellcheck off): one finding, the self-hosted runner label `meshcore-runner-2`, which `master` reports as well. **Not tested:** the e2e change and the push with a real `GITHUB_TOKEN`. Both only run on a push to `master`, so the first master run after merge is the real test. The `badges` branch already exists (`80fda6c0`, a copy of `master`'s `.badges/`), so the README badges keep working from the merge on, with the old values until that run. After it, I will check the `badges` branch contents against that run's go and frontend numbers, the step log and the rendered README. ## Not in this PR - Removing the now unused `.badges/` from `master`: the first run seeds the branch from it, so that goes in a follow-up once the branch exists. - Deleting the `BADGE_PUSH_TOKEN` secret: that needs a repository admin. - `frontend-tests.json`: no CI step produces it and no README badge uses it. It is carried over to the branch unchanged. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b8e571c9c5
commit
59708ee72e
@@ -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: |
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# CoreScope
|
||||
|
||||
[](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml)
|
||||
[](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml)
|
||||
[](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml)
|
||||
[](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml)
|
||||
[](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml)
|
||||
[](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml)
|
||||
[](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml)
|
||||
[](https://github.com/Kpa-clawbot/CoreScope/actions/workflows/deploy.yml)
|
||||
[](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.
|
||||
|
||||
Reference in New Issue
Block a user