diff --git a/.gitea/scripts/publish-release.mjs b/.gitea/scripts/publish-release.mjs index fb97add..66ebf10 100644 --- a/.gitea/scripts/publish-release.mjs +++ b/.gitea/scripts/publish-release.mjs @@ -16,6 +16,21 @@ const VERSION = SHA.slice(0, 8); const API = `${SERVER}/api/v1/repos/${REPO}`; const H = { Authorization: `token ${TOKEN}` }; +// The runner's network to Gitea is flaky (ECONNRESET mid-publish leaves a +// half-created release). Retry transient fetch failures so the multi-step +// publish is atomic-enough in practice. +const rfetch = async (url, opts = {}, tries = 4) => { + for (let i = 1; ; i++) { + try { + return await fetch(url, opts); + } catch (e) { + if (i >= tries) throw e; + console.log(`fetch ${url} failed (${e.cause?.code || e.message}); retry ${i}/${tries - 1}`); + await new Promise((r) => setTimeout(r, 1000 * i)); + } + } +}; + const ok = async (r) => { if (!r.ok) throw new Error(`${r.status} ${r.url}\n${await r.text()}`); const t = await r.text(); @@ -31,14 +46,14 @@ const files = { }; // Roll the release forward to this commit: delete the old release + tag. -const existing = await fetch(`${API}/releases/tags/${TAG}`, { headers: H }); +const existing = await rfetch(`${API}/releases/tags/${TAG}`, { headers: H }); if (existing.ok) { const rel = await existing.json(); - await fetch(`${API}/releases/${rel.id}`, { method: 'DELETE', headers: H }); + await rfetch(`${API}/releases/${rel.id}`, { method: 'DELETE', headers: H }); } -await fetch(`${API}/tags/${TAG}`, { method: 'DELETE', headers: H }); // ignore if absent +await rfetch(`${API}/tags/${TAG}`, { method: 'DELETE', headers: H }); // ignore if absent -const rel = await ok(await fetch(`${API}/releases`, { +const rel = await ok(await rfetch(`${API}/releases`, { method: 'POST', headers: { ...H, 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -53,7 +68,7 @@ const rel = await ok(await fetch(`${API}/releases`, { for (const [name, buf] of Object.entries(files)) { const fd = new FormData(); fd.append('attachment', new Blob([buf]), name); - await ok(await fetch(`${API}/releases/${rel.id}/assets?name=${encodeURIComponent(name)}`, { + await ok(await rfetch(`${API}/releases/${rel.id}/assets?name=${encodeURIComponent(name)}`, { method: 'POST', headers: H, body: fd, })); console.log(`uploaded ${name}`);