chore: switch to update-electron-app

This commit is contained in:
Naomi Chopra 2025-07-04 11:45:44 +05:30 committed by haritabh-z01
parent e7019c2776
commit 37442bd370
6 changed files with 64 additions and 286 deletions

View file

@ -1,5 +1,7 @@
import "dotenv/config";
import type { ForgeConfig } from "@electron-forge/shared-types";
import { MakerSquirrel } from "@electron-forge/maker-squirrel";
import { MakerZIP } from "@electron-forge/maker-zip";
import { MakerDMG } from "@electron-forge/maker-dmg";
import { MakerDeb } from "@electron-forge/maker-deb";
import { MakerRpm } from "@electron-forge/maker-rpm";
@ -253,9 +255,13 @@ const config: ForgeConfig = {
"This app needs access to your microphone to record audio for transcription.",
},
// Code signing configuration for macOS
osxSign: {
identity: "Developer ID Application: Exa Labs LLC (59CRJ9SJ4N)",
},
...(process.env.SKIP_CODESIGNING === "true"
? {}
: {
osxSign: {
identity: process.env.CODESIGNING_IDENTITY,
},
}),
// Notarization for macOS (configure when ready for distribution)
// osxNotarize: {
// appleId: process.env.APPLE_ID,
@ -335,9 +341,16 @@ const config: ForgeConfig = {
name: "Amical",
setupIcon: "./assets/logo.ico",
}),
new MakerZIP(
{
// Ensure the ZIP file has the correct naming for update-electron-app
// The default pattern should work, but we can customize if needed
},
["darwin"]
), // Required for macOS auto-updates
new MakerDMG(
{
name: "Amical",
name: "Amical-${arch}",
icon: "./assets/logo.icns", // Volume icon (when DMG is mounted)
},
["darwin"],

View file

@ -4,6 +4,10 @@
"description": "Amical Desktop app",
"main": ".vite/build/main.js",
"productName": "Amical",
"repository": {
"type": "git",
"url": "https://github.com/amicalhq/amical"
},
"scripts": {
"start": "pnpm build:swift-helper && electron-forge start",
"package": "pnpm build:swift-helper && electron-forge package",
@ -107,7 +111,6 @@
"electron-log": "^5.4.0",
"electron-squirrel-startup": "^1.0.1",
"electron-trpc-experimental": "1.0.0-alpha.1",
"electron-updater": "^6.6.2",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^12.10.5",
"input-otp": "^1.4.2",
@ -129,6 +132,7 @@
"superjson": "^2.2.2",
"tailwind-merge": "^3.3.0",
"tw-animate-css": "^1.2.9",
"update-electron-app": "^3.1.1",
"uuid": "^11.1.0",
"vaul": "^1.1.2",
"zod": "^3.25.24"

View file

@ -39,8 +39,7 @@ export class AppManager {
const eventHandlers = new EventHandlers(this);
eventHandlers.setupEventHandlers();
// Schedule auto-update check after startup
this.scheduleAutoUpdateCheck();
// Auto-update is now handled by update-electron-app in main.ts
logger.main.info("Application initialized successfully");
} catch (error) {
@ -152,20 +151,6 @@ export class AppManager {
return this.serviceManager.getAutoUpdaterService();
}
private scheduleAutoUpdateCheck(): void {
// Check for updates on startup (after a brief delay)
setTimeout(() => {
try {
const autoUpdaterService = this.serviceManager.getAutoUpdaterService();
autoUpdaterService.checkForUpdatesAndNotify();
} catch (error) {
logger.main.warn("Auto-update check failed during startup", {
error: error instanceof Error ? error.message : String(error),
});
}
}, 5000); // Wait 5 seconds after startup
}
private onMainWindowCreated(window: BrowserWindow): void {
this.updateTRPCHandler();
}

View file

@ -9,6 +9,11 @@ if (started) {
app.quit();
}
// Set up auto-updater for production builds
if (app.isPackaged) {
require("update-electron-app")();
}
const appManager = new AppManager();
app.whenReady().then(() => appManager.initialize());

View file

@ -1,237 +1,46 @@
import { autoUpdater } from "electron-updater";
import { app, dialog, BrowserWindow } from "electron";
import { app } from "electron";
import { EventEmitter } from "events";
import { logger } from "../logger";
import { WindowManager } from "../core/window-manager";
export class AutoUpdaterService extends EventEmitter {
private checkingForUpdate = false;
private updateAvailable = false;
constructor(private windowManager: WindowManager) {
super();
// Only set up auto-updater in production
if (process.env.NODE_ENV !== "development" && app.isPackaged) {
this.setupAutoUpdater();
} else {
logger.updater.info("Auto-updater disabled in development mode");
}
}
private setupAutoUpdater() {
// Configure updater
autoUpdater.autoDownload = false; // Don't auto-download, ask user first
autoUpdater.autoInstallOnAppQuit = true;
// Development settings
if (process.env.NODE_ENV === "development") {
// In development, you can test with a local update server
// autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml');
autoUpdater.forceDevUpdateConfig = true;
}
// Event handlers
autoUpdater.on("checking-for-update", () => {
logger.updater.info("Checking for update...");
this.checkingForUpdate = true;
});
autoUpdater.on("update-available", (info) => {
logger.updater.info("Update available", {
version: info.version,
releaseDate: info.releaseDate,
});
this.checkingForUpdate = false;
this.updateAvailable = true;
this.showUpdateDialog(info);
});
autoUpdater.on("update-not-available", (info) => {
logger.updater.info("Update not available", { version: info.version });
this.checkingForUpdate = false;
this.updateAvailable = false;
});
autoUpdater.on("error", (err) => {
logger.updater.error("Error in auto-updater", { error: err.message });
this.checkingForUpdate = false;
// Show error dialog only if user manually checked for updates
const mainWindow = this.windowManager.getMainWindow();
if (mainWindow && !mainWindow.isDestroyed()) {
dialog.showErrorBox(
"Update Error",
`Error checking for updates: ${err.message}`,
);
}
});
autoUpdater.on("download-progress", (progressObj) => {
logger.updater.info("Download progress", {
bytesPerSecond: progressObj.bytesPerSecond,
percent: progressObj.percent,
transferred: progressObj.transferred,
total: progressObj.total,
});
// Emit event for tRPC subscription
this.emit("download-progress", progressObj);
});
autoUpdater.on("update-downloaded", (info) => {
logger.updater.info("Update downloaded", { version: info.version });
this.showInstallDialog(info);
});
}
private async showUpdateDialog(info: any) {
const mainWindow = this.windowManager.getMainWindow();
if (!mainWindow || mainWindow.isDestroyed()) {
return;
}
const result = await dialog.showMessageBox(mainWindow, {
type: "info",
title: "Update Available",
message: `A new version (${info.version}) is available.`,
detail:
"Would you like to download it now? The update will be installed when you restart the app.",
buttons: ["Download Now", "Later"],
defaultId: 0,
cancelId: 1,
});
if (result.response === 0) {
logger.updater.info("User chose to download update");
autoUpdater.downloadUpdate();
} else {
logger.updater.info("User chose to skip update");
}
}
private async showInstallDialog(info: any) {
const mainWindow = this.windowManager.getMainWindow();
if (!mainWindow || mainWindow.isDestroyed()) {
return;
}
const result = await dialog.showMessageBox(mainWindow, {
type: "info",
title: "Update Ready",
message: `Update ${info.version} has been downloaded.`,
detail:
"The update will be installed when you restart the app. Would you like to restart now?",
buttons: ["Restart Now", "Later"],
defaultId: 0,
cancelId: 1,
});
if (result.response === 0) {
logger.updater.info("User chose to restart and install update");
autoUpdater.quitAndInstall();
} else {
logger.updater.info("User chose to install update later");
}
}
// These methods are kept for compatibility with existing code
// update-electron-app handles the actual update logic
async checkForUpdates(userInitiated = false): Promise<void> {
// Skip in development
if (process.env.NODE_ENV === "development" || !app.isPackaged) {
logger.updater.info("Skipping update check in development mode");
if (userInitiated) {
const mainWindow = this.windowManager.getMainWindow();
if (mainWindow && !mainWindow.isDestroyed()) {
dialog.showMessageBox(mainWindow, {
type: "info",
title: "Development Mode",
message: "Update checking is disabled in development mode.",
buttons: ["OK"],
});
}
}
return;
}
if (this.checkingForUpdate) {
logger.updater.info("Already checking for updates");
return;
}
try {
logger.updater.info("Starting update check", { userInitiated });
await autoUpdater.checkForUpdates();
} catch (error) {
logger.updater.error("Failed to check for updates", {
error: error instanceof Error ? error.message : String(error),
});
if (userInitiated) {
const mainWindow = this.windowManager.getMainWindow();
if (mainWindow && !mainWindow.isDestroyed()) {
dialog.showErrorBox(
"Update Check Failed",
"Failed to check for updates. Please try again later.",
);
}
}
}
logger.updater.info(
"Update check requested, handled by update-electron-app",
);
}
async checkForUpdatesAndNotify(): Promise<void> {
// Skip in development
if (process.env.NODE_ENV === "development" || !app.isPackaged) {
logger.updater.info(
"Skipping background update check in development mode",
);
return;
}
try {
await autoUpdater.checkForUpdatesAndNotify();
} catch (error) {
logger.updater.error("Failed to check for updates and notify", {
error: error instanceof Error ? error.message : String(error),
});
}
logger.updater.info(
"Background update check requested, handled by update-electron-app",
);
}
isCheckingForUpdate(): boolean {
return this.checkingForUpdate;
return false; // Handled by update-electron-app
}
isUpdateAvailable(): boolean {
return this.updateAvailable;
return false; // Handled by update-electron-app
}
async downloadUpdate(): Promise<void> {
// Skip in development
if (process.env.NODE_ENV === "development" || !app.isPackaged) {
logger.updater.info("Skipping update download in development mode");
throw new Error("Update downloads are disabled in development mode");
}
if (!this.updateAvailable) {
throw new Error("No update available to download");
}
try {
await autoUpdater.downloadUpdate();
} catch (error) {
logger.updater.error("Failed to download update", {
error: error instanceof Error ? error.message : String(error),
});
throw error;
}
logger.updater.info(
"Download update requested, handled by update-electron-app",
);
}
quitAndInstall(): void {
// Skip in development
if (process.env.NODE_ENV === "development" || !app.isPackaged) {
logger.updater.info("Skipping quit and install in development mode");
return;
}
autoUpdater.quitAndInstall();
logger.updater.info(
"Quit and install requested, handled by update-electron-app",
);
app.quit();
}
}

74
pnpm-lock.yaml generated
View file

@ -191,9 +191,6 @@ importers:
electron-trpc-experimental:
specifier: 1.0.0-alpha.1
version: 1.0.0-alpha.1(@trpc/client@11.4.2(@trpc/server@11.4.2(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.4.2(typescript@5.8.3))(electron@36.2.0)
electron-updater:
specifier: ^6.6.2
version: 6.6.2
embla-carousel-react:
specifier: ^8.6.0
version: 8.6.0(react@19.1.0)
@ -257,6 +254,9 @@ importers:
tw-animate-css:
specifier: ^1.2.9
version: 1.3.0
update-electron-app:
specifier: ^3.1.1
version: 3.1.1
uuid:
specifier: ^11.1.0
version: 11.1.0
@ -3724,10 +3724,6 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
builder-util-runtime@9.3.1:
resolution: {integrity: sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ==}
engines: {node: '>=12.0.0'}
busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
@ -4451,9 +4447,6 @@ packages:
'@trpc/server': '>11.0.0'
electron: '>19.0.0'
electron-updater@6.6.2:
resolution: {integrity: sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==}
electron-winstaller@5.4.0:
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
engines: {node: '>=8.0.0'}
@ -5152,6 +5145,9 @@ packages:
github-slugger@2.0.0:
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
github-url-to-object@4.0.6:
resolution: {integrity: sha512-NaqbYHMUAlPcmWFdrAB7bcxrNIiiJWJe8s/2+iOc9vlcHlwHqSGrPk+Yi3nu6ebTwgsZEa7igz+NH2vEq3gYwQ==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@ -5747,9 +5743,6 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
lazy-val@1.0.5:
resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@ -5848,9 +5841,6 @@ packages:
lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
lodash.escaperegexp@4.1.2:
resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==}
lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
@ -5858,10 +5848,6 @@ packages:
lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@ -7171,9 +7157,6 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
@ -7637,9 +7620,6 @@ packages:
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tiny-typed-emitter@2.1.0:
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
tinycolor2@1.6.0:
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
@ -7934,6 +7914,9 @@ packages:
update-check@1.5.4:
resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==}
update-electron-app@3.1.1:
resolution: {integrity: sha512-7duRr6sYn014tifhKgT/5i8N+6xLzmJVJ8hVtNrHXlIDNP6QbRe6VxZ1hSi2UH5oJPzhor/PH7yKU9em5xjRzQ==}
upload-files-express@0.4.0:
resolution: {integrity: sha512-ET3Pcstq4F1gNCPtfy9cK8WAYhg81NoiC1qjYaptGT2ODkT8FMarNbGh5Ku71/B6Ig39o58JibfEkObmhJFY0Q==}
@ -11969,13 +11952,6 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
builder-util-runtime@9.3.1:
dependencies:
debug: 4.4.1
sax: 1.4.1
transitivePeerDependencies:
- supports-color
busboy@1.6.0:
dependencies:
streamsearch: 1.1.0
@ -12655,19 +12631,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
electron-updater@6.6.2:
dependencies:
builder-util-runtime: 9.3.1
fs-extra: 10.1.0
js-yaml: 4.1.0
lazy-val: 1.0.5
lodash.escaperegexp: 4.1.2
lodash.isequal: 4.5.0
semver: 7.7.2
tiny-typed-emitter: 2.1.0
transitivePeerDependencies:
- supports-color
electron-winstaller@5.4.0:
dependencies:
'@electron/asar': 3.4.1
@ -13707,6 +13670,10 @@ snapshots:
github-slugger@2.0.0: {}
github-url-to-object@4.0.6:
dependencies:
is-url: 1.2.4
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@ -14408,8 +14375,6 @@ snapshots:
kind-of@6.0.3: {}
lazy-val@1.0.5: {}
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
@ -14508,14 +14473,10 @@ snapshots:
lodash.defaults@4.2.0: {}
lodash.escaperegexp@4.1.2: {}
lodash.get@4.4.2: {}
lodash.isarguments@3.1.0: {}
lodash.isequal@4.5.0: {}
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
@ -16295,8 +16256,6 @@ snapshots:
safer-buffer@2.1.2: {}
sax@1.4.1: {}
scheduler@0.26.0: {}
scroll-into-view-if-needed@3.1.0:
@ -16912,8 +16871,6 @@ snapshots:
tiny-invariant@1.3.3: {}
tiny-typed-emitter@2.1.0: {}
tinycolor2@1.6.0: {}
tinygradient@1.1.5:
@ -17234,6 +17191,11 @@ snapshots:
registry-auth-token: 3.3.2
registry-url: 3.1.0
update-electron-app@3.1.1:
dependencies:
github-url-to-object: 4.0.6
ms: 2.1.3
upload-files-express@0.4.0:
dependencies:
formidable: 2.1.5