generated from CubeCraft-Creations/Tracehound
18db26c265
The publish step died with "fetch failed: ECONNRESET" mid-run, leaving a half-created release (no version.txt asset → the Pi got 404s). Wrap the Gitea API calls in a small retry (rfetch) so a flaky connection doesn't leave the rolling release incomplete. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
77 lines
2.7 KiB
JavaScript
77 lines
2.7 KiB
JavaScript
// Publish the built hub binary to a rolling "dev" release on Gitea.
|
|
// Runs in the CI job with only Node available (the runner image has no
|
|
// curl/jq/sudo), so this uses Node built-ins + global fetch/FormData/Blob.
|
|
//
|
|
// Env: TOKEN (Gitea token), SERVER (github.server_url), REPO (owner/repo),
|
|
// SHA (github.sha). Expects ./remoterig in the working dir.
|
|
import { readFileSync } from 'node:fs';
|
|
import { createHash } from 'node:crypto';
|
|
|
|
const { TOKEN, SERVER, REPO, SHA } = process.env;
|
|
const BIN = 'remoterig';
|
|
// Rolling release tag. NOT "dev" — that would collide with the dev branch
|
|
// and make refs ambiguous (git push/checkout dev breaks).
|
|
const TAG = 'dev-latest';
|
|
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();
|
|
return t ? JSON.parse(t) : null;
|
|
};
|
|
|
|
const bin = readFileSync(BIN);
|
|
const sha256 = createHash('sha256').update(bin).digest('hex');
|
|
const files = {
|
|
[BIN]: bin,
|
|
[`${BIN}.sha256`]: Buffer.from(sha256 + '\n'),
|
|
'version.txt': Buffer.from(VERSION + '\n'),
|
|
};
|
|
|
|
// Roll the release forward to this commit: delete the old release + tag.
|
|
const existing = await rfetch(`${API}/releases/tags/${TAG}`, { headers: H });
|
|
if (existing.ok) {
|
|
const rel = await existing.json();
|
|
await rfetch(`${API}/releases/${rel.id}`, { method: 'DELETE', headers: H });
|
|
}
|
|
await rfetch(`${API}/tags/${TAG}`, { method: 'DELETE', headers: H }); // ignore if absent
|
|
|
|
const rel = await ok(await rfetch(`${API}/releases`, {
|
|
method: 'POST',
|
|
headers: { ...H, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
tag_name: TAG,
|
|
target_commitish: SHA,
|
|
name: `${TAG} (${VERSION})`,
|
|
body: `Rolling dev build ${SHA}`,
|
|
prerelease: true,
|
|
}),
|
|
}));
|
|
|
|
for (const [name, buf] of Object.entries(files)) {
|
|
const fd = new FormData();
|
|
fd.append('attachment', new Blob([buf]), name);
|
|
await ok(await rfetch(`${API}/releases/${rel.id}/assets?name=${encodeURIComponent(name)}`, {
|
|
method: 'POST', headers: H, body: fd,
|
|
}));
|
|
console.log(`uploaded ${name}`);
|
|
}
|
|
console.log(`Published dev release ${VERSION}`);
|