Fetch contributors data at build time

It's currently easy to hit the GitHub API's rate limit just by refreshing the credits page a couple of times. The data doesn't change that frequently, so instead fetch it into a JSON file as part of the build process, have Hugo generate the page using the JSON file and a template, and deploy the site each day at midnight so that the live data is at most 24 hours old.
This commit is contained in:
Oliver Hamlet
2026-01-11 22:57:28 +00:00
parent ffc5665b4e
commit aa66a10fd8
6 changed files with 1246 additions and 56 deletions
+7
View File
@@ -4,6 +4,8 @@ on:
push:
branches:
- master
schedule:
- cron: 0 0 * * *
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
@@ -50,6 +52,11 @@ jobs:
- name: Install Node.js dependencies
run: npm ci
- name: Fetch contributors data
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node generate_contributors_json.js
- name: Build with Hugo
env:
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
+8
View File
@@ -14,3 +14,11 @@ The website is generated using [Hugo](https://gohugo.io), and uses [Material Des
Hugo will create the generated site in the `public` folder.
To view the site locally, run `hugo server`, the site will then be accessible at <http://localhost:1313>.
## Regenerating contributor data
The contributors data is stored in a JSON file that can be regenerated by running `node generate_contributors_json.js`.
Fetching the data uses the GitHub REST API to query the loot organization's repositories, and then fetch the contributor data for each, so running the script frequently can hit GitHub's rate limits. A personal access token can be supplied in the `GITHUB_TOKEN` environment variable to benefit from the higher rate limits used for authenticated API access: only read access to public repositories is needed.
The script is run as part of the website's GitHub Actions deployment workflow, which runs daily at midnight UTC, so <https://loot.github.io/credits/> won't show any contributions made in the time since the last deployment.
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -1,9 +1,6 @@
---
layout: page
layout: credits
title: Credits
---
<p>LOOT relies on the submission of information by users to improve. We can't list everyone who has posted in LOOT's forum threads, but the people who have contributed on GitHub are listed below.
<div id="contrib">
<div id="progress" class="mdl-spinner mdl-js-spinner mdl-spinner--single-color is-active"></div>
</div>
@@ -1,4 +1,5 @@
import { Octokit } from '/js/octokit.js';
import { Octokit } from './assets/js/octokit.js';
import { writeFile } from 'fs/promises';
// Commit 37e464f6001d440ae532516f40fe19162537475a and earlier are common
// between the loot and libloot repositories, so are double-counted by GitHub.
@@ -122,42 +123,6 @@ const contributorMapping = new Map([
['David Tan', ['Velgus', 'David']],
]);
function addToList(listElement, person) {
const a = document.createElement('a');
const avatar = document.createElement('div');
const text = document.createElement('div');
const name = document.createElement('div');
const contributions = document.createElement('div');
a.classList.add('loot-contributor', 'mdl-shadow--2dp');
avatar.className = 'loot-contributor__avatar';
text.className = 'loot-contributor__text';
name.className = 'loot-contributor__name';
contributions.className = 'loot-contributor__contributions';
a.href = person.html_url;
name.textContent = person.name;
contributions.textContent = person.contributions + ' contributions';
if (person.avatar_url) {
const img = document.createElement('img');
img.src = person.avatar_url;
avatar.appendChild(img);
} else {
const icon = document.createElement('i');
icon.className = 'material-icons';
icon.textContent = 'face';
avatar.appendChild(icon);
}
a.appendChild(avatar);
a.appendChild(text);
text.appendChild(name);
text.appendChild(contributions);
listElement.appendChild(a);
}
function sortContributors(a,b) {
if (a.contributions != b.contributions) {
return b.contributions - a.contributions;
@@ -186,17 +151,6 @@ function statsReducer(previous, current) {
return previous;
}
function displayContributorsStats(contributorsStats) {
contributorsStats.sort(sortContributors);
const contrib = document.getElementById('contrib');
contrib.removeChild(contrib.firstElementChild);
for (const contributor of contributorsStats) {
addToList(contrib, contributor);
}
}
function getContributorsStats(contributors) {
return contributors
.map(getStats)
@@ -241,7 +195,7 @@ function fixStats(contributorsStats) {
}
async function getContributors() {
const octokit = new Octokit();
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const repos = await octokit.paginate(octokit.rest.repos.listForOrg, {
org: 'loot'
@@ -258,14 +212,18 @@ async function getContributors() {
return Promise.all(promises).then(results => results.flat());
}
async function getAndDisplayContributorsStats() {
async function main() {
const contributors = await getContributors();
const stats = getContributorsStats(contributors);
const fixedStats = fixStats(stats);
displayContributorsStats(fixedStats);
fixedStats.sort(sortContributors);
const json = JSON.stringify(fixedStats, null, 2);
await writeFile('./content/credits/contributors.json', json);
}
getAndDisplayContributorsStats();
await main();
+22
View File
@@ -0,0 +1,22 @@
{{ define "main" }}
{{ .Content }}
<div id="contrib">
{{ $contributors := .Resources.Get "contributors.json" | transform.Unmarshal }}
{{ range $contributors }}
<a href="{{ .html_url }}" class="loot-contributor mdl-shadow--2dp">
<div class="loot-contributor__avatar">
{{- if .avatar_url }}
<img src="{{ .avatar_url }}"/>
{{- else }}
<i class="material-icons">face</i>
{{- end }}
</div>
<div class="loot-contributor__text">
<div class="loot-contributor__name">{{ .name }}</div>
<div class="loot-contributor__contributions">{{ .contributions }} contributions</div>
</div>
</a>
{{ end }}
</div>
{{ end }}