fix(desktop): warn about Rosetta installs on Apple Silicon (#621)
* fix(desktop): warn about Rosetta installs on Apple Silicon * docs: note workspace build sync before type fixes
This commit is contained in:
@@ -55,6 +55,7 @@ import { shouldUseDesktopDaemon } from "@/desktop/daemon/desktop-daemon";
|
||||
import { listenToDesktopEvent } from "@/desktop/electron/events";
|
||||
import { updateDesktopWindowControls } from "@/desktop/electron/window";
|
||||
import { getDesktopHost } from "@/desktop/host";
|
||||
import { RosettaCalloutSource } from "@/desktop/updates/rosetta-callout-source";
|
||||
import { UpdateCalloutSource } from "@/desktop/updates/update-callout-source";
|
||||
import { useActiveWorktreeNewAction } from "@/hooks/use-active-worktree-new-action";
|
||||
import { useColorScheme } from "@/hooks/use-color-scheme";
|
||||
@@ -464,6 +465,7 @@ function AppContainer({
|
||||
</View>
|
||||
{isCompactLayout && chromeEnabled && <LeftSidebar selectedAgentId={selectedAgentId} />}
|
||||
<DownloadToast />
|
||||
<RosettaCalloutSource />
|
||||
<UpdateCalloutSource />
|
||||
<WorktreeSetupCalloutSource />
|
||||
<CommandCenter />
|
||||
|
||||
@@ -92,4 +92,31 @@ describe("desktop-updates helpers", () => {
|
||||
expect(diagnostics).toContain("STDOUT:\nstdout text");
|
||||
expect(diagnostics).toContain("STDERR:\nstderr text");
|
||||
});
|
||||
|
||||
it("parses runtime info defensively", async () => {
|
||||
const { parseDesktopRuntimeInfo } = await loadModuleForPlatform("web");
|
||||
|
||||
expect(
|
||||
parseDesktopRuntimeInfo({
|
||||
appVersion: " 0.1.64 ",
|
||||
runningUnderARM64Translation: true,
|
||||
}),
|
||||
).toEqual({
|
||||
appVersion: "0.1.64",
|
||||
runningUnderARM64Translation: true,
|
||||
});
|
||||
expect(parseDesktopRuntimeInfo(null)).toEqual({
|
||||
appVersion: null,
|
||||
runningUnderARM64Translation: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("builds the direct Apple Silicon DMG URL from a version", async () => {
|
||||
const { buildMacAppleSiliconDownloadUrl } = await loadModuleForPlatform("web");
|
||||
|
||||
expect(buildMacAppleSiliconDownloadUrl("v0.1.64")).toBe(
|
||||
"https://github.com/getpaseo/paseo/releases/download/v0.1.64/Paseo-0.1.64-arm64.dmg",
|
||||
);
|
||||
expect(buildMacAppleSiliconDownloadUrl(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,11 @@ export interface DesktopAppUpdateInstallResult {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface DesktopRuntimeInfo {
|
||||
appVersion: string | null;
|
||||
runningUnderARM64Translation: boolean;
|
||||
}
|
||||
|
||||
export type DesktopReleaseChannel = "stable" | "beta";
|
||||
|
||||
export interface LocalDaemonUpdateResult {
|
||||
@@ -30,6 +35,8 @@ export interface LocalDaemonVersionResult {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const RELEASE_DOWNLOAD_BASE_URL = "https://github.com/getpaseo/paseo/releases/download";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
@@ -71,6 +78,25 @@ export async function getLocalDaemonVersion(): Promise<LocalDaemonVersionResult>
|
||||
return parseLocalDaemonVersionResult(result);
|
||||
}
|
||||
|
||||
export function parseDesktopRuntimeInfo(raw: unknown): DesktopRuntimeInfo {
|
||||
if (!isRecord(raw)) {
|
||||
return {
|
||||
appVersion: null,
|
||||
runningUnderARM64Translation: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
appVersion: toStringOrNull(raw.appVersion),
|
||||
runningUnderARM64Translation: raw.runningUnderARM64Translation === true,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getDesktopRuntimeInfo(): Promise<DesktopRuntimeInfo> {
|
||||
const result = await invokeDesktopCommand<unknown>("desktop_get_runtime_info");
|
||||
return parseDesktopRuntimeInfo(result);
|
||||
}
|
||||
|
||||
export async function checkDesktopAppUpdate({
|
||||
releaseChannel,
|
||||
}: {
|
||||
@@ -153,6 +179,15 @@ export function formatVersionWithPrefix(version: string | null | undefined): str
|
||||
return value.startsWith("v") ? value : `v${value}`;
|
||||
}
|
||||
|
||||
export function buildMacAppleSiliconDownloadUrl(version: string | null | undefined): string | null {
|
||||
const normalizedVersion = normalizeVersionForComparison(version);
|
||||
if (!normalizedVersion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${RELEASE_DOWNLOAD_BASE_URL}/v${normalizedVersion}/Paseo-${normalizedVersion}-arm64.dmg`;
|
||||
}
|
||||
|
||||
export function buildDaemonUpdateDiagnostics(result: LocalDaemonUpdateResult): string {
|
||||
const stdout = result.stdout.length > 0 ? result.stdout : "(empty)";
|
||||
const stderr = result.stderr.length > 0 ? result.stderr : "(empty)";
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { SidebarCalloutDescriptionText } from "@/components/sidebar-callout";
|
||||
import { getIsElectronMac } from "@/constants/platform";
|
||||
import { useSidebarCallouts } from "@/contexts/sidebar-callout-context";
|
||||
import {
|
||||
buildMacAppleSiliconDownloadUrl,
|
||||
getDesktopRuntimeInfo,
|
||||
type DesktopRuntimeInfo,
|
||||
} from "@/desktop/updates/desktop-updates";
|
||||
import { useStableEvent } from "@/hooks/use-stable-event";
|
||||
import { openExternalUrl } from "@/utils/open-external-url";
|
||||
|
||||
const FALLBACK_DOWNLOAD_URL = "https://paseo.sh/download";
|
||||
|
||||
function RosettaCalloutDescription() {
|
||||
return (
|
||||
<>
|
||||
<SidebarCalloutDescriptionText>
|
||||
You're running the Intel build of Paseo under Rosetta on Apple Silicon.
|
||||
</SidebarCalloutDescriptionText>
|
||||
<SidebarCalloutDescriptionText>
|
||||
This causes high CPU usage. Download the Apple Silicon build to fix it.
|
||||
</SidebarCalloutDescriptionText>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function RosettaCalloutSource() {
|
||||
const callouts = useSidebarCallouts();
|
||||
const [runtimeInfo, setRuntimeInfo] = useState<DesktopRuntimeInfo | null>(null);
|
||||
const isElectronMac = getIsElectronMac();
|
||||
|
||||
const openDownload = useStableEvent(() => {
|
||||
const downloadUrl =
|
||||
buildMacAppleSiliconDownloadUrl(runtimeInfo?.appVersion) ?? FALLBACK_DOWNLOAD_URL;
|
||||
void openExternalUrl(downloadUrl);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isElectronMac) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
void getDesktopRuntimeInfo()
|
||||
.then((nextRuntimeInfo) => {
|
||||
if (!cancelled) {
|
||||
setRuntimeInfo(nextRuntimeInfo);
|
||||
}
|
||||
return nextRuntimeInfo;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn("[RosettaCallout] Failed to load desktop runtime info", error);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [isElectronMac]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isElectronMac || runtimeInfo?.runningUnderARM64Translation !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
return callouts.show({
|
||||
id: "desktop-rosetta-warning",
|
||||
priority: 300,
|
||||
title: "Download the Apple Silicon build",
|
||||
description: <RosettaCalloutDescription />,
|
||||
variant: "error",
|
||||
dismissible: false,
|
||||
actions: [
|
||||
{
|
||||
label: "Download",
|
||||
onPress: openDownload,
|
||||
variant: "primary",
|
||||
},
|
||||
],
|
||||
testID: "rosetta-callout",
|
||||
});
|
||||
}, [callouts, isElectronMac, openDownload, runtimeInfo]);
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user