Jesse A. Green / notebook

resume.github.com Administrator & Developer Guide

Generated April 2026 from resume/resume.github.com at f599075

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is the technical companion to the User Guide. It is written for forkers, self-hosters, and contributors who want to understand how resume/resume.github.com is built, how to deploy it, what its security boundary actually is, and where every behaviour lives in source. The project is small — twenty-one source files, one JavaScript module of roughly nineteen kilobytes, and a two-line Rack runner — but the surface the JavaScript exercises against the GitHub REST API is non-trivial. This document walks every piece.

The guide is pinned to commit f599075 (5 August 2016). The repository has been dormant since that commit; the live site at resume.github.com runs the same code.

Note

Because this repository contains no package.json, no Gemfile, and no dependency manifest, every external library is loaded by the HTML templates from a CDN at runtime. Treat the rendered DOM, not the file tree, as the source of truth for which JavaScript runs in production.

1. Architecture Overview

resume.github.com is a client-only static web application. There is no server-side application logic in the repository, and no first-party backend in production. The deployable artefact is a directory of static files that any HTTP server can serve verbatim.

What the system does

  1. Serve index.html over HTTP (or HTTPS, in production via GitHub Pages) along with js/githubresume.js, the two CSS files in css/, and the static images in images/.
  2. When the browser loads the page, js/githubresume.js parses window.location.search to extract a username.
  3. If a username is present, the script verifies the user has starred resume/resume.github.com (synchronous AJAX), then issues parallel asynchronous JSONP calls to api.github.com for profile, repos, merged PRs, and orgs.
  4. Each response is fed into a Mustache template loaded from views/ via AJAX; the rendered HTML is injected into the #resume host element inside index.html.
  5. The page can be printed to PDF via the browser's native print dialog; css/print.css controls the print-only styling.

There is no persistence, no session, no authentication, and no server-side rendering. Every render is fresh, sourced live from the GitHub REST API.

High-level architecture

                         Visitor's browser
        ┌─────────────────────────────────────────────────────┐
        │                                                     │
        │   index.html  ──(loads)──► js/githubresume.js       │
        │       │                          │                  │
        │       │ <div id="resume">        │                  │
        │       │                          │ JSONP calls      │
        │       │                          ▼                  │
        │       │                  ┌──────────────┐           │
        │       │                  │ api.github   │           │
        │       │ AJAX HTML        │ .com         │           │
        │       ▼                  └──────────────┘           │
        │   views/*.html                  │                   │
        │   (Mustache)                    ▼                   │
        │       │                  Profile / repos /          │
        │       └─► render ◄───── PRs / orgs JSON             │
        │                                                     │
        └────────────────────┬────────────────────────────────┘
                             │ HTTP GET
                             ▼
                   resume.github.com
                   (static GitHub Pages)

Component inventory

Component Path Role
Entry HTML index.html Boots jQuery, Mustache, and js/githubresume.js; provides the #resume host element.
Front-end controller js/githubresume.js Orchestrates URL parsing, opt-in check, GitHub API calls, and Mustache rendering.
Page styling css/resume.css Screen layout, colours, typography.
Print styling css/print.css @media print overrides for clean PDF output.
Landing template views/index.html Default screen with a username input, shown when no query string is present.
User résumé template views/resume.html Main résumé layout for data.type === 'User'.
Org résumé template views/resumeOrgs.html Variant layout for data.type === 'Organization'.
Job row template views/job.html Single-repository row in the Featured Projects block.
Org row template views/org.html Single-organisation row in the Organisations block.
Contribution row template views/contrib.html Single-row entry in the Open-source Contributions block.
Error template views/error.html Generic error screen, bound to window's error event.
API-limit template views/api_limit.html Shown when the starred-repos call returns HTTP 403.
Not-found template views/not_found.html Shown when the starred-repos call returns HTTP 404.
Opt-out template views/opt_out.html Shown when the user has not starred the repo.
Static assets images/ favicon.png, loader.gif, low_contrast_linen.png, srt.png, ttab.png.
Rack runner config.ru Two-line Rack file used to serve the directory locally with rackup.
Repo readme README.markdown Repo-level introduction (the briefing reports it as not extracted, so do not assume specific content).

Technology stack

Layer Technology Notes
Markup HTML5 Templates use Mustache {{ }} placeholders.
Styling CSS3 Two stylesheets, no preprocessor.
Scripting Vanilla JavaScript ES5 No bundler, no transpilation.
DOM/AJAX library jQuery Loaded by the HTML templates from a CDN; not vendored in the repo.
Templating Mustache.js Mustache.to_html() rendered client-side. Loaded from a CDN.
Analytics Google Analytics (UA-21222559-1) Classic ga.js loader; protocol switches based on page protocol.
Local serving Rack / config.ru Optional — any static server suffices.
Hosting (production) GitHub Pages Inferred from the resume.github.com domain pattern; not encoded in the repo.

Note

jQuery and Mustache are referenced by the HTML templates but are not committed to the repository. The exact versions used in production depend on whatever the templates <script>-tag in. Treat any assertion about jQuery version as something you must verify by inspecting the rendered index.html in the browser.

Data flow — the rendering pipeline

URL query string
      │
      ▼
parse window.location.search
      │
      ▼
$(document).ready
      │
      ├─ no username ──► home() ──► AJAX views/index.html ──► #resume
      │
      └─ has username ──► run()
                          │
                          ▼
            github_user_starred_resume()  (synchronous)
                          │
              ┌───────────┼───────────┬──────────────┐
              │ 403       │ 404       │ false (no    │ true
              ▼           ▼           ▼ star)        ▼
       api_limit.html  not_found.html  opt_out.html  ── parallel calls ──
                                                       │
                                  ┌────────────────────┼──────────────────┐
                                  ▼                    ▼                  ▼
                            github_user        github_user_repos    github_user_orgs
                                  │                    │                  │
                                  │             github_user_issues        │
                                  ▼                    ▼                  ▼
                            views/resume.html    views/job.html     views/org.html
                            views/resumeOrgs.html views/contrib.html
                                  │                    │                  │
                                  └────────────────────┴──────────────────┘
                                                       │
                                                       ▼
                                                  #resume DOM
                                                       │
                                                       ▼
                                          Print button bound → window.print()

2. System Requirements

The project has minimal infrastructural requirements. They are different for visitors, for self-hosting on GitHub Pages, and for running locally.

For visitors (hosted site)

Requirement Detail
Browser Any modern browser with JavaScript and cookies enabled.
Network Outbound HTTPS to resume.github.com, api.github.com, and *.google-analytics.com.
GitHub account Required for the subject of the résumé (the user being rendered must have starred the repo). Visitors do not need an account.

For local development

Requirement Detail
OS Anything (Linux, macOS, Windows, BSD). The project is platform-neutral static files.
HTTP server Anything that serves static files: Ruby + Rack, Python's http.server, Node's http-server, nginx, Apache, Caddy.
Disk Less than 1 MiB for the entire repository.
RAM Negligible — the server only serves static bytes.
Internet Required at runtime so the browser can reach api.github.com.

For production hosting

Requirement Detail
Static-file host GitHub Pages, S3 + CloudFront, Netlify, Vercel, Cloudflare Pages, or any equivalent.
TLS certificate Strongly recommended (Google Analytics protocol switching depends on https).
DNS An apex or subdomain pointing to the static host (the upstream uses resume.github.com).

Important

Because the front-end calls api.github.com directly from the browser, your hosting environment must allow third-party CORS. GitHub's public API explicitly allows cross-origin requests; no proxy is required. If you put the site behind a CSP, you must allow connect-src https://api.github.com.

3. Configuration Reference

This section documents every configurable surface in the project. The list is short by design: the upstream code prefers hard-coded constants and JavaScript defaults over external configuration.

Configuration files

File Format Location Purpose
config.ru Ruby (Rack) Repo root One-shot Rack runner used by rackup to serve the directory locally. Two lines: require 'rack' and a Rack::Directory mount. Not used in production.

There is no package.json, no Gemfile, no .env, no YAML config, no JSON config, and no environment variables. The AST extractor confirms zero configuration keys in the repository.

Environment variables

The project reads no environment variables from the JavaScript runtime (the browser does not have access to host environment variables, and the front end does not consult any). If you self-host behind nginx, you may set environment variables for nginx itself; those are out of scope for this repository.

Hard-coded constants in js/githubresume.js

These are the only knobs an operator can turn without rewriting the front-end. All live in js/githubresume.js. Edit the file directly.

Constant Default Lines Description Required?
trackerId 'UA-21222559-1' top of file Google Analytics property ID. The trailing if (trackerId) block injects the GA loader only when this is truthy. No — set to '' or remove to disable analytics.
username undefined (set at runtime) top of file Holds the parsed query-string user. Do not edit. N/A
urlParams {} top of file Holds the parsed query string. Do not edit. N/A
maxItems 5 inside run() Maximum number of featured projects rendered. Also used as the soft cap for organisations (shared itemCount). No
maxLanguages 9 inside run() Maximum number of language entries displayed. No
COEF_REPOS 2 inside getUserStatus() Weight applied to public_repos. No
COEF_GISTS 0.25 inside getUserStatus() Weight applied to public_gists. No
COEF_FOLLOWERS 0.5 inside getUserStatus() Weight applied to followers. No
COEF_FOLLOWING 0.25 inside getUserStatus() Weight applied to following. No
FIRST_STEP 0 inside getUserStatus() Score threshold below which the Inactive label applies. No
SECOND_STEP 5 inside getUserStatus() Upper bound for Newbie. No
THIRD_STEP 20 inside getUserStatus() Upper bound for Regular. No
FOURTH_STEP 50 inside getUserStatus() Upper bound for Advanced. No
FIFTH_STEP 150 inside getUserStatus() Upper bound for Enthusiastic. Above this, Passionate. No
EXTRA_POINT_GAIN 1 inside getUserStatus() Bonus added when the user is an early adopter, or when location, email, and blog are all set. Awarded twice (once for each condition). No

Tip

If you fork the project, the most common single change is to set trackerId to your own Google Analytics property (or to an empty string to disable analytics). The second most common change is to raise maxItems from 5 for users with extensive portfolios.

Runtime URL inputs

Input Source Behaviour
First query-string key window.location.search Used as the GitHub username. The parser walks ?key=value&key=value and assigns the first key it sees to urlParams[0], which is then read as username.

Important

The query-string parser uses urlParams[0] = d(e[1]); inside the while (e = r.exec(q)) loop. Because the assignment is to index 0 on every iteration, only the last parameter parsed actually survives. In practice, with a single-key URL like ?octocat, this works as intended; with multi-key URLs, behaviour is non-obvious.

Templates

The Mustache templates in views/ accept these named variables:

views/resume.html and views/resumeOrgs.html

Variable Source Notes
name data.name ?? data.login Display name.
type data.type User or Organization.
email data.email May be null.
created_at data.created_at ISO timestamp.
earlyAdopter 0 or 1 Computed locally.
location data.location May be null.
gravatar_id data.gravatar_id Legacy, may be empty.
avatar_url Computed Rewritten for organisations to a Gravatar URL.
repos data.public_repos Count.
reposLabel repository / repositories Singular/plural form.
followers data.followers Count.
followersLabel follower / followers Singular/plural form.
username URL parameter Echoed for self-links.
userStatus getUserStatus() One of six labels (see the User Guide).
since Computed Year, or 'this year' / 'last year'.
resume_url window.location Echoed for QR codes / sharing.
website data.blog, prefixed with http:// if missing scheme Optional.

views/job.html

Variable Source
name, date, language, description, homepage, username, watchers, forks, watchersLabel, forksLabel, last All derived from repo.info and rendering loop state.

views/contrib.html

Variable Source
count Number of merged PRs in the repo.
username URL parameter.
repoUrl Rewritten from repository_url.
repoName Rewritten from repository_url.
commitsUrl <repoUrl>/commits?author=<username>.

views/org.html

Variable Source
name org.info.name ?? org.info.login.
now new Date().getFullYear().
last Set on the final iteration.

4. Deployment & Scaling

The deployable artefact is the entire repository. There is nothing to build, transpile, or bundle.

Path A — GitHub Pages (matches upstream)

The upstream production site is resume.github.com. The conventional GitHub Pages deploy is:

  1. Fork or clone the repository.
  2. Push the contents to a branch named master (or whatever your GitHub Pages source is set to).
  3. In the repository settings, enable Pages with the source set to the chosen branch and the directory set to root (/).
  4. Configure your custom domain in Pages → Custom domain and set up the DNS A or CNAME records as GitHub instructs. Apex domains use A records to GitHub Pages IPs; subdomains use a CNAME to <owner>.github.io.
  5. Wait for the certificate to provision, then enable Enforce HTTPS.

Note

A CNAME file at the repo root would be required for a custom domain on GitHub Pages, but the upstream commit f599075 does not include one — the upstream relies on the implicit <owner>.github.io mapping for the resume organisation.

Path B — Object storage + CDN

Any static-file host works. The pattern is identical regardless of provider:

# Example: AWS S3 + CloudFront
aws s3 sync . s3://your-bucket/ --exclude '.git/*' --exclude 'README.markdown'
aws cloudfront create-invalidation \
    --distribution-id E1XXXXXXX --paths '/*'
upload: ./index.html to s3://your-bucket/index.html
upload: ./js/githubresume.js to s3://your-bucket/js/githubresume.js
upload: ./css/resume.css to s3://your-bucket/css/resume.css
... (and so on for the remaining static files)

Path C — A single Docker container

The project does not ship a Dockerfile. If you want a containerised version, point any nginx-based image at the repository directory:

FROM nginx:alpine
COPY . /usr/share/nginx/html
EXPOSE 80
docker build -t resume-github-com .
docker run --rm -p 8080:80 resume-github-com

Tip

For local development on Windows, the simplest path is python -m http.server from the repository root — no Docker, no Ruby, no Node required, and Python ships in modern Windows installations.

Scaling considerations

Because every request is for static bytes, scaling is trivial — any CDN in front of the storage will handle effectively unlimited concurrency. The interesting bottleneck is on the client side: the GitHub API rate limit caps how many concurrent visitors any single IP can serve.

Bottleneck Where Mitigation
60-req/hour limit (unauthenticated) api.github.com None applied in the upstream. Forks add tokens at their own risk (see Security).
Search API quota api.github.com/search/issues Same — the search endpoint has its own limits.
Synchronous starred-repos call github_user_starred_resume() Blocks the page; large user star lists cascade additional 100-per-page calls until the repo is found or the list ends.
Mustache rendering Browser CPU Negligible for typical résumé sizes.

5. Operations

There is essentially no operational surface for the upstream — once deployed to a static host, the site runs without maintenance. The operational tasks below apply to forks and self-hosters.

Routine maintenance checklist

  • [ ] Rotate Google Analytics property if you are tracking your own deployment.
  • [ ] Audit dependencies referenced by index.html and the templates (jQuery, Mustache CDNs).
  • [ ] Bump the year displayed by views/org.html if your fork uses a static template instead of the runtime now value.
  • [ ] Re-test against the GitHub API at least once a year — GitHub's REST API has continued to change since 2016 (legacy data envelope, gravatar IDs, etc.).
  • [ ] Verify the site still loads correctly after browser updates that may deprecate JSONP.

Health checks

The site has no explicit health-check endpoint. The closest equivalent is a request for / returning HTTP 200 with the literal string <title>Resume</title> in the HTML, which can be verified with a curl probe:

curl -sf https://your-host/ | grep -q '<title>Resume</title>' && echo OK || echo FAIL
OK

Logging

The upstream has no first-party logging. Three signals exist:

  1. console.log(err) inside $(document).ready — only visible in the visitor's browser console. Surfaces unhandled errors from the run() pipeline.
  2. Google Analytics — page views and (if you extend the GA block) custom events.
  3. HTTP access logs from your static host (GitHub Pages, S3, nginx).

Note

The error template (views/error.html) is loaded by the global window error event handler bound at the very bottom of js/githubresume.js: $(window).bind('error', error);. This means any uncaught script error will replace the page contents with the error template; you will not see the original error UI. For debugging, disable the bind('error', error) line locally so the original page state survives.

Monitoring recommendations

  • Synthetic check: probe / once per hour from an external monitor.
  • Synthetic render check: load https://your-host/?known-username in a headless browser weekly and assert the rendered DOM contains the expected username.
  • API health: monitor https://api.github.com/users/octocat from your monitoring source. If GitHub returns 429s widely, the site will surface API-limit screens to many visitors regardless of the local state.

6. Performance

Page-load profile

Phase Approx. duration Notes
HTML download <100 ms (CDN) index.html is small.
jQuery + Mustache 100–300 ms Depends on the CDN used by the templates.
js/githubresume.js <50 ms parse ~19 KB minified would be smaller, but the upstream commits the full unminified source.
Star check (synchronous) 200–800 ms First page of starred repos. Adds ~200 ms per additional 100 starred repos until the project repo is found.
Profile / repos / PRs / orgs 200–600 ms each Run in parallel, so the bottleneck is the slowest of the four.
Mustache render <50 ms Trivial DOM injection.

The dominant variable is the synchronous star check. Users who star many repositories will experience longer page loads on first render.

Optimisation ideas (not in upstream)

  • Replace the synchronous $.ajax({ async: false }) call with an asynchronous call and use a loader spinner instead of blocking the thread.
  • Cache the GitHub API responses in localStorage keyed by username + date, with a five-minute TTL.
  • Defer Google Analytics with async/defer script attributes.
  • Strip unused images from the deployment (images/srt.png, images/ttab.png are referenced by templates; verify before removing).

Tip

If you fork and want to make the page faster without rewriting the star-check, simply move the call to async and show the loader image from images/loader.gif in the meantime. The synchronous design was a pragmatic 2016 choice that a modern browser flags as a performance warning.

7. Security

Security analysis for resume.github.com is unusually simple because there is no backend, no authentication, and no first-party data store. The threat surface is the GitHub REST API and the static files themselves. This section covers all of it.

Trust boundary

The browser running js/githubresume.js is the trust boundary. All data displayed comes from one of three sources:

  1. The static files served by your host — trusted.
  2. GitHub's REST API (api.github.com) — trusted up to whatever public profile data GitHub returns for the named user.
  3. The URL query string controlled by the visitor — untrusted.

What the project does NOT expose

  • No personally-identifying information beyond what GitHub already exposes publicly via the REST API.
  • No private repositories, private gists, private email addresses, or authenticated organisation details.
  • No first-party cookies (Google Analytics drops its own).
  • No POST endpoints. Every API call is GET, and the project does not ship a single line of mutation logic.

What the project DOES expose

  • The ?username query parameter is a public input that any visitor can manipulate. The username flows directly into multiple URLs:

    url = 'https://api.github.com/users/' + username + '/repos?per_page=100&callback=?'
    

    GitHub's API performs its own input validation server-side, so a malicious username (e.g. URL-encoded special characters) will most likely return a 404 rather than reflect into anything dangerous.

  • The username value is also injected into rendered HTML via Mustache:

    toString: function() {
        return '<a href="https://github.com/search?q=user%3A'
            + username + '&l=' + encodeURIComponent(this.name) + '">'
            + this.name + '</a>';
    }
    

    Mustache by default escapes interpolated {{ }} values. However, the toString() callback above builds the link with string concatenation, so the username is not escaped at this point.

Warning

The string-concatenated <a href="...?q=user%3A' + username + …'"> path is a potential reflected-XSS vector if username ever contains a quote character. In practice, GitHub usernames are restricted to [A-Za-z0-9-], so this is a latent risk rather than an exploitable one — but a fork that loosens the input contract should add explicit escaping. Use $('<a/>').attr('href', …).text(name) instead of string concatenation.

GitHub API rate-limit and token handling

The upstream makes all GitHub API calls unauthenticated. There is no token in the source, no token-passing UI, and no localStorage key for one. Operators forking the project sometimes consider adding a personal-access token to raise the rate limit; this is strongly discouraged for any public deployment.

Caution

Embedding a personal-access token in client-side JavaScript is equivalent to publishing the token. Anyone who views the page source can extract and use it. Even a token scoped to public_repo is a real account credential and is logged in GitHub's audit trail. Do not do this. If you need authenticated rate limits, build a server-side proxy with a token stored in environment variables and forward only specific GET endpoints; do not expose the token to the browser.

Public-data-only assumption

The project assumes every input maps to a public GitHub user. There is no permission model beyond GitHub's own — if a user's profile is private, the API will return 404 and the site will show the Not found template. Private repositories, gists, and PRs are simply absent from the API responses, so the rendered résumé reflects only what GitHub already publishes openly.

Content Security Policy

There is no <meta http-equiv="Content-Security-Policy"> in the repository. A reasonable hardened CSP for a fork would be:

default-src 'self';
script-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com
           https://www.google-analytics.com https://ssl.google-analytics.com;
style-src 'self' 'unsafe-inline';
img-src 'self' https://avatars.githubusercontent.com
           https://secure.gravatar.com https://assets-cdn.github.com
           https://www.google-analytics.com data:;
connect-src https://api.github.com https://www.google-analytics.com;

Important

The script-src directive must explicitly allow whichever CDN your templates load jQuery and Mustache from. The upstream does not name a specific CDN — inspect the rendered HTML to discover the URLs in production.

Things you should NOT expose

  • A token of any kind in client-side JS or in the static files.
  • A custom backend that proxies the GitHub API without rate limiting, request-size limits, and abuse logging — you will become a botnet amplifier.
  • Visitor IP addresses or query strings beyond what your hosting access log already records. Do not add custom analytics that capture the ?username parameter for users who did not opt in.

Dependencies — supply-chain considerations

The repository commits no JavaScript dependencies. They are loaded at runtime by the HTML templates from CDNs. In 2026, that means:

  • jQuery — pin to a known-good version on a trusted CDN, ideally with Subresource Integrity (integrity="sha384-...").
  • Mustache — same.
  • https://www.google-analytics.com/ga.js — the classic GA loader, deprecated by Google. Replace with gtag.js or remove entirely on any new deployment.

Caution

The upstream ga.js loader is loaded over both HTTP and HTTPS depending on the page protocol, with no Subresource Integrity. A compromise of the GA CDN or a downgrade attack on a non-HTTPS host would let an attacker run arbitrary JavaScript in the user's browser session. Always serve over HTTPS and consider removing the classic GA loader on modern forks.

8. Developer Internals

This section is the source-level reference. It covers structure, code patterns, function-by-function semantics, and a contributing guide.

Repository structure

resume.github.com/
├── README.markdown          ── Repository introduction (briefing did not extract its content)
├── config.ru                ── Two-line Rack runner for local dev
├── index.html               ── Page shell, hosts <div id="resume">
├── css/
│   ├── print.css            ── @media print overrides
│   └── resume.css           ── Screen styling
├── images/
│   ├── favicon.png          ── Browser tab icon
│   ├── loader.gif           ── Spinner shown during async loads
│   ├── low_contrast_linen.png  ── Background texture
│   ├── srt.png              ── Asset referenced by template (verify before removing)
│   └── ttab.png             ── Asset referenced by template (verify before removing)
├── js/
│   └── githubresume.js      ── The single front-end module
└── views/
    ├── api_limit.html       ── Shown on 403 from GitHub API
    ├── contrib.html         ── Single contribution row template
    ├── error.html           ── Generic error screen
    ├── index.html           ── Default landing screen
    ├── job.html             ── Single project row template
    ├── not_found.html       ── Shown on 404 from GitHub API
    ├── opt_out.html         ── Shown when subject has not starred the repo
    ├── org.html             ── Single organisation row template
    ├── resume.html          ── User résumé layout
    └── resumeOrgs.html      ── Organisation résumé layout

Note

views/index.html and the top-level index.html are different files. The top-level index.html is the page shell that boots JavaScript. The views/index.html is the partial Mustache template that the home() function loads into the #resume div when no username is supplied. Don't confuse them.

Module reference — js/githubresume.js

The entire front end lives in this one ~19 KB module. It is structured as a top-level IIFE that parses the URL, then a $(document).ready handler that branches on whether a username is present. Below is every function, in source order.

URL parser IIFE (top of file)

Anonymous immediately-invoked function expression (IIFE). Walks window.location.search once and stores the last parsed key in urlParams[0]. The variable is read as the username when $(document).ready fires.

var urlParams = {};
(function () {
    var e, a = /\+/g, r = /([^&=]+)=?([^&]*)/g,
        d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
        q = window.location.search.substring(1);
    while (e = r.exec(q)) {
       urlParams[0] = d(e[1]);
    }
})();

Note

The assignment urlParams[0] = d(e[1]); overwrites on every loop iteration. With a single key like ?octocat, this is fine. With ?a=1&b=2, urlParams[0] ends as b. This is a pre-existing behaviour and not a bug to fix without coordination — many forks rely on the single-key idiom.

error()

Loads views/error.html into the #resume element. Bound to the global window error event at the bottom of the file: $(window).bind('error', error);. Any unhandled JavaScript error replaces the page contents with this template.

home()

Loads views/index.html into the #resume element. Called when no username is supplied.

github_user(username, callback)

JSONP fetch of https://api.github.com/users/<username>?callback=?. Calls back with the entire response object. Note that GitHub's legacy JSONP responses wrap the user record in a data envelope; the downstream consumer accesses data.data.created_at, data.data.name, etc.

github_user_repos(username, callback, page_number, prev_data)

Recursive paginated JSONP fetch of https://api.github.com/users/<username>/repos?per_page=100. Continues to the next page when repos.data.length == 100, calls back with the accumulated array when shorter. Pages are 1-indexed; the recursion appends &page=<n> for pages > 1.

github_user_issues(username, callback, page_number, prev_data)

Recursive paginated JSONP fetch of https://api.github.com/search/issues?q=type:pr+is:merged+author:<user>&per_page=100. Note the recursion condition if (repos.data.total_count == 100) is unusual — it tests the total count, not the page count, which will recurse forever if the user has exactly 100 merged PRs total.

Warning

The merged-PRs paginator has a known bug: the stop condition repos.data.total_count == 100 should be repos.data.items.length == 100. Forks should fix this; the upstream behaviour is to recurse until the server returns an empty page or returns a different total. Verify the behaviour with users in the 100-PR range before relying on it.

github_user_orgs(username, callback)

Single JSONP fetch of https://api.github.com/users/<username>/orgs?callback=?. No pagination.

github_user_starred_resume(username, page)

Synchronous AJAX (async: false) fetch of https://api.github.com/users/<username>/starred?per_page=100&page=<n>. This is the function that blocks the page during the opt-in check.

Behaviour:

  • Iterates each repo in the response.
  • Returns true immediately if it sees repo.full_name == "resume/resume.github.com".
  • Returns the string 'api_limit' on HTTP 403.
  • Returns the string 'not_found' on HTTP 404.
  • Recurses with page + 1 if the page returned exactly 100 entries.
  • Returns false once the recursion exhausts all pages without finding the project repo.

run()

The orchestrator. Called from $(document).ready when a username is present. Performs the synchronous star check, branches to api_limit.html, not_found.html, or opt_out.html if the check returns a non-truthy result, otherwise launches the four parallel JSONP calls (github_user, github_user_repos, github_user_issues, github_user_orgs) and wires their results into Mustache templates. Inside run() is also the nested getUserStatus() function described in §3.

The Mustache template chosen for the main résumé block depends on account type:

var resume = (data.type == 'User' ? 'views/resume.html' : 'views/resumeOrgs.html');

Google Analytics block (bottom of file)

Classic ga.js loader. Switches between https://ssl.google-analytics.com/ga.js and http://www.google-analytics.com/ga.js based on document.location.protocol. Skipped entirely when trackerId is falsy.

Code patterns and conventions

Pattern Where it appears Notes
IIFE for parser URL parser Common 2016-era idiom.
Recursive pagination via callback github_user_repos, github_user_issues, github_user_starred_resume All three use the same shape: pass page_number and prev_data through, recurse on a length-based stop condition.
Mustache to_html(template, view) All template rendering Loaded as a global.
$('#id').html(data) Template injection Replaces the inner HTML of the host element.
Magic strings for state 'api_limit', 'not_found' Returned from github_user_starred_resume and re-checked in run().

Known issues and tech debt

  • Synchronous AJAX in github_user_starred_resume — blocks the page; modern browsers print a deprecation warning.
  • Pagination bug in github_user_issues — uses total_count instead of items length.
  • Shared itemCount between projects and orgs — silently truncates organisations.
  • urlParams[0] overwritten in a loop — only the last key survives.
  • Classic GA ga.js loader — long deprecated by Google in favour of gtag.js / GA4.
  • No package manifest — jQuery and Mustache versions are uncontrolled (loaded from whatever CDN the templates reference).
  • No CNAME file in the repo, despite production use of a custom domain. The upstream relies on the GitHub Pages default mapping.
  • No issue history — the briefing reports zero open or closed issues fetched at this commit, so this guide cannot reference real issue numbers. Operators should track new issues at https://github.com/resume/resume.github.com/issues going forward.

Testing strategy

The repository commits no test suite. The functional contract is small enough to manually verify with a handful of usernames. A reasonable manual test plan:

  • [ ] ? (no username) → landing page renders.
  • [ ] ?<known-active-user> → résumé renders with all five blocks.
  • [ ] ?<known-inactive-user> → résumé renders with empty languages and Inactive status label.
  • [ ] ?<user-without-star> → opt-out screen renders.
  • [ ] ?<nonexistent> → not-found screen renders.
  • [ ] ?<known-org> → organisation résumé renders with the org template.
  • [ ] Print preview from any rendered résumé → clean print layout.

For automated coverage on a fork, a Playwright or Cypress smoke test that exercises each branch is sufficient.

Contributing guide

The upstream has been quiet for years. If you intend to contribute back:

  1. Open an issue first describing the change. Without an issue, drive-by PRs may not be reviewed.
  2. Keep changes minimal. The project is intentionally tiny.
  3. Maintain ES5 compatibility unless you also bring a build step.
  4. Test by serving rackup config.ru and exercising a handful of real usernames as listed above.
  5. Update README.markdown if a contributor-visible behaviour changes.

For private forks, treat the upstream as a starting point and feel free to modernise (ES modules, fetch instead of jQuery AJAX, gtag.js, etc.).

9. Integration Points

This section enumerates every external service the project touches and how to swap or extend each.

Outbound integrations

Target Endpoint Auth Purpose
GitHub REST API — users https://api.github.com/users/<user> None (public) Fetch profile.
GitHub REST API — repos https://api.github.com/users/<user>/repos None (public) Fetch non-fork repositories for the projects block and language histogram.
GitHub REST API — orgs https://api.github.com/users/<user>/orgs None (public) Fetch organisation memberships.
GitHub REST API — starred https://api.github.com/users/<user>/starred None (public) Opt-in check.
GitHub Search API — issues https://api.github.com/search/issues?q=type:pr+is:merged+author:<user> None (public) Fetch merged PRs.
Gravatar https://secure.gravatar.com/avatar/<hash> None Used for organisation avatars (extracted from the data.avatar_url regex match).
GitHub Emoji CDN https://assets-cdn.github.com/images/icons/emoji/<name>.png None Replaces :emoji: shortcodes in repo descriptions.
Google Analytics https://ssl.google-analytics.com/ga.js or http://www.google-analytics.com/ga.js None (key is UA-21222559-1) Page-view tracking.
External CDNs jQuery, Mustache None Loaded by the HTML templates; exact CDN inferred from rendered HTML.

Replacing GitHub Analytics with GA4

To migrate to GA4, replace the entire trailing if (trackerId) block in js/githubresume.js with a gtag.js snippet:

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXX');
</script>

Or remove analytics entirely by deleting the block and the trackerId variable.

Replacing the GitHub data source

The four github_user_* functions are the single integration boundary. A fork that wants to render résumés from a different code-hosting platform (GitLab, Gitea, etc.) needs to:

  1. Reimplement each github_user_* function against the target API.
  2. Reshape the response to match the field names the Mustache templates expect (see §3 for the variable list).
  3. Decide what the equivalent of "starring the repo" is on the target platform — without it, every fork has no opt-in mechanism.

Adding a server-side proxy

If you need authenticated rate limits without exposing a token, add a small server-side proxy that:

  1. Accepts only the GET endpoints listed in the table above.
  2. Forwards them to api.github.com with an Authorization: token <pat> header read from a server-side environment variable.
  3. Caches responses for ~5 minutes.
  4. Returns CORS headers permitting your origin only.

The front end then only needs to change the URL prefix from https://api.github.com to your proxy's domain. The rest of js/githubresume.js is unaffected.

Hosting integrations

The repository works on any static host. The most common production configurations are:

Host Configuration Notes
GitHub Pages Repo → Settings → Pages → Source = master branch Matches upstream.
Netlify netlify.toml not required; default static deploy works Add a _redirects file if you want clean URLs.
Vercel vercel.json not required Add cleanUrls: true for /?user to render index.html automatically.
Cloudflare Pages Default static build Effectively zero config.
nginx try_files $uri $uri/ /index.html; Required so /?user correctly serves index.html.
Apache DirectoryIndex index.html Default behaviour suffices for most installs.

Note

The query-string-as-input pattern means the path always resolves to /. There are no nested routes to map. Single-page-app fallback rules (try_files, cleanUrls) are not strictly required, but they protect against accidentally serving a 404 if a user trims the trailing / from the URL.

Browser integrations

The project depends on the browser's native APIs:

  • window.location.search for the query string.
  • window.print() for the print button.
  • document.title for the dynamic title.
  • console.log for the silent error sink.
  • Date for year computation.

No navigator features (geolocation, clipboard, service workers) are used. The project will run in any browser with ES5 support.