* feat: add Windows ARM64 support with x64-baseline CLI workaround Windows ARM64 native opencode.exe fails with a Bun FFI/TinyCC dlopen error (anomalyco/opencode#19130). As a temporary workaround: - Bundle x64-baseline OpenCode CLI on ARM64 instead of native ARM64 (prepare-opencode-cli.mjs, env-runtime.js) - Disable OpenCode self-upgrade on ARM64 in server, VS Code, and UI (upgrade-capability.js, opencode-upgrade-runtime.ts, useUIStore.ts, OpenCodeCliSettings.tsx, search.ts, SettingsView.tsx, platform.ts) - Add ARM64 Windows cross-compile builds to release and smoke workflows (release.yml, release-desktop-smoke.yml) - Refactor Windows latest.yml to use combine-electron-manifests pattern matching macOS, since two arches now produce per-arch manifests The CI ARM64 build itself is permanent; only the x64-baseline CLI bundling and upgrade disablement are temporary and should be reverted when the upstream issue is resolved. * fix(desktop): select Windows updater by architecture --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
75 lines
3.3 KiB
TypeScript
75 lines
3.3 KiB
TypeScript
import { isDesktopShell, isVSCodeRuntime } from '@/lib/desktop';
|
|
|
|
/** True when running inside the native Capacitor shell (iOS/Android app), not the web/PWA. */
|
|
export const isCapacitorApp = (): boolean => {
|
|
if (typeof window === 'undefined') return false;
|
|
const capacitor = (window as typeof window & { Capacitor?: { isNativePlatform?: () => boolean } }).Capacitor;
|
|
return capacitor?.isNativePlatform?.() === true || window.location.protocol === 'capacitor:';
|
|
};
|
|
|
|
// TEMPORARY WORKAROUND — Windows ARM64: native opencode.exe fails with a Bun
|
|
// FFI/TinyCC dlopen error (https://github.com/anomalyco/opencode/issues/19130).
|
|
// Suppress OpenCode update UI on ARM64 so it can't self-upgrade to the broken
|
|
// ARM64 build. Remove this helper and its call sites when resolved upstream.
|
|
export const isWindowsArm64 = (): boolean => {
|
|
if (typeof window === 'undefined') return false;
|
|
|
|
const electronArch = window.__OPENCHAMBER_ELECTRON__?.arch?.toLowerCase?.();
|
|
if (electronArch === 'arm64' || electronArch === 'aarch64') {
|
|
const platform = (navigator.platform || '').toLowerCase();
|
|
return platform.includes('win');
|
|
}
|
|
|
|
const vscodeArch = (window as { __VSCODE_CONFIG__?: { arch?: string } }).__VSCODE_CONFIG__?.arch?.toLowerCase?.();
|
|
if (vscodeArch === 'arm64' || vscodeArch === 'aarch64') {
|
|
const platform = (navigator.platform || '').toLowerCase();
|
|
return platform.includes('win');
|
|
}
|
|
|
|
const nav = (navigator as Navigator & { userAgentData?: { architecture?: string; platform?: string } }).userAgentData;
|
|
if (nav?.architecture?.toLowerCase?.() === 'arm64' || nav?.architecture?.toLowerCase?.() === 'aarch64') {
|
|
const platform = (nav.platform || navigator.platform || '').toLowerCase();
|
|
return platform.includes('win');
|
|
}
|
|
|
|
const ua = navigator.userAgent.toLowerCase();
|
|
if ((ua.includes('aarch64') || ua.includes('arm64') || ua.includes('armv')) && ua.includes('windows')) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* True when running inside the native Capacitor shell on an iPad.
|
|
* Capacitor reports 'ios' for both iPhone and iPad; iPadOS WKWebView
|
|
* masquerades as macOS Safari, so the only reliable tell is a Mac-like
|
|
* platform with real touch points (or a legacy explicit iPad UA).
|
|
*/
|
|
export const isIPadApp = (): boolean => {
|
|
if (typeof window === 'undefined' || !isCapacitorApp()) return false;
|
|
if (getClientPlatform() !== 'ios') return false;
|
|
const userAgent = navigator.userAgent || '';
|
|
const maxTouchPoints = navigator.maxTouchPoints ?? 0;
|
|
return /iPad/i.test(userAgent)
|
|
|| (/Macintosh|MacIntel/i.test(userAgent) && maxTouchPoints > 1);
|
|
};
|
|
|
|
export type ClientPlatform = 'ios' | 'android' | 'vscode' | 'desktop' | 'web';
|
|
|
|
/**
|
|
* The runtime surface this client is. Used by the push presence model: only 'ios'/'android'
|
|
* count as mobile (push recipients); everything else is an interactive surface that suppresses
|
|
* mobile push while visible.
|
|
*/
|
|
export const getClientPlatform = (): ClientPlatform => {
|
|
if (typeof window !== 'undefined') {
|
|
const capacitor = (window as typeof window & { Capacitor?: { getPlatform?: () => string } }).Capacitor;
|
|
const native = capacitor?.getPlatform?.();
|
|
if (native === 'ios' || native === 'android') return native;
|
|
}
|
|
if (isVSCodeRuntime()) return 'vscode';
|
|
if (isDesktopShell()) return 'desktop';
|
|
return 'web';
|
|
};
|