Fix updater: resolve latest via Codeberg API, not the broken /latest/ alias

Codeberg does not serve the `/releases/latest/download/<file>` alias (404s); only
tag-specific `/releases/download/<tag>/<file>` URLs work. The update feed baked into
the app used the alias, so update checks never resolved on any platform.

Now both the Linux notifier and the Windows electron-updater resolve the newest
release via the Codeberg API (`/releases/latest` -> tag_name) and build the working
tag-specific feed URL from it (setFeedURL for electron-updater).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 12:22:41 -07:00
parent 0601e50778
commit 7445a9d4e4
+35 -10
View File
@@ -14,11 +14,29 @@ const { autoUpdater } = electronUpdater;
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
// Blap update feed (same Codeberg release the electron-builder `publish` config points at).
// Blap update source (Codeberg). NOTE: Codeberg does NOT support the
// `/releases/latest/download/<file>` alias (it 404s) — only tag-specific
// `/releases/download/<tag>/<file>` URLs work. So we resolve "latest" via the API
// and build tag-specific URLs from that, instead of relying on the broken alias.
const CODEBERG_BASE = "https://codeberg.org/Enkisu/Blap";
const LINUX_FEED_URL = `${CODEBERG_BASE}/releases/latest/download/latest-linux.yml`;
const CODEBERG_API = "https://codeberg.org/api/v1/repos/Enkisu/Blap";
const HOWTO_UPDATE_URL = `${CODEBERG_BASE}/src/branch/main/HOW-TO-UPDATE.md`;
/** Resolve the newest published release via the API. Returns its tag and numeric version. */
async function getLatestRelease(): Promise<{ tag: string; version: string } | null> {
try {
const res = await fetch(`${CODEBERG_API}/releases/latest?t=${Date.now()}`);
if (!res.ok) return null;
const rel = (await res.json()) as { tag_name?: string };
const tag = rel.tag_name;
if (!tag) return null;
return { tag, version: tag.replace(/^v/, "") };
} catch (e) {
console.log("Couldn't resolve latest release", e);
return null;
}
}
function ipcChannelSendUpdateStatus(status: boolean | string): void {
global.mainWindow?.webContents.send("check_updates", status);
}
@@ -32,6 +50,16 @@ function installUpdate(): void {
async function pollForUpdates(): Promise<void> {
try {
// Point electron-updater at the latest release's tag-specific download folder
// (the baked app-update.yml uses the broken /latest/download/ alias, so we
// override it here each poll). electron-updater then fetches <url>/latest.yml.
const latest = await getLatestRelease();
if (latest) {
autoUpdater.setFeedURL({
provider: "generic",
url: `${CODEBERG_BASE}/releases/download/${latest.tag}`,
});
}
await autoUpdater.checkForUpdates();
} catch (e) {
console.log("Couldn't check for update", e);
@@ -63,16 +91,13 @@ let lastLinuxNotifiedVersion: string | undefined;
*/
async function checkLinuxUpdate(): Promise<void> {
try {
const res = await fetch(`${LINUX_FEED_URL}?t=${Date.now()}`);
if (!res.ok) return;
const text = await res.text();
const latest = /^version:\s*(.+)$/m.exec(text)?.[1]?.trim();
if (!latest || !isNewer(latest, app.getVersion())) return;
if (latest === lastLinuxNotifiedVersion) return; // don't nag every poll
lastLinuxNotifiedVersion = latest;
const latest = await getLatestRelease();
if (!latest || !isNewer(latest.version, app.getVersion())) return;
if (latest.version === lastLinuxNotifiedVersion) return; // don't nag every poll
lastLinuxNotifiedVersion = latest.version;
if (!Notification.isSupported()) return;
const notification = new Notification({
title: `Blap ${latest} is available`,
title: `Blap ${latest.version} is available`,
body: "Click to see how to update.",
});
notification.on("click", () => void shell.openExternal(HOWTO_UPDATE_URL));