fix(desktop): app icon + harden auto-updater loading

Generate a 1024x1024 macOS app icon from the dashboard logo so packaged
builds carry the Fusion brand instead of the default Electron icon, and
load electron-updater dynamically inside a try/catch so packaged builds
tolerate CJS/ESM interop quirks and missing transitive deps. Widen the
electron-builder files whitelist to include electron-updater's
transitive dep tree so they are actually bundled into the asar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-16 12:51:12 -07:00
parent c6e978286a
commit 6dde53ff0d
9 changed files with 89 additions and 27 deletions

View File

@@ -12,8 +12,23 @@ files:
- node_modules/@fusion/core/**/*
- node_modules/@fusion/dashboard/**/*
- node_modules/better-sqlite3/**/*
- node_modules/ms/**/*
- node_modules/electron-updater/**/*
- node_modules/builder-util-runtime/**/*
- node_modules/fs-extra/**/*
- node_modules/jsonfile/**/*
- node_modules/graceful-fs/**/*
- node_modules/universalify/**/*
- node_modules/js-yaml/**/*
- node_modules/argparse/**/*
- node_modules/sprintf-js/**/*
- node_modules/lazy-val/**/*
- node_modules/lodash.escaperegexp/**/*
- node_modules/lodash.isequal/**/*
- node_modules/semver/**/*
- node_modules/tiny-typed-emitter/**/*
- node_modules/debug/**/*
- node_modules/ms/**/*
- node_modules/sax/**/*
asarUnpack:
- node_modules/better-sqlite3/**/*

View File

@@ -12,23 +12,47 @@ const outputDir = resolve(packageDir, "src/icons");
const iconSizes = [16, 32, 48] as const;
const APP_ICON_SIZE = 1024;
const APP_ICON_PADDING = 128;
const APP_ICON_BG = "#0d1117";
const APP_ICON_FG = "#58a6ff";
async function main(): Promise<void> {
const sourceSvg = await readFile(sourceSvgPath, "utf8");
const tintedSvg = sourceSvg.replaceAll("currentColor", "#333333");
const trayTintedSvg = sourceSvg.replaceAll("currentColor", "#333333");
const appTintedSvg = sourceSvg.replaceAll("currentColor", APP_ICON_FG);
await mkdir(outputDir, { recursive: true });
await Promise.all(
iconSizes.map(async (size) => {
const outputPath = resolve(outputDir, `tray-${size}.png`);
await sharp(Buffer.from(tintedSvg), { density: 1024 })
await sharp(Buffer.from(trayTintedSvg), { density: 1024 })
.resize(size, size, { fit: "contain" })
.png({ compressionLevel: 9 })
.toFile(outputPath);
}),
);
console.log(`Generated ${iconSizes.length} tray icons in ${outputDir}`);
const markSize = APP_ICON_SIZE - APP_ICON_PADDING * 2;
const mark = await sharp(Buffer.from(appTintedSvg), { density: 2048 })
.resize(markSize, markSize, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } })
.png()
.toBuffer();
await sharp({
create: {
width: APP_ICON_SIZE,
height: APP_ICON_SIZE,
channels: 4,
background: APP_ICON_BG,
},
})
.composite([{ input: mark, top: APP_ICON_PADDING, left: APP_ICON_PADDING }])
.png({ compressionLevel: 9 })
.toFile(resolve(outputDir, "icon.png"));
console.log(`Generated ${iconSizes.length} tray icons and app icon in ${outputDir}`);
}
void main();

View File

@@ -96,6 +96,7 @@ vi.mock("electron", () => ({
}));
vi.mock("electron-updater", () => ({
default: { autoUpdater: mocks.autoUpdater },
autoUpdater: mocks.autoUpdater,
}));

View File

@@ -14,4 +14,7 @@ declare module "electron-updater" {
}
export const autoUpdater: AutoUpdater;
const electronUpdater: { autoUpdater: AutoUpdater };
export default electronUpdater;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 B

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 B

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -8,7 +8,6 @@ import {
type OpenDialogOptions,
type SaveDialogOptions,
} from "electron";
import { autoUpdater } from "electron-updater";
export interface WindowState {
x?: number;
@@ -167,34 +166,54 @@ export function showDesktopNotification(
}
export function setupAutoUpdater(mainWindow?: BrowserWindow): void {
try {
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
void (async () => {
try {
const mod = (await import("electron-updater")) as {
default?: { autoUpdater?: unknown };
autoUpdater?: unknown;
};
const autoUpdater = (mod.default?.autoUpdater ?? mod.autoUpdater) as
| {
autoDownload: boolean;
autoInstallOnAppQuit: boolean;
on: (event: string, handler: (...args: unknown[]) => void) => unknown;
checkForUpdates: () => Promise<unknown>;
}
| undefined;
autoUpdater.on("update-available", (info) => {
showDesktopNotification("Fusion Update Available", "Update available — downloading in background", {
silent: true,
if (!autoUpdater) {
console.warn("[desktop/native] Auto-updater module loaded without autoUpdater export");
return;
}
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.on("update-available", (info) => {
showDesktopNotification("Fusion Update Available", "Update available — downloading in background", {
silent: true,
});
mainWindow?.webContents.send("update-available", info);
});
mainWindow?.webContents.send("update-available", info);
});
autoUpdater.on("update-downloaded", (info) => {
showDesktopNotification("Fusion Update Ready", "Update ready — will install on quit", {
silent: true,
autoUpdater.on("update-downloaded", (info) => {
showDesktopNotification("Fusion Update Ready", "Update ready — will install on quit", {
silent: true,
});
mainWindow?.webContents.send("update-downloaded", info);
});
mainWindow?.webContents.send("update-downloaded", info);
});
autoUpdater.on("error", (error) => {
console.error("[desktop/native] Auto-updater error", error);
});
autoUpdater.on("error", (error) => {
console.error("[desktop/native] Auto-updater error", error);
});
void autoUpdater.checkForUpdates().catch((error) => {
console.error("[desktop/native] Auto-updater check failed", error);
});
} catch (error) {
console.error("[desktop/native] Auto-updater unavailable", error);
}
await autoUpdater.checkForUpdates().catch((error: unknown) => {
console.error("[desktop/native] Auto-updater check failed", error);
});
} catch (error) {
console.warn("[desktop/native] Auto-updater unavailable", error);
}
})();
}
function normalizeServerBaseUrl(serverUrl: string): string | null {