|
1 | 1 | import { execFileSync } from "node:child_process"; |
| 2 | +import { readFileSync, writeFileSync } from "node:fs"; |
2 | 3 | import { homedir } from "node:os"; |
3 | 4 | import { join } from "node:path"; |
4 | 5 | import { createDb, repo } from "@ateam/db"; |
5 | | -import { app, BrowserWindow } from "electron"; |
| 6 | +import { app, BrowserWindow, dialog } from "electron"; |
6 | 7 | import { autoUpdater } from "electron-updater"; |
7 | 8 | import { type AgentStatus, type KanbanColumn, CH } from "../shared/types"; |
8 | 9 | import { ensureNotifyScript } from "./agent-setup"; |
@@ -60,15 +61,81 @@ function adoptLoginShellPath(): void { |
60 | 61 | } |
61 | 62 |
|
62 | 63 | /** |
63 | | - * Auto-update from GitHub Releases (electron-updater): check at launch and |
64 | | - * every 4 hours; downloads in the background and notifies — the update |
65 | | - * installs when the app quits. No more manual trips to the repo. |
| 64 | + * Auto-update from GitHub Releases (electron-updater), Sparkle-style: ask |
| 65 | + * before downloading (with release notes; "Skip This Version" is remembered), |
| 66 | + * then offer "Restart Now" once downloaded — otherwise it installs on quit. |
| 67 | + * Checks at launch and every 4 hours. |
66 | 68 | */ |
67 | 69 | function setupAutoUpdate(): void { |
68 | 70 | if (!app.isPackaged) return; |
| 71 | + autoUpdater.autoDownload = false; |
| 72 | + |
| 73 | + const skipFile = join(app.getPath("userData"), "skipped-version.txt"); |
| 74 | + const skippedVersion = (): string => { |
| 75 | + try { |
| 76 | + return readFileSync(skipFile, "utf8").trim(); |
| 77 | + } catch { |
| 78 | + return ""; |
| 79 | + } |
| 80 | + }; |
| 81 | + // "Remind Me Later" = stay quiet until the next app launch. |
| 82 | + let snoozed = false; |
| 83 | + |
| 84 | + autoUpdater.on("update-available", (info) => { |
| 85 | + if (snoozed || info.version === skippedVersion()) return; |
| 86 | + const notes = |
| 87 | + typeof info.releaseNotes === "string" |
| 88 | + ? info.releaseNotes.replace(/<[^>]+>/g, "").trim() |
| 89 | + : ""; |
| 90 | + void dialog |
| 91 | + .showMessageBox({ |
| 92 | + type: "info", |
| 93 | + title: "Software Update", |
| 94 | + message: `A new version of Ateam is available!`, |
| 95 | + detail: `Ateam ${info.version} is now available — you have ${app.getVersion()}. Would you like to download it now?${notes ? `\n\n${notes.slice(0, 1500)}` : ""}`, |
| 96 | + buttons: ["Install Update", "Remind Me Later", "Skip This Version"], |
| 97 | + defaultId: 0, |
| 98 | + cancelId: 1, |
| 99 | + }) |
| 100 | + .then(({ response }) => { |
| 101 | + if (response === 0) { |
| 102 | + void autoUpdater |
| 103 | + .downloadUpdate() |
| 104 | + .catch((err) => |
| 105 | + console.warn("[ateam] update download failed:", err), |
| 106 | + ); |
| 107 | + } else if (response === 1) { |
| 108 | + snoozed = true; |
| 109 | + } else { |
| 110 | + try { |
| 111 | + writeFileSync(skipFile, info.version, "utf8"); |
| 112 | + } catch { |
| 113 | + /* best-effort */ |
| 114 | + } |
| 115 | + } |
| 116 | + }); |
| 117 | + }); |
| 118 | + |
| 119 | + autoUpdater.on("update-downloaded", (info) => { |
| 120 | + void dialog |
| 121 | + .showMessageBox({ |
| 122 | + type: "info", |
| 123 | + title: "Update Ready", |
| 124 | + message: `Ateam ${info.version} has been downloaded.`, |
| 125 | + detail: |
| 126 | + "Restart now to apply it — or keep working, and it will install when you quit. Agent terminals survive the restart.", |
| 127 | + buttons: ["Restart Now", "Later"], |
| 128 | + defaultId: 0, |
| 129 | + cancelId: 1, |
| 130 | + }) |
| 131 | + .then(({ response }) => { |
| 132 | + if (response === 0) autoUpdater.quitAndInstall(); |
| 133 | + }); |
| 134 | + }); |
| 135 | + |
69 | 136 | const check = () => |
70 | 137 | autoUpdater |
71 | | - .checkForUpdatesAndNotify() |
| 138 | + .checkForUpdates() |
72 | 139 | .catch((err) => console.warn("[ateam] update check failed:", err)); |
73 | 140 | check(); |
74 | 141 | setInterval(check, 4 * 60 * 60 * 1000); |
|
0 commit comments