Repository navigation
Analyze Repositories with PAT #109
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 Repositories with PAT | |
| on: | |
| schedule: | |
| - cron: "0 0 * * 0" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| count-lines: | |
| # Keep this workflow copyable without running PAT analysis in this demo repo. | |
| if: github.repository != 'arhamkhnz/github-code-analyzer' | |
| runs-on: ubuntu-latest | |
| env: | |
| # Use cloc language names. Highlighted languages get their own README row. | |
| HIGHLIGHT_LANGS: "JavaScript,TypeScript,JSX,Vuejs Component,PHP,C#" | |
| # These languages are excluded from the cloc total. | |
| IGNORE_LANGS: "JSON,HTML,CSS,SCSS,Sass,Markdown,SVG,XML,YAML,TOML,CSV,Text,Properties" | |
| steps: | |
| - name: Checkout report repository | |
| uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Install cloc and jq | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y cloc jq | |
| - name: Discover repositories accessible to the PAT | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "$GH_TOKEN" ]]; then | |
| echo "Add a GH_PAT repository secret before running this workflow" >&2 | |
| exit 1 | |
| fi | |
| # The authenticated-user endpoint can include owned, collaborator, | |
| # public, and private repositories. Fetch every page, then skip forks. | |
| if ! gh api --paginate --slurp 'user/repos?per_page=100' > "$RUNNER_TEMP/pat-repos.json" 2>/dev/null; then | |
| echo "Could not list repositories. Check GH_PAT access." >&2 | |
| exit 1 | |
| fi | |
| jq -e 'type == "array" and all(.[]; type == "array")' "$RUNNER_TEMP/pat-repos.json" > /dev/null | |
| if ! jq -e 'any(.[][]; .private == true and .fork == false)' "$RUNNER_TEMP/pat-repos.json" > /dev/null; then | |
| echo "::warning::No private non-fork repositories were returned. Check the token's resource owner and repository access if you expected private repos." | |
| fi | |
| jq -r '.[][] | select(.fork == false) | .full_name' \ | |
| "$RUNNER_TEMP/pat-repos.json" > "$RUNNER_TEMP/pat-source-repos.txt" | |
| - name: Clone default branches | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| gh auth setup-git --hostname github.com --force | |
| mapfile -t repos < "$RUNNER_TEMP/pat-source-repos.txt" | |
| clone_root="$RUNNER_TEMP/pat-code-repos" | |
| mkdir -p "$clone_root" | |
| for index in "${!repos[@]}"; do | |
| repo="${repos[$index]}" | |
| if ! git clone --quiet --depth 1 --single-branch \ | |
| "https://github.com/$repo.git" "$clone_root/$index" > /dev/null 2>&1; then | |
| echo "A repository could not be cloned. Check GH_PAT access." >&2 | |
| exit 1 | |
| fi | |
| done | |
| - name: Count code lines | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p output | |
| if [[ -s "$RUNNER_TEMP/pat-source-repos.txt" ]]; then | |
| if ! cloc "$RUNNER_TEMP/pat-code-repos" --json \ | |
| --report-file=output/cloc-output.json \ | |
| --exclude-dir=.git --exclude-lang="$IGNORE_LANGS" > /dev/null 2>&1; then | |
| echo "Could not count code lines; no stats were published." >&2 | |
| exit 1 | |
| fi | |
| else | |
| jq -n '{SUM: {code: 0}}' > output/cloc-output.json | |
| fi | |
| jq 'del(.header)' output/cloc-output.json > "$RUNNER_TEMP/pat-cloc-summary.json" | |
| mv "$RUNNER_TEMP/pat-cloc-summary.json" output/cloc-output.json | |
| - name: Update PAT section in README | |
| shell: python | |
| run: | | |
| import json | |
| import os | |
| import re | |
| from pathlib import Path | |
| report = json.loads(Path("output/cloc-output.json").read_text()) | |
| total = report.get("SUM", {}).get("code") | |
| if not isinstance(total, int) or total < 0: | |
| raise ValueError("cloc did not produce a valid code total") | |
| languages = [] | |
| for name, counts in report.items(): | |
| if name in {"header", "SUM"}: | |
| continue | |
| code = counts.get("code") | |
| if not isinstance(code, int) or code < 0: | |
| raise ValueError(f"Invalid cloc count for {name}") | |
| if code: | |
| languages.append((name, code)) | |
| if sum(code for _, code in languages) != total: | |
| raise ValueError("Language counts do not match the cloc total") | |
| highlights = {name.strip() for name in os.environ["HIGHLIGHT_LANGS"].split(",")} | |
| named = sorted( | |
| ((name, code) for name, code in languages if name in highlights), | |
| key=lambda item: (-item[1], item[0]), | |
| ) | |
| other = sum(code for name, code in languages if name not in highlights) | |
| width = max([12, *(len(name) for name, _ in named)]) | |
| fence = chr(96) * 3 | |
| rows = [fence, "[ LANGUAGES BREAKDOWN ]", ""] | |
| rows += [f"{name:<{width}} --> {code:,} lines" for name, code in named] | |
| if other: | |
| rows.append(f"{'Others':<{width}} --> {other:,} lines") | |
| rows += ["", f"[ TOTAL LINES OF CODE: {total:,} ]", fence] | |
| start = re.compile(r"^<!-- LANGUAGES BREAKDOWN START -->$", re.MULTILINE) | |
| end = re.compile(r"^<!-- LANGUAGES BREAKDOWN END -->$", re.MULTILINE) | |
| readme = Path("README.md") | |
| content = readme.read_text() | |
| starts = list(start.finditer(content)) | |
| ends = list(end.finditer(content)) | |
| if len(starts) != 1 or len(ends) != 1 or starts[0].end() >= ends[0].start(): | |
| raise ValueError("README must contain exactly one ordered PAT stats marker pair") | |
| updated = ( | |
| content[: starts[0].end()] | |
| + "\n" | |
| + "\n".join(rows) | |
| + "\n" | |
| + content[ends[0].start() :] | |
| ) | |
| if updated != content: | |
| readme.write_text(updated) | |
| - name: Commit changed PAT stats | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| git add README.md output/cloc-output.json | |
| if git diff --cached --quiet; then | |
| echo "PAT 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 PAT repository code stats" | |
| gh auth setup-git --hostname github.com --force | |
| git push origin "HEAD:$GITHUB_REF" |