desktop: fix auto-update feed — Codeberg has no /releases/latest/download route

Forgejo/Codeberg 404s the GitHub-style latest/download path, so the feed
baked into app-update.yml never worked (1.0.0/1.0.1 never saw updates).
Point it at a rolling release on the fixed tag "updates" instead, and
make release-codeberg.mjs sync latest.yml + the Windows installer to
that release on every ship (channel files always replaced; curl retries
added).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 20:37:27 -07:00
parent 3439d61fe0
commit 8a26cdca8c
2 changed files with 31 additions and 14 deletions
+26 -11
View File
@@ -54,17 +54,17 @@ async function api(path, init = {}) {
return res;
}
async function getOrCreateRelease() {
let res = await api(`/repos/${OWNER}/${REPO}/releases/tags/${encodeURIComponent(tag)}`);
async function getOrCreateRelease(tagName, { prerelease = false } = {}) {
let res = await api(`/repos/${OWNER}/${REPO}/releases/tags/${encodeURIComponent(tagName)}`);
if (res.status === 404) {
res = await api(`/repos/${OWNER}/${REPO}/releases`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tag_name: tag, name: tag, draft: false, prerelease: false }),
body: JSON.stringify({ tag_name: tagName, name: tagName, draft: false, prerelease }),
});
console.log(`Created release ${tag}`);
console.log(`Created release ${tagName}`);
} else {
console.log(`Reusing existing release ${tag}`);
console.log(`Reusing existing release ${tagName}`);
}
return res.json();
}
@@ -79,7 +79,21 @@ async function main() {
process.exit(1);
}
const release = await getOrCreateRelease();
const release = await getOrCreateRelease(tag);
await syncAssets(release, files);
// Forgejo/Codeberg has NO GitHub-style /releases/latest/download/ route, so
// app-update.yml points at a rolling release on the fixed tag "updates" —
// re-point its channel file + Windows installer at the build we just shipped.
const updaterFiles = files.filter((f) => f === "latest.yml" || /\.(exe|exe\.blockmap)$/.test(f));
const updates = await getOrCreateRelease("updates", { prerelease: true });
await syncAssets(updates, updaterFiles);
const webBase = API.replace(/\/api\/v1\/?$/, "");
console.log(`\nDone. Updater feed: ${webBase}/${OWNER}/${REPO}/releases/download/updates/latest.yml`);
}
async function syncAssets(release, files) {
const existing = new Map((release.assets ?? []).map((a) => [a.name, a]));
for (const file of files) {
@@ -88,8 +102,9 @@ async function main() {
const size = statSync(filePath).size;
const already = existing.get(name);
// Resume: skip assets already uploaded at the same size (lets a re-run
// finish where it left off instead of re-uploading everything).
if (already && already.size === size) {
// finish where it left off instead of re-uploading everything). Channel
// files are tiny and change meaning per build — always replace those.
if (already && already.size === size && !CHANNEL_FILES.has(name)) {
console.log(`Skipping ${name} (already uploaded)`);
continue;
}
@@ -104,6 +119,9 @@ async function main() {
[
"-sS",
"--fail-with-body",
"--retry",
"4",
"--retry-all-errors",
"-X",
"POST",
"-H",
@@ -116,9 +134,6 @@ async function main() {
);
console.log("done");
}
const webBase = API.replace(/\/api\/v1\/?$/, "");
console.log(`\nDone. Updater feed: ${webBase}/${OWNER}/${REPO}/releases/latest/download/latest.yml`);
}
await main();