openvibers/openvibe-git Commits

Official mirrors: OpenVibers is a user account, list it as one (#10)

OP OpenVibers committed 4 days ago · a4c89c68ce · 2 files, +34 −2

* Official mirrors: list a user account's repositories when the name is not an organisation

OpenVibers is a GitHub user account; /orgs/OpenVibers/repos answers 404, so no official mirror was ever created. A 404 on the first org page retries /users/<name>/repos?type=owner; the seed still keeps only public, non-fork, non-archived repositories.

* Official mirrors: the listing fills the metadata cache, so mirroring costs no call per repository

Changes

ddiff --git a/server/github.js b/server/github.js
iindex b697144..5c3b436 100644
---- a/server/github.js
++++ b/server/github.js
@@@ -103,15 +103,27 @@ function createGithubClient({ config, fetchImpl = globalThis.fetch, now = () =>
} catch { return null; }
}
-- /** Every public repository of an organisation, following GitHub's Link pagination. */
++ /**
++ * Every public repository of an organisation (or of a user account: OpenVibers is one, and GitHub answers 404 for
++ * /orgs/<user>/repos, 11-09), following GitHub's Link pagination.
++ */
async function orgRepos(org, { perPage = 100, maxPages = 100 } = {}) {
const apiOrigin = new URL(config.github.apiUrl).origin;
let url = `${config.github.apiUrl}/orgs/${encodeURIComponent(org)}/repos?type=public&per_page=${perPage}`;
++ let triedUser = false;
const all = [];
for (let page = 0; url && page < maxPages; page += 1) {
const current = new URL(url);
if (current.origin !== apiOrigin) throw new Error('GitHub pagination left the API origin');
const res = await fetchImpl(current.toString(), { headers: headers(), signal: AbortSignal.timeout(15_000) });
++ if (res.status === 404 && page === 0 && !triedUser) {
++ // Not an organisation: the same name as a user account. type=owner lists what it owns; private ones
++ // never appear without that account's token, and the seed keeps only public repositories anyway.
++ triedUser = true;
++ url = `${config.github.apiUrl}/users/${encodeURIComponent(org)}/repos?type=owner&per_page=${perPage}`;
++ page -= 1;
++ continue;
++ }
if (!res.ok) {
const err = new Error(`GitHub ${res.status} for ${current.pathname}`);
err.status = res.status;
@@@ -119,6 +131,15 @@ function createGithubClient({ config, fetchImpl = globalThis.fetch, now = () =>
}
const batch = await res.json();
if (!Array.isArray(batch)) throw new Error('GitHub returned no repository list');
++ // The listing carries what meta() would ask for: cache it, so mirroring 70 repositories costs a page or two
++ // of the API, not one call each (the host has 60 an hour without a token).
++ for (const repo of batch) {
++ if (!repo || !repo.full_name) continue;
++ metaCache.set(String(repo.full_name).toLowerCase(), { at: now(), meta: {
++ full_name: repo.full_name, description: repo.description || '', default_branch: repo.default_branch || 'main',
++ language: repo.language || null, stars: Number(repo.stargazers_count) || 0, private: !!repo.private,
++ } });
++ }
all.push(...batch);
url = nextLink(res.headers && typeof res.headers.get === 'function' ? res.headers.get('link') : null);
}
ddiff --git a/test/official.test.js b/test/official.test.js
iindex 6c89595..9d11100 100644
---- a/test/official.test.js
++++ b/test/official.test.js
@@@ -36,6 +36,9 @@ const LISTING = {
const link = page === 1 ? '<https://api.github.com/orgs/OpenVibers/repos?type=public&per_page=100&page=2>; rel="next"' : null;
return response(LISTING[page] || [], link);
}
++ // A user account, not an organisation: GitHub answers 404 for /orgs/<name>/repos (OpenVibers is one, 11-09).
++ if (target.pathname === '/orgs/Solo/repos') return { ok: false, status: 404, headers: { get: () => null }, json: async () => ({ message: 'Not Found' }) };
++ if (target.pathname === '/users/Solo/repos') return response([{ name: 'solo-site', full_name: 'Solo/solo-site', fork: false, archived: false, private: false, clone_url: 'https://github.com/Solo/solo-site.git' }], null);
const meta = /^\/repos\/OpenVibers\/([^/]+)$/.exec(target.pathname);
if (meta) return response({ full_name: `OpenVibers/${meta[1]}`, description: '', default_branch: 'main', private: false, stargazers_count: 0 });
throw new Error(`unexpected GitHub request ${url}`);
@@@ -123,6 +126,14 @@ const LISTING = {
assert.equal((await pending).ran, true);
t.ctx.github.orgRepos = realOrgRepos;
-- console.log('official: 30 checks passed');
++ // A user account's repositories: the org listing 404s, the user listing answers.
++ const solo = await t.ctx.github.orgRepos('Solo');
++ assert.deepEqual(solo.map((r) => r.full_name), ['Solo/solo-site']);
++ assert.ok(calls.some((c) => c.url.includes('/users/Solo/repos?type=owner')), 'falls back to /users/<name>/repos');
++ const before = calls.length;
++ assert.equal((await t.ctx.github.meta('Solo/solo-site')).default_branch, 'main');
++ assert.equal(calls.length, before, 'the listing fills the metadata cache: no call per repository');
++
++ console.log('official: 34 checks passed');
} finally { await t.close(); }
})().catch((err) => { console.error(err); process.exit(1); });