From b2458dcbc2a744e2622aa44bf00f7fc615266cc7 Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Sat, 18 Jul 2026 19:19:49 +0300 Subject: [PATCH] fix(code): restore external app icons Run the macOS icon extractor from its unpacked binary and add cmux to the detected terminal applications. Generated-By: PostHog Code Task-Id: 02c8320e-3a8f-47a2-893a-6381dd31369c --- apps/code/runtime-dependencies.ts | 4 +- .../electron-file-icon.test.ts | 16 ++++++ .../platform-adapters/electron-file-icon.ts | 49 ++++++++++++++----- .../services/external-apps/external-apps.ts | 5 ++ 4 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 apps/code/src/main/platform-adapters/electron-file-icon.test.ts diff --git a/apps/code/runtime-dependencies.ts b/apps/code/runtime-dependencies.ts index 67f612cac5..0d2f45bc70 100644 --- a/apps/code/runtime-dependencies.ts +++ b/apps/code/runtime-dependencies.ts @@ -37,8 +37,8 @@ export const requiredNativeModules = [ "better-sqlite3", ]; -// file-icon (and its p-map dependency) is only used on macOS. -export const macOnlyNativeModules = ["file-icon", "p-map"]; +// file-icon is only used on macOS. +export const macOnlyNativeModules = ["file-icon"]; // The subset that ships compiled .node binaries and must be unpacked from asar. const asarUnpackModules = [ diff --git a/apps/code/src/main/platform-adapters/electron-file-icon.test.ts b/apps/code/src/main/platform-adapters/electron-file-icon.test.ts new file mode 100644 index 0000000000..e29b0dd990 --- /dev/null +++ b/apps/code/src/main/platform-adapters/electron-file-icon.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { resolveMacFileIconBinary } from "./electron-file-icon"; + +describe("resolveMacFileIconBinary", () => { + it("resolves the packaged extractor outside the ASAR", () => { + expect( + resolveMacFileIconBinary( + "/Applications/PostHog Code.app/Contents/Resources/app.asar", + true, + "/unused/node_modules/file-icon/index.js", + ), + ).toBe( + "/Applications/PostHog Code.app/Contents/Resources/app.asar.unpacked/node_modules/file-icon/file-icon", + ); + }); +}); diff --git a/apps/code/src/main/platform-adapters/electron-file-icon.ts b/apps/code/src/main/platform-adapters/electron-file-icon.ts index 683f79e999..31fea36327 100644 --- a/apps/code/src/main/platform-adapters/electron-file-icon.ts +++ b/apps/code/src/main/platform-adapters/electron-file-icon.ts @@ -1,19 +1,29 @@ +import { execFile } from "node:child_process"; +import path from "node:path"; import type { IFileIcon } from "@posthog/platform/file-icon"; import { app } from "electron"; import { injectable } from "inversify"; -type FileIconModule = typeof import("file-icon"); +const FILE_ICON_MAX_BUFFER_BYTES = 100 * 1024 * 1024; + +export function resolveMacFileIconBinary( + appPath: string, + isPackaged: boolean, + modulePath: string, +): string { + const resolvedModulePath = isPackaged + ? path.join(`${appPath}.unpacked`, "node_modules", "file-icon", "index.js") + : modulePath; + return path.join(path.dirname(resolvedModulePath), "file-icon"); +} @injectable() export class ElectronFileIcon implements IFileIcon { - private fileIconModule: FileIconModule | undefined; - public async getAsDataUrl(filePath: string): Promise { try { if (process.platform === "darwin") { - const mod = await this.loadFileIconModule(); - const uint8Array = await mod.fileIconToBuffer(filePath, { size: 64 }); - const base64 = Buffer.from(uint8Array).toString("base64"); + const buffer = await this.getMacFileIcon(filePath); + const base64 = buffer.toString("base64"); return `data:image/png;base64,${base64}`; } @@ -25,10 +35,27 @@ export class ElectronFileIcon implements IFileIcon { } } - private async loadFileIconModule(): Promise { - if (!this.fileIconModule) { - this.fileIconModule = await import("file-icon"); - } - return this.fileIconModule; + private getMacFileIcon(filePath: string): Promise { + const binaryPath = resolveMacFileIconBinary( + app.getAppPath(), + app.isPackaged, + require.resolve("file-icon"), + ); + const input = JSON.stringify([{ appOrPID: filePath, size: 64 }]); + + return new Promise((resolve, reject) => { + execFile( + binaryPath, + [input], + { encoding: "buffer", maxBuffer: FILE_ICON_MAX_BUFFER_BYTES }, + (error, stdout) => { + if (error) { + reject(error); + return; + } + resolve(Buffer.from(stdout)); + }, + ); + }); } } diff --git a/packages/workspace-server/src/services/external-apps/external-apps.ts b/packages/workspace-server/src/services/external-apps/external-apps.ts index 542348b8ee..95df5dd7e2 100644 --- a/packages/workspace-server/src/services/external-apps/external-apps.ts +++ b/packages/workspace-server/src/services/external-apps/external-apps.ts @@ -380,6 +380,10 @@ export class ExternalAppsService { type: "terminal", darwin: { path: "/Applications/Ghostty.app" }, }, + cmux: { + type: "terminal", + darwin: { path: "/Applications/cmux.app" }, + }, kitty: { type: "terminal", darwin: { path: "/Applications/kitty.app" }, @@ -477,6 +481,7 @@ export class ExternalAppsService { alacritty: "Alacritty", kitty: "Kitty", ghostty: "Ghostty", + cmux: "cmux", hyper: "Hyper", tabby: "Tabby", rio: "Rio",