├── .gitignore ├── .prettierrc ├── art └── screenshot.png ├── src ├── userSettings.ts ├── types.ts ├── constants.ts ├── options.html ├── options.ts ├── index.ts └── dom.ts ├── tsconfig.json ├── manifest.json ├── LICENSE.md ├── .github └── workflows │ └── release.yml ├── package.json ├── README.md └── icon └── icon.svg /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | dist/ 4 | .cache/ 5 | .vscode/ 6 | web-ext-artifacts/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } -------------------------------------------------------------------------------- /art/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shywim/github-repo-size/HEAD/art/screenshot.png -------------------------------------------------------------------------------- /src/userSettings.ts: -------------------------------------------------------------------------------- 1 | export const getStoredSetting = async (key: string) => { 2 | const storage = await browser.storage.local.get() 3 | return storage[key] 4 | } 5 | 6 | export const setSetting = async (key: string, value: unknown) => { 7 | await browser.storage.local.set({ 8 | [key]: value, 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type HumanSize = { 2 | size: string 3 | unit: string 4 | } 5 | 6 | export type RepoInfo = { 7 | owner: string 8 | name: string 9 | } 10 | 11 | export type PartialGitHubRepo = { 12 | data: { 13 | repository: { 14 | diskUsage: number 15 | } 16 | } 17 | } 18 | 19 | export type PartialGitHubRepoRestV3 = { 20 | size: number 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "strict": true, 5 | "module": "es2015", 6 | "lib": ["es2015", "es2016", "es2017", "dom", "dom.iterable"], 7 | "esModuleInterop": true, 8 | "target": "ES2015", 9 | "noUnusedLocals": true, 10 | "noImplicitReturns": true, 11 | "skipLibCheck": true, 12 | "noFallthroughCasesInSwitch": true 13 | } 14 | } -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const TOKEN_KEY = 'grs_gh_token' 2 | export const GITHUB_API = 'https://api.github.com/graphql' 3 | export const GITHUB_API_V3 = 'https://api.github.com/repos/' 4 | export const REPO_STATS_CLASS = 'numbers-summary' 5 | export const REPO_REFRESH_STATS_QUERY = 6 | '.repository-content .Box-header .Details ul' 7 | export const REPO_SIZE_ID = 'addon-repo-size' 8 | export const SIZE_KILO = 1024 9 | export const UNITS = [ 10 | 'B', 11 | 'KiB', 12 | 'MiB', 13 | 'GiB', 14 | 'TiB', 15 | 'PiB', 16 | 'EiB', 17 | 'ZiB', 18 | 'YiB', 19 | ] 20 | export const AUTO_ASK_KEY = 'grs_auto_ask' 21 | export const MODAL_ID = 'grs_token_modal' 22 | export const TOKEN_INPUT_ID = 'grs_token_input' 23 | 24 | export const ERROR_UNAUTHORIZED = '401' 25 | export const ERROR_UNKNOWN = '-1' 26 | -------------------------------------------------------------------------------- /src/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 12 | 13 | 14 | 15 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Github Repo Size", 4 | "author": "Matthieu Harlé", 5 | "homepage_url": "https://github.com/Shywim/github-repo-size", 6 | "version": "1.7.0", 7 | "description": "Add repository size to their github homepage.", 8 | "options_ui": { 9 | "page": "options.html", 10 | "browser_style": true 11 | }, 12 | "browser_specific_settings": { 13 | "gecko": { 14 | "id": "github-repo-size@mattelrah.com" 15 | } 16 | }, 17 | "icons": { 18 | "32": "icon/icon.svg", 19 | "48": "icon/icon.svg", 20 | "64": "icon/icon.svg", 21 | "96": "icon/icon.svg", 22 | "128": "icon/icon.svg", 23 | "256": "icon/icon.svg" 24 | }, 25 | "content_scripts": [{ 26 | "matches": ["*://github.com/*"], 27 | "js": ["index.js"] 28 | }], 29 | "permissions": [ 30 | "*://api.github.com/repos/*", 31 | "storage" 32 | ] 33 | } -------------------------------------------------------------------------------- /src/options.ts: -------------------------------------------------------------------------------- 1 | import { TOKEN_KEY } from './constants' 2 | import { getStoredSetting, setSetting } from './userSettings' 3 | 4 | async function initForm() { 5 | const form = document.getElementById('ghs-options-form') as HTMLFormElement 6 | const existingTokenElmt = document.getElementById('existing-token') 7 | 8 | if (form == null || existingTokenElmt == null) { 9 | return 10 | } 11 | 12 | const token = await getStoredSetting(TOKEN_KEY) 13 | if (token) { 14 | const input = form.elements.namedItem('ghs-token') 15 | if (input == null) { 16 | return 17 | } 18 | ;(input as HTMLInputElement).placeholder = 19 | '****************************************' 20 | existingTokenElmt.style.display = 'block' 21 | } 22 | 23 | form.addEventListener('submit', (e) => { 24 | e.preventDefault() 25 | const input = form.elements.namedItem('ghs-token') 26 | if (input == null) { 27 | return 28 | } 29 | const token = (input as HTMLInputElement).value 30 | setSetting(TOKEN_KEY, token) 31 | }) 32 | } 33 | 34 | initForm() 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Matthieu Harlé 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | # cache dependencies 15 | - name: Get yarn cache directory 16 | id: yarn-cache 17 | run: echo "::set-output name=dir::$(yarn cache dir)" 18 | - name: "Cache: yarn" 19 | uses: actions/cache@v1.1.2 20 | with: 21 | path: ${{ steps.yarn-cache.outputs.dir }} 22 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 23 | restore-keys: | 24 | ${{ runner.os }}-yarn- 25 | - name: Use Node.js 12 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: "12" 29 | registry-url: "https://npm.pkg.github.com" 30 | 31 | - run: yarn install --pure-lockfile 32 | 33 | - run: yarn build 34 | 35 | - name: Upload Webext Artifact 36 | uses: actions/upload-release-asset@v1 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | upload_url: ${{ github.event.release.upload_url }} 41 | asset_path: ./web-ext-artifacts/github_repo_size-${{ github.event.release.tag_name }}.zip 42 | asset_name: github-repo-size.zip 43 | asset_content_type: application/zip 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-repo-size", 3 | "version": "1.7.0", 4 | "description": "Firefox addon to add repository size to their Github homepage", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "build": "npm-run-all build:sources build:package", 8 | "build:package": "web-ext build -s ./dist", 9 | "build:sources": "npm-run-all --parallel build:main build:options copyAssets copyIcons", 10 | "build:main": "parcel build src/index.ts --public-url ./", 11 | "build:options": "parcel build src/options.ts --public-url ./", 12 | "watch": "npm-run-all --parallel watch:main watch:options copyAssets copyIcons", 13 | "watch:main": "parcel watch src/index.ts --public-url ./", 14 | "watch:options": "parcel watch src/options.ts --public-url ./", 15 | "copyAssets": "cpy manifest.json LICENSE.md README.md package.json tsconfig.json yarn.lock src/options.html dist/", 16 | "copyIcons": "cpy --parents icon/icon.svg dist/", 17 | "webext:run": "web-ext run -s ./dist", 18 | "webext:lint": "web-ext lint -s ./dist" 19 | }, 20 | "author": "Matthieu Harlé", 21 | "license": "MIT", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/Shywim/github-repo-size.git" 25 | }, 26 | "devDependencies": { 27 | "@types/firefox-webext-browser": "^82.0.1", 28 | "cpy-cli": "^3.1.1", 29 | "npm-run-all": "^4.1.5", 30 | "parcel-bundler": "^1.12.5", 31 | "prettier": "^2.4.1", 32 | "rimraf": "^3.0.2", 33 | "typescript": "^4.4.3", 34 | "web-ext": "^6.4.0" 35 | }, 36 | "dependencies": { 37 | "dom-loaded": "^3.0.0" 38 | }, 39 | "browserslist": [ 40 | "last 1 Firefox version" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Firefox addon to display a Github repository's size 2 | 3 | [][amo] 4 | 5 | Add repository size to the Github's summary. 6 | 7 |  8 | 9 | **⚠ This addon use the size as returned by the GitHub API and may be 10 | innacurate due to how GitHub stores git repositories! See [here][soq] and 11 | [here][ghb] for more informations.** 12 | 13 | ## Usage 14 | 15 | Download the addon from **[addons.mozilla.org][amo]** or, if you prefer, you 16 | can download this project as a userscript from the **[GitHub releases page][ghreleases]**. 17 | 18 | ### Private Repositories 19 | 20 | A **Personal Access Token** from an account with access to the private repository is 21 | required for this addon to work. You can create a Personal Access Token 22 | [here][ghsettings]. **Don't forget to check the `repo` scope.** 23 | 24 | You can also show the dialog to save your token by clicking on the element added 25 | by the addon on any repository, public or private, or you can visit the addon's 26 | settings page. 27 | 28 | ## Building 29 | 30 | - Use `yarn build` to build the Firefox webextension 31 | - Use `yarn watch` to have an automated build on changes and `yarn webext:run` to test the addon 32 | 33 | [amo]: https://addons.mozilla.org/firefox/addon/github-repo-size/ 34 | [ujs]: https://github.com/Shywim/github-repo-size/releases/latest/download/github-repo-size.user.js 35 | [ghreleases]: https://github.com/Shywim/github-repo-size/releases 36 | [soq]: https://stackoverflow.com/a/8679592/1424030 37 | [ghb]: https://git-blame.blogspot.fr/2012/08/bringing-bit-more-sanity-to-alternates.html 38 | [ghsettings]: https://github.com/settings/tokens 39 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import domLoaded from 'dom-loaded' 2 | import { 3 | AUTO_ASK_KEY, 4 | ERROR_UNAUTHORIZED, 5 | ERROR_UNKNOWN, 6 | GITHUB_API, 7 | GITHUB_API_V3, 8 | REPO_REFRESH_STATS_QUERY, 9 | REPO_SIZE_ID, 10 | SIZE_KILO, 11 | TOKEN_KEY, 12 | UNITS, 13 | } from './constants' 14 | import { 15 | askForToken, 16 | createErrorElement, 17 | createMissingTokenElement, 18 | createSizeElements, 19 | createSizeWrapperElement, 20 | } from './dom' 21 | import { 22 | HumanSize, 23 | PartialGitHubRepo, 24 | PartialGitHubRepoRestV3, 25 | RepoInfo, 26 | } from './types' 27 | import { getStoredSetting } from './userSettings' 28 | 29 | const checkIsPrivate = () => { 30 | return ( 31 | document.querySelector( 32 | '#repository-container-header .Label.Label--secondary' 33 | )?.innerHTML === 'Private' 34 | ) 35 | } 36 | 37 | const getRepoInfo = (url: string): RepoInfo | null => { 38 | const paths = url.split('/') 39 | 40 | if (paths.length < 2) { 41 | return null 42 | } 43 | 44 | return { owner: paths[0], name: paths[1] } 45 | } 46 | 47 | const getRepoDataAnon = (repoInfo: RepoInfo) => { 48 | const url = `${GITHUB_API_V3}${repoInfo.owner}/${repoInfo.name}` 49 | const request = new window.Request(url) 50 | 51 | return window 52 | .fetch(request) 53 | .then