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 =
|
const RELEASES_URL =
|
||||||
"https://git.mithraic.cloud/api/v1/repos/ad3laid3/sanctum/releases/latest";
|
"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() {
|
export async function checkForUpdates() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(RELEASES_URL);
|
const res = await fetch(RELEASES_URL);
|
||||||
if (!res.ok) return;
|
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 latest = release.tag_name.replace(/^v/, "");
|
||||||
const current = app.getVersion();
|
const current = app.getVersion();
|
||||||
|
|
||||||
if (!isNewer(latest, current)) return;
|
if (!isNewer(latest, current)) return;
|
||||||
|
|
||||||
const notification = new Notification({
|
const asset = findAsset(release.assets);
|
||||||
title: "Update Available",
|
if (!asset) return;
|
||||||
body: `Version ${latest} is available. Click to download.`,
|
|
||||||
silent: true,
|
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));
|
const n = notify("Update Ready", `Sanctum ${version} is installed — click to restart.`);
|
||||||
notification.show();
|
n.on("click", () => { app.relaunch(); app.exit(0); });
|
||||||
} catch {
|
|
||||||
// non-critical — silently ignore network/parse errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
function isNewer(latest: string, current: string): boolean {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue