Fail mirror runs on API and migration errors

This commit is contained in:
2026-09-21 08:43:03 -07:00
parent 8f917d9ac6
commit 988acf7bdd
+19 -19
View File
@@ -6,25 +6,24 @@ const headers = {
GT: { Authorization: `token ${GITEA_TOKEN}`, 'Content-Type': 'application/json' } GT: { Authorization: `token ${GITEA_TOKEN}`, 'Content-Type': 'application/json' }
}; };
const req = async (url, h, m = 'GET', b = null) => { const req = async (url, h, m = 'GET', b = null, allow404 = false) => {
try { const res = await fetch(url, { method: m, headers: h, body: b ? JSON.stringify(b) : null });
const res = await fetch(url, { method: m, headers: h, body: b ? JSON.stringify(b) : null }); if (res.status === 404 && allow404) return null;
if (!res.ok && m !== 'GET') { if (!res.ok) {
const txt = await res.text(); const remaining = res.headers.get('x-ratelimit-remaining');
throw new Error(`${res.status} ${res.statusText}: ${txt}`); const reset = res.headers.get('x-ratelimit-reset');
} const limit = remaining === null ? '' : ` (GitHub rate limit remaining: ${remaining}, reset: ${reset})`;
return res.ok ? (m === 'GET' ? res.json() : res) : null; throw new Error(`${res.status} ${res.statusText}${limit}: ${(await res.text()).slice(0, 500)}`);
} catch (e) {
if (m !== 'GET') throw e;
return null;
} }
return m === 'GET' ? res.json() : res;
}; };
const getPages = async (url) => { const getPages = async (url) => {
let p = 1, all = [], d; let p = 1, all = [], d;
do { do {
d = await req(`${url}${url.includes('?') ? '&' : '?'}per_page=100&page=${p++}`, headers.GH); d = await req(`${url}${url.includes('?') ? '&' : '?'}per_page=100&page=${p++}`, headers.GH);
if (Array.isArray(d)) all.push(...d); if (!Array.isArray(d)) throw new Error(`Expected a list from ${url}`);
all.push(...d);
} while (d?.length === 100); } while (d?.length === 100);
return all; return all;
}; };
@@ -42,12 +41,12 @@ const getPages = async (url) => {
]); ]);
let allRepos = [...ghUserRepos]; let allRepos = [...ghUserRepos];
let avatarFailures = 0; let avatarFailures = 0, repoFailures = 0;
for (const org of ghOrgs) { for (const org of ghOrgs) {
console.log(`Checking Org: ${org.login}`); console.log(`Checking Org: ${org.login}`);
try { try {
const gOrg = await req(`${G_API}/orgs/${org.login}`, headers.GT); const gOrg = await req(`${G_API}/orgs/${org.login}`, headers.GT, 'GET', null, true);
if (!gOrg) { if (!gOrg) {
console.log(`Creating Org: ${org.login}`); console.log(`Creating Org: ${org.login}`);
await req(`${G_API}/orgs`, headers.GT, 'POST', { username: org.login, visibility: 'public' }); await req(`${G_API}/orgs`, headers.GT, 'POST', { username: org.login, visibility: 'public' });
@@ -66,7 +65,7 @@ const getPages = async (url) => {
const orgRepos = await getPages(`https://api.github.com/orgs/${org.login}/repos?type=all`); const orgRepos = await getPages(`https://api.github.com/orgs/${org.login}/repos?type=all`);
allRepos.push(...orgRepos); allRepos.push(...orgRepos);
} catch (e) { } catch (e) {
console.error(`Failed to process org ${org.login}:`, e.message); throw new Error(`Failed to process org ${org.login}: ${e.message}`);
} }
} }
@@ -74,7 +73,7 @@ const getPages = async (url) => {
for (const r of allRepos) { for (const r of allRepos) {
try { try {
const owner = r.owner.login; const owner = r.owner.login;
const exists = await req(`${G_API}/repos/${owner}/${r.name}`, headers.GT); const exists = await req(`${G_API}/repos/${owner}/${r.name}`, headers.GT, 'GET', null, true);
if (!exists) { if (!exists) {
console.log(`Mirroring: ${owner}/${r.name}`); console.log(`Mirroring: ${owner}/${r.name}`);
@@ -102,16 +101,17 @@ const getPages = async (url) => {
await req(`${G_API}/repos/${owner}/${r.name}`, headers.GT, 'PATCH', { private: r.private }); await req(`${G_API}/repos/${owner}/${r.name}`, headers.GT, 'PATCH', { private: r.private });
} }
} catch (e) { } catch (e) {
repoFailures++;
console.error(`Failed to mirror ${r.owner.login}/${r.name}:`, e.message); console.error(`Failed to mirror ${r.owner.login}/${r.name}:`, e.message);
} }
} }
if (avatarFailures) { if (avatarFailures || repoFailures) {
console.error(`Failed to sync ${avatarFailures} organization avatar(s).`); console.error(`Sync finished with ${avatarFailures} avatar failure(s) and ${repoFailures} repository failure(s).`);
process.exitCode = 1; process.exitCode = 1;
} }
} catch (err) { } catch (err) {
console.error('Fatal Error:', err); console.error('Fatal Error:', err);
process.exit(1); process.exit(1);
} }
console.log('Sync Complete.'); if (!process.exitCode) console.log('Sync Complete.');
})(); })();