diff --git a/.github/workflows/refresh-stats.yml b/.github/workflows/refresh-stats.yml new file mode 100644 index 00000000..1e270237 --- /dev/null +++ b/.github/workflows/refresh-stats.yml @@ -0,0 +1,68 @@ +name: Refresh site stats + +# The landing page's "Already downloaded by N people" line and the star counts +# are baked into docs/stats.json at generate time, so they freeze the moment +# someone stops running scripts/site-release.py --stats by hand. (They sat at +# 13,091 downloads for nine days while the real total passed 23,000.) This +# refreshes them daily from the GitHub API โ€” the same per-asset download sum +# the README's shields.io badge renders โ€” and redeploys when they move. +# +# Scheduled runs only fire from the default branch once this is merged. Before +# then, test on the branch: gh workflow run "Refresh site stats" --ref + +on: + schedule: + - cron: "41 7 * * *" # daily 07:41 UTC, clear of the upstream watcher + workflow_dispatch: + +permissions: + contents: write # commit the refreshed counts + actions: write # dispatch the site deploy (see the last step) + +concurrency: + group: site-stats + cancel-in-progress: false + +jobs: + refresh: + runs-on: ubuntu-latest + if: github.repository == 'caezium/Burrow' + steps: + # Pinned to a SHA, not a tag: this job holds a token that can write to + # the default branch, so a moved tag must not be able to run new code. + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Refresh counts from the GitHub API + env: + GH_TOKEN: ${{ github.token }} + # --stats rewrites docs/stats.json, then regenerates every page that + # prints a count. If an unrelated generated file is already stale in + # main, its regeneration rides along in this commit; that is a drift + # signal worth reading, not a failure. + run: python3 scripts/site-release.py --stats + + - name: Commit when the numbers moved + id: commit + run: | + # --porcelain, not `git diff --quiet`: a new blog page would be + # untracked, which a diff of tracked files reports as no change. + if [ -z "$(git status --porcelain)" ]; then + echo "::notice::Counts unchanged โ€” nothing to commit." + echo "changed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "site: refresh download and star counts" + git push + echo "changed=true" >> "$GITHUB_OUTPUT" + + # A push made with GITHUB_TOKEN deliberately does not start other + # workflows, so deploy-site.yml would never see this commit. Dispatching + # it explicitly is the documented exception to that rule. + - name: Deploy the refreshed site + if: steps.commit.outputs.changed == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: gh workflow run deploy-site.yml --ref ${{ github.ref_name }} diff --git a/docs/blog/choosing-a-mac-cleanup-tool.html b/docs/blog/choosing-a-mac-cleanup-tool.html index 501098ac..795f5e42 100644 --- a/docs/blog/choosing-a-mac-cleanup-tool.html +++ b/docs/blog/choosing-a-mac-cleanup-tool.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/find-duplicate-files-on-a-mac.html b/docs/blog/find-duplicate-files-on-a-mac.html index ba936cf0..aafcae78 100644 --- a/docs/blog/find-duplicate-files-on-a-mac.html +++ b/docs/blog/find-duplicate-files-on-a-mac.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/give-claude-code-access-to-your-macs-system-state.html b/docs/blog/give-claude-code-access-to-your-macs-system-state.html index 23e60f18..ecbb4521 100644 --- a/docs/blog/give-claude-code-access-to-your-macs-system-state.html +++ b/docs/blog/give-claude-code-access-to-your-macs-system-state.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/index.html b/docs/blog/index.html index f3b4392a..a80fba65 100644 --- a/docs/blog/index.html +++ b/docs/blog/index.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/leftover-files-after-uninstalling-mac-apps.html b/docs/blog/leftover-files-after-uninstalling-mac-apps.html index 9a3daa7e..d03d4940 100644 --- a/docs/blog/leftover-files-after-uninstalling-mac-apps.html +++ b/docs/blog/leftover-files-after-uninstalling-mac-apps.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/mac-says-disk-is-full-but-it-isnt.html b/docs/blog/mac-says-disk-is-full-but-it-isnt.html index 1aec6c7d..c7bd061c 100644 --- a/docs/blog/mac-says-disk-is-full-but-it-isnt.html +++ b/docs/blog/mac-says-disk-is-full-but-it-isnt.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/node-modules-eating-your-disk.html b/docs/blog/node-modules-eating-your-disk.html index a4cf9973..b631d8cc 100644 --- a/docs/blog/node-modules-eating-your-disk.html +++ b/docs/blog/node-modules-eating-your-disk.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/safely-clear-xcode-deriveddata-and-caches.html b/docs/blog/safely-clear-xcode-deriveddata-and-caches.html index cfd6b1d8..02694ec8 100644 --- a/docs/blog/safely-clear-xcode-deriveddata-and-caches.html +++ b/docs/blog/safely-clear-xcode-deriveddata-and-caches.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/what-is-using-port-3000-on-your-mac.html b/docs/blog/what-is-using-port-3000-on-your-mac.html index 10b3e433..736e13ce 100644 --- a/docs/blog/what-is-using-port-3000-on-your-mac.html +++ b/docs/blog/what-is-using-port-3000-on-your-mac.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/what-macos-system-data-actually-contains.html b/docs/blog/what-macos-system-data-actually-contains.html index 0bb43d1c..021dc76d 100644 --- a/docs/blog/what-macos-system-data-actually-contains.html +++ b/docs/blog/what-macos-system-data-actually-contains.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/blog/which-process-is-using-your-network-on-macos.html b/docs/blog/which-process-is-using-your-network-on-macos.html index 8f04860a..f8a8d7c8 100644 --- a/docs/blog/which-process-is-using-your-network-on-macos.html +++ b/docs/blog/which-process-is-using-your-network-on-macos.html @@ -117,7 +117,7 @@ Burrow diff --git a/docs/compare.html b/docs/compare.html index e0fbd4d4..f573e4c6 100644 --- a/docs/compare.html +++ b/docs/compare.html @@ -104,7 +104,7 @@ Burrow diff --git a/docs/docs.html b/docs/docs.html index f1e04808..638abba5 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -111,7 +111,7 @@ Burrow diff --git a/docs/index.html b/docs/index.html index 60eea26b..52eee0dc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -320,7 +320,7 @@ Burrow @@ -358,7 +358,7 @@

Everything you'd open six ap macOS 14+ยทWindows 10/11 (beta)

-

Already downloaded by 13,091 people

+

Already downloaded by 24,358 people

Burrow Analyze treemap showing what is using disk space diff --git a/docs/install.html b/docs/install.html index 376f3103..1eb6e581 100644 --- a/docs/install.html +++ b/docs/install.html @@ -99,7 +99,7 @@ Burrow
@@ -141,7 +141,7 @@

Homebrew

Updating an older copy? If the in-app update can't complete, quit Burrow, download the latest macOS ZIP above, and replace Burrow.app in Applications. Your settings and history stay on this Mac.

-

Already downloaded by 13,091 people

+

Already downloaded by 24,358 people

Latest release v0.14.0, a 22 MB universal build. Looking for an older version, the Windows preview notes, or the checksums? Browse all releases.
diff --git a/docs/releases.html b/docs/releases.html index 9413f79f..a68f4f9d 100644 --- a/docs/releases.html +++ b/docs/releases.html @@ -123,7 +123,7 @@ Burrow

diff --git a/docs/roadmap.html b/docs/roadmap.html index a0462028..8ea6b5bb 100644 --- a/docs/roadmap.html +++ b/docs/roadmap.html @@ -104,7 +104,7 @@ Burrow diff --git a/docs/stats.json b/docs/stats.json index f643df5c..cafc0d6a 100644 --- a/docs/stats.json +++ b/docs/stats.json @@ -1,4 +1,4 @@ { - "stars": 1214, - "downloads": 13091 + "stars": 1279, + "downloads": 24358 } diff --git a/scripts/site-release.py b/scripts/site-release.py index 09c03477..5e4c44cb 100644 --- a/scripts/site-release.py +++ b/scripts/site-release.py @@ -23,6 +23,7 @@ import argparse import json +import os import re import sys from datetime import date @@ -684,10 +685,16 @@ def render_readme_roadmap(data): def gh_json(path): """GET one GitHub API path. Raises on any failure; callers decide.""" import urllib.request - req = urllib.request.Request( - f"https://api.github.com/{path}", - headers={"Accept": "application/vnd.github+json", - "User-Agent": "burrow-site-release"}) + headers = {"Accept": "application/vnd.github+json", + "User-Agent": "burrow-site-release"} + # Anonymous API access is 60 requests/hour per source IP, and Actions + # runners share IPs with every other repo on the fleet, so the scheduled + # refresh 403s at random without a token. Locally there is usually no + # token and the anonymous path is plenty for the handful of calls here. + token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") + if token: + headers["Authorization"] = f"Bearer {token}" + req = urllib.request.Request(f"https://api.github.com/{path}", headers=headers) with urllib.request.urlopen(req, timeout=20) as r: return json.loads(r.read().decode("utf-8"))