Add built-in auto-updater with tab-bar update pill
Wires electrobun's Updater to the /dl download host. The packaged app checks https://miniscp.com/dl on launch + every 6h; when a newer build exists, an accent-colored "update tool" appears next to Connections in the tab bar. It announces the update, becomes its own inline progress bar while downloading, then flips to "Restart to apply" (two-step: download, then user-triggered relaunch). - electrobun.config.ts: release.baseUrl = https://miniscp.com/dl (baked into version.json at build; miniscp.com fronted by an external proxy for TLS). - rpc.ts: checkForUpdate/downloadUpdate/applyUpdate requests + updateStatus push. - rpc-handlers.ts + index.ts: call Updater; poll on launch+6h; stream onStatusChange progress. checkForUpdate self-guards the dev channel. - rpc.service.ts + new updater.service.ts: signal-based state (available -> downloading -> ready, ready derived from downloadUpdate ok). - tab-bar.ts: single bg-primary pill; progress fills the pill itself. - icons.ts: register mynaDownload. i18n: tabBar + menu keys across all locales. - menu.ts + app.ts: "Check for Updates…" app-menu item -> UpdaterService.check(). - docs: build-and-release + architecture updated. Unsigned builds still update (applyUpdate strips the macOS quarantine xattr). Verified: tsc --noEmit (exit 0) + build:ui AOT (exit 0). Live pill + full download/apply flow require a two-build test on the host. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -250,4 +250,26 @@ ApplicationMenu.on("application-menu-clicked", (event) => {
|
||||
rpc.send.menuAction({ action });
|
||||
});
|
||||
|
||||
// ---- auto-updater: check on launch + every 6h, stream status to the tab bar ----
|
||||
// Updater.checkForUpdate() self-guards the dev channel (returns updateAvailable:
|
||||
// false), so the pill never appears during `bun run dev`.
|
||||
Updater.onStatusChange((entry) => {
|
||||
if (entry.status === "download-progress") {
|
||||
rpc.send.updateStatus({ phase: "downloading", progress: entry.details?.progress ?? 0 });
|
||||
} else if (entry.status === "error") {
|
||||
rpc.send.updateStatus({ phase: "error", error: entry.message });
|
||||
}
|
||||
});
|
||||
|
||||
async function runUpdateCheck(): Promise<void> {
|
||||
try {
|
||||
const r = await Updater.checkForUpdate();
|
||||
if (r.updateAvailable) rpc.send.updateStatus({ phase: "available", version: r.version });
|
||||
} catch {
|
||||
// offline / feed unreachable — stay quiet and retry on the next tick
|
||||
}
|
||||
}
|
||||
setTimeout(() => void runUpdateCheck(), 5_000); // let the window settle first
|
||||
setInterval(() => void runUpdateCheck(), 6 * 60 * 60 * 1000);
|
||||
|
||||
console.log(`${BRANDING.appName} started (${url})`);
|
||||
|
||||
@@ -36,6 +36,8 @@ export async function applyApplicationMenu(store: ConnectionStore): Promise<void
|
||||
submenu: [
|
||||
{ label: t("menu.about", { app: BRANDING.appName }), action: "about" },
|
||||
{ type: "separator" },
|
||||
{ label: t("menu.checkForUpdates"), action: "check-for-updates" },
|
||||
{ type: "separator" },
|
||||
{ role: "hide" },
|
||||
{ role: "hideOthers" },
|
||||
{ role: "showAll" },
|
||||
|
||||
+20
-1
@@ -1,4 +1,4 @@
|
||||
import { BrowserView, Utils } from "electrobun/bun";
|
||||
import { BrowserView, Updater, Utils } from "electrobun/bun";
|
||||
import type { AppRPC, EditorOpenContext } from "../shared/rpc";
|
||||
import { effectiveConcurrency, type AppSettings, type Result } from "../shared/types";
|
||||
import type { PanelContextMenu } from "./context-menu";
|
||||
@@ -322,6 +322,25 @@ export function createAppRPC({
|
||||
}),
|
||||
|
||||
listDrives: () => wrap(async () => ({ drives: listLocalDrives() })),
|
||||
|
||||
// ---- auto-updater ----
|
||||
checkForUpdate: () =>
|
||||
wrap(async () => {
|
||||
const r = await Updater.checkForUpdate();
|
||||
return { updateAvailable: r.updateAvailable, version: r.version };
|
||||
}),
|
||||
|
||||
downloadUpdate: () =>
|
||||
wrap(async () => {
|
||||
await Updater.downloadUpdate();
|
||||
return {};
|
||||
}),
|
||||
|
||||
applyUpdate: () =>
|
||||
wrap(async () => {
|
||||
await Updater.applyUpdate(); // relaunches + quits; may never resolve
|
||||
return {};
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user