Skip to content

GitHub Clone Count Update Everyday #4

GitHub Clone Count Update Everyday

GitHub Clone Count Update Everyday #4

Workflow file for this run

name: GitHub Clone Count Update Everyday
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
permissions:
# Required to commit CLONE.md file to repository
# Note: contents: write is the minimal permission needed for this workflow
# trunk-ignore(checkov): contents: write is required to commit files to repository
contents: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
fetch-depth: 0
token: ${{ secrets.CLONE_SECRET_TOKEN }}
- name: gh login
run: echo "${{ secrets.CLONE_SECRET_TOKEN }}" | gh auth login --with-token
- name: parse latest clone count
env:
GITHUB_TOKEN: ${{ secrets.CLONE_SECRET_TOKEN }}
run: |
set -euo pipefail
# CLONE_SECRET_TOKEN is required because:
# 1. The traffic/clones endpoint requires "Administration" (read) permissions
# 2. GITHUB_TOKEN typically doesn't have access to traffic data
# 3. A Personal Access Token (PAT) with repo scope is needed
# Using environment variable to avoid exposing secret in process list
# Note: Secret is passed via env var, not command line, for security
# trunk-ignore(trufflehog): Secret properly handled via environment variable, not command line
if ! curl -f --user "${{ github.actor }}:$GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${{ github.repository }}/traffic/clones" \
> clone.json; then
echo "Error: Failed to fetch clone statistics"
exit 1
fi
- name: create gist and download previous count
id: set_id
run: |
set -euo pipefail
if gh secret list | grep -q "GIST_ID"
then
echo "GIST_ID found"
echo "GIST=${{ secrets.GIST_ID }}" >> $GITHUB_OUTPUT
curl -f "https://gist.githubusercontent.com/${{ github.actor }}/${{ secrets.GIST_ID }}/raw/clone.json" > clone_before.json || true
if grep -q '404: Not Found' clone_before.json 2>/dev/null || [ ! -s clone_before.json ]; then
echo "GIST_ID not valid anymore. Creating another gist..."
gist_id=$(gh gist create clone.json | awk -F / '{print $NF}')
if [ -z "$gist_id" ]; then
echo "Error: Failed to create gist"
exit 1
fi
echo "$gist_id" | gh secret set GIST_ID
echo "GIST=$gist_id" >> $GITHUB_OUTPUT
cp clone.json clone_before.json
git rm --ignore-unmatch CLONE.md || true
fi
else
echo "GIST_ID not found. Creating a gist..."
gist_id=$(gh gist create clone.json | awk -F / '{print $NF}')
if [ -z "$gist_id" ]; then
echo "Error: Failed to create gist"
exit 1
fi
echo "$gist_id" | gh secret set GIST_ID
echo "GIST=$gist_id" >> $GITHUB_OUTPUT
cp clone.json clone_before.json
fi
- name: update clone.json
run: |
set -euo pipefail
python3 << 'EOF'
import json
# Read current clone data from GitHub API
with open('clone.json', 'r') as fh:
now = json.load(fh)
# Read previous accumulated data
with open('clone_before.json', 'r') as fh:
before = json.load(fh)
# Create timestamp index for efficient lookup
timestamps = {before['clones'][i]['timestamp']: i for i in range(len(before['clones']))}
# Start with previous data and merge in new data
latest = dict(before)
for i in range(len(now['clones'])):
timestamp = now['clones'][i]['timestamp']
if timestamp in timestamps:
# Update existing timestamp with new data
latest['clones'][timestamps[timestamp]] = now['clones'][i]
else:
# Add new timestamp
latest['clones'].append(now['clones'][i])
# Recalculate totals from all clones
latest['count'] = sum(map(lambda x: int(x['count']), latest['clones']))
latest['uniques'] = sum(map(lambda x: int(x['uniques']), latest['clones']))
# Aggregate old data by month if we have more than 100 timestamps
if len(timestamps) > 100:
remove_this = []
clones = latest['clones']
for i in range(len(timestamps) - 35):
clones[i]['timestamp'] = clones[i]['timestamp'][:7] # Truncate to YYYY-MM
if clones[i]['timestamp'] == clones[i+1]['timestamp'][:7]:
clones[i+1]['count'] += clones[i]['count']
clones[i+1]['uniques'] += clones[i]['uniques']
remove_this.append(clones[i])
for item in remove_this:
clones.remove(item)
# Write updated data back
with open('clone.json', 'w', encoding='utf-8') as fh:
json.dump(latest, fh, ensure_ascii=False, indent=4)
EOF
- name: Update gist with latest count
env:
GITHUB_TOKEN: ${{ secrets.CLONE_SECRET_TOKEN }}
run: |
set -euo pipefail
# Using environment variable to avoid exposing secret in process list
content=$(sed -e 's/\\/\\\\/g' -e 's/\t/\\t/g' -e 's/\"/\\"/g' -e 's/\r//g' "clone.json" | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g')
echo '{"description": "${{ github.repository }} clone statistics", "files": {"clone.json": {"content": "'"$content"'"}}}' > post_clone.json
if ! curl -f -s -X PATCH \
--user "${{ github.actor }}:$GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d @post_clone.json "https://api.github.com/gists/${{ steps.set_id.outputs.GIST }}" > /dev/null; then
echo "Error: Failed to update gist"
exit 1
fi
if [ ! -f CLONE.md ]; then
shields="https://img.shields.io/badge/dynamic/json?color=success&label=Clone&query=count&url="
url="https://gist.githubusercontent.com/${{ github.actor }}/${{ steps.set_id.outputs.GIST }}/raw/clone.json"
repo="https://gist.githubusercontent.com/${{ github.actor }}/${{ steps.set_id.outputs.GIST }}/raw/clone.json"
echo '' > CLONE.md
echo '**Markdown**' >> CLONE.md
echo '```markdown' >> CLONE.md
echo "[![GitHub Clones](${shields}${url}&logo=github)](${repo})" >> CLONE.md
echo '```' >> CLONE.md
git add CLONE.md
git config --global user.name "GitHub Action"
git config --global user.email "[email protected]"
git commit -m "create clone count badge"
fi
- name: Push
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.CLONE_SECRET_TOKEN }}
branch: "gh-actions"
force_with_lease: true