Generate a polished, static GitHub profile site from any user or organization — using a simple local generator script.
- Input: a GitHub username/org and (optionally) a small config file.
- Output: a Vite + React + TypeScript site wired to static data generated from the GitHub API.
- No runtime API calls: all data is fetched once at build time and written into JSON/TS files.
- License: MIT (see
LICENSE).
Recommended for most users:
- Fork this repo to your GitHub account.
- (Optional but recommended) Rename the fork to
yourusername.github.ioif you want the site at the root:Settings → General → Repository name
- GitHub Actions will:
- Run the generator using your GitHub username as
GITHUB_OWNER. - Build the site.
- Deploy to GitHub Pages.
- Run the generator using your GitHub username as
You’ll get a live site at:
https://yourusername.github.io/if the repo is namedyourusername.github.iohttps://yourusername.github.io/gitfolio/if the repo is namedgitfolio
The scheduled workflow also runs daily to refresh your GitHub data and stats.
By default, the generator uses the built‑in GITHUB_TOKEN provided by GitHub Actions:
-
This is already wired in the workflow:
env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_OWNER: ${{ github.repository_owner }} GITHUB_PROFILE_TYPE: user
-
This is usually enough for:
- Higher rate limits than anonymous requests.
- Access to public profile + repos.
If you want to include private repos in stats (they are only used for aggregates, never shown as projects), you can:
- Create a personal access token with
reposcope. - Add it as a secret named
GITHUB_TOKENin:- Settings → Secrets and variables → Actions → New repository secret.
- The workflow will automatically use that instead of the default token.
If you want to run it locally or customize more deeply:
git clone https://github.com/usedamru/gitforge.git .
pnpm install # or: npm installYou can either pass the owner on the CLI or via config.
# user profile
pnpm generate:github amide-init --type user
# organization
pnpm generate:github usedamru --type org- Copy the example:
cp gitforge.config.example.json gitforge.config.json- Edit
gitforge.config.json:
- Run the generator (no args needed; it reads the config):
pnpm generate:githubPrecedence for owner/type: CLI args → env vars →
gitforge.config.json→ defaults.
The CLI calls the GitHub API once and writes:
src/generated/githubData.ts– raw, typed snapshot of the profile + repos.src/siteContent.json– editable JSON template used by the React app.
The React app (src/App.tsx) reads only from src/siteContent.json, so:
- You can fully tweak copy/ordering/layout text without touching TypeScript.
- Regenerating with
gitforgewill overwritesiteContent.jsonwith fresh data, so:- Either treat JSON as “generated, don’t edit”, or
- Keep a copy / commit your version and regenerate only when needed.
-
Language distribution (used for the language pie chart):
-
For each repo we read GitHub’s primary
languagefield. -
We count how many repos use each language:
[ \text{count(language)} = #{\text{repos with that primary language}} ]
-
We only consider repos that have a language set. Let (\text{total_with_language}) be the sum of counts over all languages:
[ \text{total_with_language} = \sum_{\ell} \text{count}(\ell) ]
-
The percentage for each language is:
[ \text{percentage}(\ell) = \text{round}\left( \frac{\text{count}(\ell)}{\text{total_with_language}} \times 100 \right) ]
-
Only the top 8 languages (by
count) are shown in the chart.
-
Key sections in siteContent.json:
hero: title, subtitle, CTA, caption.snapshot: list of profile stats (repos, followers, last updated).philosophy: section title, intro, and cards summarizing repo activity/languages/topics.projects: section title, intro, and an array of featured repos with:name,description,url,stars,language,topics,lastUpdated.
footer: short explanatory text + GitHub link label/URL.
After generating content:
pnpm dev # or: npm run devThen open the printed URL (usually http://localhost:5173).
To build and preview a production bundle:
pnpm build
pnpm previewThe generated site is:
- Dark, minimal, developer‑focused.
- Single‑page (no routing).
- Built with React + TypeScript + Vite.
The /admin route lets repository admins edit gitforge.config.json (hero, featured repos, custom links) from the browser. Changes are committed via the GitHub API; GitHub Actions then rebuilds the site.
- Go to GitHub → Settings → Developer settings → OAuth Apps: github.com/settings/developers.
- Click New OAuth App.
- Set:
- Application name: e.g.
gitfolio Admin - Homepage URL: your site URL
- User site:
https://YOUR_USERNAME.github.io - Project site:
https://YOUR_USERNAME.github.io/gitfolio
- User site:
- Authorization callback URL: same as Homepage (or the default shown).
- Application name: e.g.
- Click Register application.
- Copy the Client ID (you do not need the client secret for the Device Flow used here).
Copy the example env file and set the admin variables:
cp .env.example .envEdit .env and set:
VITE_GITHUB_CLIENT_ID=your_oauth_app_client_id_here
VITE_GITHUB_OWNER=your-github-username-or-org
VITE_GITHUB_REPO=gitfolioVITE_GITHUB_OWNER: owner of the repo (your username or org).VITE_GITHUB_REPO: repository name (e.g.gitfoliooryourusername.github.io).
pnpm devThen open:
- Main site:
http://localhost:5173/ - Admin panel:
http://localhost:5173/admin
Click Login with GitHub, complete the device flow in the new tab, then edit and save. Only users with admin permission on the repo can save; others see “Unauthorized”.
-
Create an OAuth App at github.com/settings/developers:
- Homepage URL: your site URL (e.g.
https://yourusername.github.io/gitfolio) - Authorization callback URL: same as Homepage
- Copy the Client ID
- Homepage URL: your site URL (e.g.
-
Add GitHub Actions secret:
- Repo → Settings → Secrets and variables → Actions
- New repository secret
- Name:
VITE_GITHUB_CLIENT_ID - Value: your OAuth App Client ID
The workflow already uses
VITE_GITHUB_OWNERandVITE_GITHUB_REPOfrom the repo (automatic for forks). -
OAuth CORS fix (required for admin login): GitHub blocks OAuth from the browser. Deploy the Cloudflare Worker: run
npx wrangler deployfromworkers/github-oauth-proxy/(requires Cloudflare account), then add a repository variableVITE_GITHUB_OAUTH_PROXY_URL=https://YOUR-WORKER.workers.dev(no trailing slash).
gitforge [owner] [--type user|org]owner(optional): GitHub user or org (e.g.amide-init,steipete,usedamru).--type(optional):userororg.
If you omit both, gitforge falls back to:
GITHUB_OWNER/GITHUB_PROFILE_TYPEenv vars.gitforge.config.json.- Internal defaults (
usedamru/org).
# CLI only (local)
pnpm generate:github steipete --type user
# With config file only
cp gitforge.config.example.json gitforge.config.json
pnpm generate:githubKey files:
scripts/generate-github-data.js– CLI & GitHub fetcher.src/App.tsx– main React app wired tositeContent.json.src/App.css– layout & styling (dark, minimal, responsive).src/siteContent.json– generated + editable content JSON.gitforge.config.example.json– config template for users.gitforge.config.json– user-local config (ignored by git).
.gitignore is set up to ignore:
node_modules,dist, Vite/TS build artefacts.- Logs (
*.log,logs/,npm-debug.log*, etc.). - Editor files (
.vscode,.idea,.DS_Store, etc.). - Generated content:
src/generated/,src/siteContent.json. - Local config:
gitforge.config.json.
This keeps the repo clean while allowing each user to have their own profile config.
gitforge init– scaffold a fresh project into any empty directory (no manual clone).- Additional themes (still minimal, dev‑focused).
- Multi-profile / team pages.
If you have a specific workflow in mind, open an issue or PR in the repo where this package lives. :)
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- @vitejs/plugin-react uses Babel (or oxc when used in rolldown-vite) for Fast Refresh
- @vitejs/plugin-react-swc uses SWC for Fast Refresh
The React Compiler is currently not compatible with SWC. See this issue for tracking the progress.
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])You can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
{ "githubOwner": "amide-init", // user or org name "profileType": "user", // "user" | "org" "featuredRepos": ["tide-app"], // optional; list of repo names to always include "listedRepo": { "count": 4, // how many *additional* repos to list "sort": "date" // "date" | "star" | "date-then-star" | "star-then-date" } }