Files
spoopyghosty0--frameport/site/scripts/contributors.mjs
T
spoopyghosty0 f631e5fc1b Project page round 3: docs pages, contributors, carousel, a fuller app demo
- Hero: the Frame-side card is the game running in the headset (one view per eye, the scene moving), switching on
  as it lands while its frame rate climbs to 72 and the pacing line draws.
- App demo: working search, right-click menu, select several and install them (queued), install from a link (the
  app's link rules), Screenshots tab with a viewer, Settings with the real themes switching live, Files that take
  real files from the desktop (they never leave the tab), process table with End, Live view quality, launch-test
  result, technical patch ids, toasts; Wi-Fi or USB cable when connecting. Progress ticks no longer rebuild the page.
- Screenshots: a carousel (arrows, keys, swipe, thumbnails, autoplay that pauses for the reader, full screen).
- Compared: four headline differences (free and open source first), every difference as user questions behind
  "Show all differences"; the README says the same.
- Wi-Fi or USB cable said everywhere; the setup command explains itself part by part; first-start steps per system.
- Field notes removed, the notice kept in its own band.
- Built on: contributors wall (code, pull requests, issues; scripts/contributors.mjs, saved list as fallback).
- Docs: user-facing docs/*.md as pages (links rewritten, GitHub heading ids, contents with scroll position, copy
  buttons).
- Games board rows open to show the recipe; the install log replays with another tested game.
2026-10-09 22:29:55 -04:00

53 lines
2.4 KiB
JavaScript

// Everyone who contributed to FramePort on GitHub: code (the contributors list) and issues / pull requests (their
// authors). Writes src/data/contributors.json. Runs before each build; without network (or over the rate limit) the
// committed file stays as it is, so the site still builds. GITHUB_TOKEN (set in CI) raises the limit.
import { readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const REPO = 'spoopyghosty0/frameport';
const OUT = join(dirname(fileURLToPath(import.meta.url)), '..', 'src', 'data', 'contributors.json');
const headers = { Accept: 'application/vnd.github+json', 'User-Agent': 'frameport-site' };
if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
async function pages(path) {
const all = [];
for (let page = 1; page <= 30; page++) {
const r = await fetch(`https://api.github.com/repos/${REPO}/${path}${path.includes('?') ? '&' : '?'}per_page=100&page=${page}`,
{ headers, signal: AbortSignal.timeout(15000) });
if (!r.ok) throw new Error(`${r.status} ${path}`);
const items = await r.json();
all.push(...items);
if (items.length < 100) break;
}
return all;
}
const isBot = (u) => !u || u.type === 'Bot' || u.login.endsWith('[bot]');
try {
const people = new Map();
const person = (u) => {
if (!people.has(u.login)) {
people.set(u.login, { login: u.login, avatar: u.avatar_url, url: u.html_url, commits: 0, issues: 0, prs: 0 });
}
return people.get(u.login);
};
for (const c of await pages('contributors')) if (!isBot(c)) person(c).commits += c.contributions;
for (const i of await pages('issues?state=all')) {
if (isBot(i.user)) continue;
if (i.pull_request) person(i.user).prs++;
else person(i.user).issues++;
}
const list = [...people.values()]
.map((p) => ({ ...p, total: p.commits + p.issues * 3 + p.prs * 5 }))
.sort((a, b) => b.total - a.total || a.login.localeCompare(b.login));
const data = { updated: new Date().toISOString().slice(0, 10), people: list };
writeFileSync(OUT, JSON.stringify(data, null, 1) + '\n');
console.log(`contributors: ${list.length} people`);
} catch (e) {
let kept = 0;
try { kept = JSON.parse(readFileSync(OUT, 'utf8')).people.length; } catch { /* none yet */ }
console.warn(`contributors: kept the saved list (${kept} people): ${e.message}`);
}