announce updates rather than install them when a package manager owns the binary

This commit is contained in:
pj committed 2026-09-03 13:24:20 +05:30
1 parent ae5a7b4c0e
commit 958931f5ef
4 files changed
+79 -1

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
import { call } from "../ipc";
/** The package manager that owns this install, or null when the app updates itself. */
export const packagedBy = () => call<string | null>("packaged_by");
+51
View File
@@ -0,0 +1,51 @@
// The one path where the updater must stand down: a package manager owns the binary, so a newer
// version is announced and never installed over it.
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
check: vi.fn(),
relaunch: vi.fn(),
notify: vi.fn(),
call: vi.fn(),
}));
vi.mock("@tauri-apps/plugin-updater", () => ({ check: mocks.check }));
vi.mock("@tauri-apps/plugin-process", () => ({ relaunch: mocks.relaunch }));
vi.mock("../store/useToast", () => ({ notify: mocks.notify }));
vi.mock("../ipc", () => ({ isDesktop: true, call: mocks.call }));
import { checkForUpdates } from "./updates";
const downloadAndInstall = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
mocks.check.mockResolvedValue({ version: "0.0.5", downloadAndInstall });
});
describe("checkForUpdates", () => {
it("installs and relaunches when the app owns its own binary", async () => {
mocks.call.mockResolvedValue(null);
await checkForUpdates();
expect(downloadAndInstall).toHaveBeenCalled();
expect(mocks.relaunch).toHaveBeenCalled();
});
it("points at the package manager instead of installing over it", async () => {
mocks.call.mockResolvedValue("nix");
await checkForUpdates();
expect(downloadAndInstall).not.toHaveBeenCalled();
expect(mocks.relaunch).not.toHaveBeenCalled();
expect(mocks.notify).toHaveBeenCalledWith(
"0.0.5 is out. Update with: nix profile upgrade margin-calendar",
);
});
it("does not ask who owns the install when there is nothing newer", async () => {
mocks.check.mockResolvedValue(null);
await checkForUpdates();
expect(mocks.call).not.toHaveBeenCalled();
expect(mocks.notify).toHaveBeenCalledWith("Margin Calendar is up to date");
});
});
+12
View File
@@ -4,11 +4,18 @@
import { relaunch } from "@tauri-apps/plugin-process";
import { check } from "@tauri-apps/plugin-updater";
import { packagedBy } from "../api/updates";
import { isDesktop } from "../ipc";
import { notify } from "../store/useToast";
let running = false;
/** A package manager owns the binary, so the update is announced and left to it. */
function updateHint(manager: string, version: string): string {
if (manager === "nix") return `${version} is out. Update with: nix profile upgrade margin-calendar`;
return `${version} is out. Update it through ${manager}`;
}
export async function checkForUpdates(): Promise<void> {
if (!isDesktop || running) return;
running = true;
@@ -18,6 +25,11 @@ export async function checkForUpdates(): Promise<void> {
notify("Margin Calendar is up to date");
return;
}
const manager = await packagedBy();
if (manager) {
notify(updateHint(manager, update.version));
return;
}
notify(`Installing ${update.version}…`);
await update.downloadAndInstall();
await relaunch();