feat: auto-download and install updates in the background
All checks were successful
Build & Release / build (push) Successful in 2m28s
All checks were successful
Build & Release / build (push) Successful in 2m28s
Downloads the platform zip, extracts over the install dir, then prompts to restart with a single click. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e259d9b63c
commit
25543cb7ba
1 changed files with 62 additions and 10 deletions
|
|
@ -1,30 +1,82 @@
|
|||
import { Notification, app, shell } from "electron";
|
||||
import { Notification, app } from "electron";
|
||||
import { exec } from "child_process";
|
||||
import { createWriteStream, mkdirSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { tmpdir } from "os";
|
||||
import { pipeline } from "stream/promises";
|
||||
import { Readable } from "stream";
|
||||
|
||||
const RELEASES_URL =
|
||||
"https://git.mithraic.cloud/api/v1/repos/ad3laid3/sanctum/releases/latest";
|
||||
|
||||
interface Asset {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
}
|
||||
|
||||
interface Release {
|
||||
tag_name: string;
|
||||
html_url: string;
|
||||
assets: Asset[];
|
||||
}
|
||||
|
||||
export async function checkForUpdates() {
|
||||
try {
|
||||
const res = await fetch(RELEASES_URL);
|
||||
if (!res.ok) return;
|
||||
|
||||
const release = (await res.json()) as { tag_name: string; html_url: string };
|
||||
const release = (await res.json()) as Release;
|
||||
const latest = release.tag_name.replace(/^v/, "");
|
||||
const current = app.getVersion();
|
||||
|
||||
if (!isNewer(latest, current)) return;
|
||||
|
||||
const notification = new Notification({
|
||||
title: "Update Available",
|
||||
body: `Version ${latest} is available. Click to download.`,
|
||||
silent: true,
|
||||
const asset = findAsset(release.assets);
|
||||
if (!asset) return;
|
||||
|
||||
notify("Update Downloading", `Sanctum ${latest} is downloading in the background…`);
|
||||
await downloadAndInstall(asset.browser_download_url, latest);
|
||||
} catch {
|
||||
// non-critical
|
||||
}
|
||||
}
|
||||
|
||||
function findAsset(assets: Asset[]): Asset | undefined {
|
||||
if (process.platform === "linux")
|
||||
return assets.find((a) => a.name.includes("linux") && a.name.endsWith(".zip"));
|
||||
if (process.platform === "win32")
|
||||
return assets.find((a) => a.name.includes("win32") && a.name.endsWith(".zip"));
|
||||
}
|
||||
|
||||
async function downloadAndInstall(url: string, version: string) {
|
||||
const tmpDir = join(tmpdir(), `sanctum-update-${version}`);
|
||||
mkdirSync(tmpDir, { recursive: true });
|
||||
const zipPath = join(tmpDir, "update.zip");
|
||||
const extractDir = join(tmpDir, "extracted");
|
||||
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
||||
|
||||
await pipeline(Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]), createWriteStream(zipPath));
|
||||
|
||||
const installDir = dirname(process.execPath);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
exec(
|
||||
`unzip -o "${zipPath}" -d "${extractDir}" && SUBDIR=$(ls "${extractDir}" | head -1) && cp -rT "${extractDir}/$SUBDIR" "${installDir}"`,
|
||||
{ shell: "/bin/bash" },
|
||||
(err) => (err ? reject(err) : resolve()),
|
||||
);
|
||||
});
|
||||
|
||||
notification.on("click", () => shell.openExternal(release.html_url));
|
||||
notification.show();
|
||||
} catch {
|
||||
// non-critical — silently ignore network/parse errors
|
||||
const n = notify("Update Ready", `Sanctum ${version} is installed — click to restart.`);
|
||||
n.on("click", () => { app.relaunch(); app.exit(0); });
|
||||
}
|
||||
|
||||
function notify(title: string, body: string) {
|
||||
const n = new Notification({ title, body, silent: true });
|
||||
n.show();
|
||||
return n;
|
||||
}
|
||||
|
||||
function isNewer(latest: string, current: string): boolean {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue