From 7d36fda419aad17b0e218430f830b0dc6b6076ce Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Tue, 8 Sep 2026 14:19:24 -0700 Subject: [PATCH] Move size to About section, add Git Trees fallback for 0KB repos, and enhance GH_PAT secret retrieval --- DisplayRepoSizeGitHub.user.js | 122 +++++++++++++++++++++++----------- README.md | 21 +++--- 2 files changed, 94 insertions(+), 49 deletions(-) diff --git a/DisplayRepoSizeGitHub.user.js b/DisplayRepoSizeGitHub.user.js index c832037..7e4747e 100644 --- a/DisplayRepoSizeGitHub.user.js +++ b/DisplayRepoSizeGitHub.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Display Repo Size for GitHub -// @version 1.0.0 -// @description Displays the total repository size on GitHub repository pages (supports public & private repos via GH_PAT). +// @version 1.1.0 +// @description Displays repository size inside the About section on GitHub (supports public & private repos using GH_PAT). // @author OpenScript // @match https://github.com/*/* // @grant none @@ -11,86 +11,132 @@ 'use strict'; const cache = new Map(); - const BADGE_ID = 'openscript-repo-size'; + const ROW_ID = 'openscript-repo-size-about'; - // Format KB to readable size - const formatBytes = kb => { - if (kb < 1024) return `${kb} KB`; + // Format bytes to human readable format + const formatSize = bytes => { + const kb = bytes / 1024; + if (kb < 1024) return `${kb.toFixed(1)} KB`; if (kb < 1024 * 1024) return `${(kb / 1024).toFixed(1)} MB`; return `${(kb / (1024 * 1024)).toFixed(2)} GB`; }; - // Parse owner & repo from path + // Extract owner & repo const getRepoInfo = () => { const [, owner, repo] = location.pathname.split('/'); - const reserved = new Set(['settings', 'orgs', 'organizations', 'notifications', 'search', 'features', 'pricing', 'explore']); + const reserved = new Set(['settings', 'orgs', 'organizations', 'notifications', 'search', 'features', 'pricing', 'explore', 'marketplace']); return (owner && repo && !reserved.has(owner)) ? { owner, repo } : null; }; - // Fetch size from GitHub API with optional GH_PAT + // Safely retrieve GH_PAT from OpenScript environment + const getPat = () => { + const secrets = (typeof OpenScript !== 'undefined' ? OpenScript?.env : null) || + (typeof env !== 'undefined' ? env : null) || + window.OpenScript?.env || + window.env || + {}; + return secrets.GH_PAT || secrets.gh_pat || secrets.GITHUB_PAT || secrets.GITHUB_TOKEN || null; + }; + + // Fetch repository size from GitHub API (with fallback to recursive Git Trees API when size is 0) const fetchRepoSize = async (owner, repo) => { const key = `${owner}/${repo}`; if (cache.has(key)) return cache.get(key); - const token = window.OpenScript?.env?.GH_PAT || window.env?.GH_PAT; + const pat = getPat(); const headers = { Accept: 'application/vnd.github.v3+json' }; - if (token) headers.Authorization = `Bearer ${token}`; + if (pat) headers.Authorization = `Bearer ${pat.trim()}`; try { const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, { headers }); - if (!res.ok) return res.status === 404 ? 'private/missing GH_PAT' : 'error'; + if (!res.ok) { + if (res.status === 404) return pat ? 'repo not found' : 'private (set GH_PAT)'; + if (res.status === 401) return 'invalid GH_PAT'; + return `API error (${res.status})`; + } + const data = await res.json(); - const formatted = formatBytes(data.size); + let bytes = (data.size || 0) * 1024; + + // GitHub async calculation fallback: if size is 0, sum blob sizes via Git Trees API + if (bytes <= 0) { + const branch = data.default_branch || 'main'; + const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`, { headers }); + if (treeRes.ok) { + const treeData = await treeRes.json(); + bytes = (treeData.tree || []).reduce((acc, item) => acc + (item.size || 0), 0); + } + } + + const formatted = formatSize(bytes); cache.set(key, formatted); return formatted; } catch { - return null; + return 'failed to load'; } }; - // Inject or update size badge - const updateBadge = async () => { + // Find the GitHub "About" section in sidebar + const findAboutContainer = () => { + // Priority 1: First BorderGrid-cell containing About header or in right sidebar + const cells = document.querySelectorAll('.Layout-sidebar .BorderGrid-cell, .BorderGrid-cell'); + for (const cell of cells) { + const h2 = cell.querySelector('h2'); + if (h2 && /About/i.test(h2.textContent)) return cell; + } + + // Priority 2: Standard first cell in sidebar + return document.querySelector('.Layout-sidebar .BorderGrid-row:first-child .BorderGrid-cell') || + document.querySelector('[data-testid="about-section"]') || + document.querySelector('.Layout-sidebar section'); + }; + + // Render or update the size element inside the About section + const updateAboutSize = async () => { const info = getRepoInfo(); if (!info) return; - // Anchor locations on GitHub repo pages - const anchor = document.querySelector('.file-navigation') || - document.querySelector('[data-testid="latest-commit-details"]') || - document.querySelector('.BorderGrid-cell .d-flex') || - document.querySelector('#repository-container-header ul'); + // Clean up any old top badge if present + document.getElementById('openscript-repo-size')?.remove(); - if (!anchor || document.getElementById(BADGE_ID)) return; + const container = findAboutContainer(); + if (!container || document.getElementById(ROW_ID)) return; - const badge = document.createElement('div'); - badge.id = BADGE_ID; - badge.className = 'd-inline-flex flex-items-center mr-2 px-2 py-1 text-bold text-small rounded-2 border color-border-default color-bg-subtle'; - badge.style.cssText = 'align-self: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;'; - badge.innerHTML = ` - calculating... + calculating... repo size `; - anchor.prepend(badge); + // Insert after description / before bottom stats + const heading = container.querySelector('h2'); + if (heading && heading.nextElementSibling) { + heading.parentNode.insertBefore(row, heading.nextElementSibling.nextElementSibling || heading.nextElementSibling); + } else { + container.appendChild(row); + } const size = await fetchRepoSize(info.owner, info.repo); - const sizeSpan = badge.querySelector('.size-text'); - if (sizeSpan) sizeSpan.textContent = size ? size : 'unknown'; + const valEl = row.querySelector('.size-val'); + if (valEl) valEl.textContent = size; }; - // Re-run on Turbo navigation & DOM mutations - ['turbo:load', 'turbo:render', 'pjax:end', 'popstate'].forEach(ev => + // Listen to GitHub SPA navigation events + ['turbo:load', 'turbo:render', 'pjax:end', 'popstate'].forEach(ev => window.addEventListener(ev, () => { - document.getElementById(BADGE_ID)?.remove(); - updateBadge(); + document.getElementById(ROW_ID)?.remove(); + updateAboutSize(); }) ); const observer = new MutationObserver(() => { - if (!document.getElementById(BADGE_ID)) updateBadge(); + if (!document.getElementById(ROW_ID)) updateAboutSize(); }); observer.observe(document.body, { childList: true, subtree: true }); - updateBadge(); + updateAboutSize(); })(); diff --git a/README.md b/README.md index b085b81..2416fa1 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,23 @@ # Display Repo Size for GitHub (OpenScript Userscript) -A lightweight OpenScript userscript that displays the total repository size directly on GitHub repository pages (for both public and private repositories). +A lightweight OpenScript userscript that displays the total repository size directly in the **About** section on GitHub repository pages (for both public and private repositories). ## Features -- Displays total repo size (formatted in KB, MB, or GB) next to the file navigation header. -- Works seamlessly on public and private repositories. -- Integrates with OpenScript's synced secrets: uses `GH_PAT` (GitHub Personal Access Token) to authenticate API requests, unlock private repo access, and elevate the rate limit from 60 to 5,000 req/hr. -- Supports GitHub's Turbo and SPA client-side navigations. +- **Integrated in About Section**: Injects cleanly under repository details in the right sidebar (e.g. `📦 673.9 KB repo size`). +- **Accurate Size Detection**: Automatically detects when GitHub's API returns `0 KB` on newly created repositories and falls back to calculating the total content size via the Git Trees API. +- **Private & Public Repositories**: Works on public repositories out-of-the-box and uses `GH_PAT` from OpenScript secrets for private repositories. +- **Turbo / SPA Compatible**: Seamlessly persists across GitHub's Turbo and client-side page transitions. ## Installation in OpenScript 1. Open the **OpenScript** extension popup. 2. Click **+ New** in the header. -3. Copy and paste the code from [`DisplayRepoSizeGitHub.user.js`](./DisplayRepoSizeGitHub.user.js) into the editor. +3. Paste the contents of [`DisplayRepoSizeGitHub.user.js`](./DisplayRepoSizeGitHub.user.js). 4. Click **save script**. -## Private Repos & GitHub PAT Configuration -To view the size of private repositories: +## GitHub PAT Configuration (for Private Repos) 1. Generate a GitHub Personal Access Token (`repo` scope for private repos) at [github.com/settings/tokens](https://github.com/settings/tokens). 2. Open **OpenScript** and switch to the **Secrets** tab. -3. Add a secret with: +3. Add a secret: - **Key**: `GH_PAT` - - **Value**: `` -4. Click **+ Add**. The secret will sync across your devices via `chrome.storage.sync` and will be automatically available to the script. + - **Value**: `` +4. Click **+ Add**. OpenScript synchronizes the secret via `chrome.storage.sync` and securely provides it to your script as `OpenScript.env.GH_PAT`.