Repository navigation
Analyze Public Repositories by Username #5
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: Analyze Public Repositories by Username | |
| on: | |
| push: | |
| branches: | |
| - "**" | |
| schedule: | |
| - cron: "17 0 * * 0" | |
| workflow_dispatch: | |
| inputs: | |
| username: | |
| description: GitHub username to analyze (defaults to PUBLIC_STATS_USERNAME or the repository owner) | |
| required: false | |
| type: string | |
| permissions: | |
| contents: write | |
| jobs: | |
| count-public-lines: | |
| runs-on: ubuntu-latest | |
| env: | |
| SOURCE_USERNAME: ${{ inputs.username || vars.PUBLIC_STATS_USERNAME || github.repository_owner }} | |
| HIGHLIGHT_LANGS: "JavaScript,TypeScript,JSX,Vuejs Component,PHP,C#" | |
| IGNORE_LANGS: "JSON,HTML,CSS,SCSS,Sass,Markdown,SVG,XML,YAML,TOML,CSV,Text,Properties" | |
| steps: | |
| - name: Checkout report repository | |
| uses: actions/checkout@v7 | |
| - name: Install cloc and jq | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y cloc jq | |
| - name: Discover and count public repositories | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "$SOURCE_USERNAME" =~ ^[A-Za-z0-9-]+$ ]]; then | |
| echo "Invalid GitHub username" >&2 | |
| exit 1 | |
| fi | |
| # This endpoint lists public repositories owned by the named user. | |
| # --paginate retrieves all pages; --slurp wraps them for one jq pass. | |
| REPOS_JSON=$(gh api --paginate --slurp "users/$SOURCE_USERNAME/repos?per_page=100&type=owner") | |
| jq -e 'type == "array" and all(.[]; type == "array")' <<< "$REPOS_JSON" > /dev/null | |
| mapfile -t REPOS < <(jq -r '.[][] | select(.fork == false) | .full_name' <<< "$REPOS_JSON") | |
| CLONE_ROOT="$RUNNER_TEMP/public-code-repos" | |
| mkdir -p "$CLONE_ROOT" output | |
| for INDEX in "${!REPOS[@]}"; do | |
| REPO="${REPOS[$INDEX]}" | |
| echo "Cloning $REPO" | |
| git clone --quiet --depth 1 --single-branch "https://github.com/$REPO.git" "$CLONE_ROOT/$INDEX" | |
| done | |
| if (( ${#REPOS[@]} > 0 )); then | |
| cloc "$CLONE_ROOT" --json --report-file="$RUNNER_TEMP/public-cloc.json" --exclude-lang="$IGNORE_LANGS" | |
| else | |
| jq -n '{SUM: {code: 0}}' > "$RUNNER_TEMP/public-cloc.json" | |
| fi | |
| jq -e '(.SUM.code | type == "number") and (.SUM.code >= 0)' "$RUNNER_TEMP/public-cloc.json" > /dev/null | |
| jq --arg username "$SOURCE_USERNAME" --argjson repository_count "${#REPOS[@]}" \ | |
| '{username: $username, | |
| repository_count: $repository_count, | |
| total_code_lines: .SUM.code, | |
| languages: ([to_entries[] | |
| | select(.key != "header" and .key != "SUM" and .value.code > 0) | |
| | {name: .key, code_lines: .value.code}] | sort_by(.name))}' \ | |
| "$RUNNER_TEMP/public-cloc.json" > output/public-summary.json | |
| - name: Update public section in README | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| summary = json.loads(Path("output/public-summary.json").read_text()) | |
| highlights = {name.strip() for name in os.environ["HIGHLIGHT_LANGS"].split(",")} | |
| languages = summary["languages"] | |
| named = sorted( | |
| (item for item in languages if item["name"] in highlights), | |
| key=lambda item: (-item["code_lines"], item["name"]), | |
| ) | |
| other_lines = sum(item["code_lines"] for item in languages if item["name"] not in highlights) | |
| if sum(item["code_lines"] for item in languages) != summary["total_code_lines"]: | |
| raise ValueError("Language counts do not match the cloc total") | |
| username = summary["username"] | |
| rows = [ | |
| f"Public, non-fork repositories owned by [@{username}](https://github.com/{username}): " | |
| f"**{summary['repository_count']:,}**", | |
| "", | |
| "| Language | Code lines |", | |
| "| --- | ---: |", | |
| ] | |
| rows += [f"| {item['name']} | {item['code_lines']:,} |" for item in named] | |
| if other_lines: | |
| rows.append(f"| Others | {other_lines:,} |") | |
| rows.append(f"| **Total** | **{summary['total_code_lines']:,}** |") | |
| start = "<!-- PUBLIC CODE STATS START -->" | |
| end = "<!-- PUBLIC CODE STATS END -->" | |
| readme = Path("README.md") | |
| content = readme.read_text() | |
| if content.count(start) != 1 or content.count(end) != 1: | |
| raise ValueError("README must contain exactly one public stats marker pair") | |
| before, remainder = content.split(start) | |
| _, after = remainder.split(end) | |
| readme.write_text(before + start + "\n" + "\n".join(rows) + "\n" + end + after) | |
| PY | |
| - name: Commit changed public stats | |
| run: | | |
| set -euo pipefail | |
| git add README.md output/public-summary.json | |
| if git diff --cached --quiet; then | |
| echo "Public code statistics are unchanged" | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git commit -m "chore: update public repository code stats" | |
| git push origin "HEAD:$GITHUB_REF" |