feat(doctor): detect OpenCode desktop GUI installations on all platforms (#1352)
* feat(doctor): detect OpenCode desktop GUI installations on all platforms - Add getDesktopAppPaths() returning platform-specific desktop app paths - macOS: /Applications/OpenCode.app, ~/Applications/OpenCode.app - Windows: C:\Program Files\OpenCode, %LOCALAPPDATA%\Programs\OpenCode - Linux: /opt/opencode, /snap/bin, ~/.local/bin - Add findDesktopBinary() for testable desktop path detection - Modify findOpenCodeBinary() to check desktop paths as fallback Fixes #1310 * fix: use verified installation paths from OpenCode source Verified paths from sst/opencode Tauri config: macOS: - /Applications/OpenCode.app/Contents/MacOS/OpenCode (capital C) Windows: - C:\Program Files\OpenCode\OpenCode.exe - %LOCALAPPDATA%\OpenCode\OpenCode.exe - Removed hardcoded paths, use ProgramFiles env var - Filter empty paths when env vars undefined Linux: - /usr/bin/opencode (deb symlink) - /usr/lib/opencode/opencode (deb actual binary) - ~/Applications/*.AppImage (user AppImage) - Removed non-existent /opt/opencode and /snap/bin paths * chore: remove unused imports from tests
This commit is contained in:
@@ -224,4 +224,108 @@ describe("opencode check", () => {
|
||||
expect(typeof def.check).toBe("function")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getDesktopAppPaths", () => {
|
||||
it("returns macOS desktop app paths for darwin platform", () => {
|
||||
// given darwin platform
|
||||
const platform: NodeJS.Platform = "darwin"
|
||||
|
||||
// when getting desktop paths
|
||||
const paths = opencode.getDesktopAppPaths(platform)
|
||||
|
||||
// then should include macOS app bundle paths with correct binary name
|
||||
expect(paths).toContain("/Applications/OpenCode.app/Contents/MacOS/OpenCode")
|
||||
expect(paths.some((p) => p.includes("Applications/OpenCode.app"))).toBe(true)
|
||||
})
|
||||
|
||||
it("returns Windows desktop app paths for win32 platform when env vars set", () => {
|
||||
// given win32 platform with env vars set
|
||||
const platform: NodeJS.Platform = "win32"
|
||||
const originalProgramFiles = process.env.ProgramFiles
|
||||
const originalLocalAppData = process.env.LOCALAPPDATA
|
||||
process.env.ProgramFiles = "C:\\Program Files"
|
||||
process.env.LOCALAPPDATA = "C:\\Users\\Test\\AppData\\Local"
|
||||
|
||||
// when getting desktop paths
|
||||
const paths = opencode.getDesktopAppPaths(platform)
|
||||
|
||||
// then should include Windows program paths with correct binary name
|
||||
expect(paths.some((p) => p.includes("Program Files"))).toBe(true)
|
||||
expect(paths.some((p) => p.endsWith("OpenCode.exe"))).toBe(true)
|
||||
expect(paths.every((p) => p.startsWith("C:\\"))).toBe(true)
|
||||
|
||||
// cleanup
|
||||
process.env.ProgramFiles = originalProgramFiles
|
||||
process.env.LOCALAPPDATA = originalLocalAppData
|
||||
})
|
||||
|
||||
it("returns empty array for win32 when all env vars undefined", () => {
|
||||
// given win32 platform with no env vars
|
||||
const platform: NodeJS.Platform = "win32"
|
||||
const originalProgramFiles = process.env.ProgramFiles
|
||||
const originalLocalAppData = process.env.LOCALAPPDATA
|
||||
delete process.env.ProgramFiles
|
||||
delete process.env.LOCALAPPDATA
|
||||
|
||||
// when getting desktop paths
|
||||
const paths = opencode.getDesktopAppPaths(platform)
|
||||
|
||||
// then should return empty array (no relative paths)
|
||||
expect(paths).toEqual([])
|
||||
|
||||
// cleanup
|
||||
process.env.ProgramFiles = originalProgramFiles
|
||||
process.env.LOCALAPPDATA = originalLocalAppData
|
||||
})
|
||||
|
||||
it("returns Linux desktop app paths for linux platform", () => {
|
||||
// given linux platform
|
||||
const platform: NodeJS.Platform = "linux"
|
||||
|
||||
// when getting desktop paths
|
||||
const paths = opencode.getDesktopAppPaths(platform)
|
||||
|
||||
// then should include verified Linux installation paths
|
||||
expect(paths).toContain("/usr/bin/opencode")
|
||||
expect(paths).toContain("/usr/lib/opencode/opencode")
|
||||
expect(paths.some((p) => p.includes("AppImage"))).toBe(true)
|
||||
})
|
||||
|
||||
it("returns empty array for unsupported platforms", () => {
|
||||
// given unsupported platform
|
||||
const platform = "freebsd" as NodeJS.Platform
|
||||
|
||||
// when getting desktop paths
|
||||
const paths = opencode.getDesktopAppPaths(platform)
|
||||
|
||||
// then should return empty array
|
||||
expect(paths).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("findOpenCodeBinary with desktop fallback", () => {
|
||||
it("falls back to desktop paths when PATH binary not found", async () => {
|
||||
// given no binary in PATH but desktop app exists
|
||||
const existsSyncMock = (p: string) =>
|
||||
p === "/Applications/OpenCode.app/Contents/MacOS/OpenCode"
|
||||
|
||||
// when finding binary with mocked filesystem
|
||||
const result = await opencode.findDesktopBinary("darwin", existsSyncMock)
|
||||
|
||||
// then should find desktop app
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.path).toBe("/Applications/OpenCode.app/Contents/MacOS/OpenCode")
|
||||
})
|
||||
|
||||
it("returns null when no desktop binary found", async () => {
|
||||
// given no binary exists
|
||||
const existsSyncMock = () => false
|
||||
|
||||
// when finding binary
|
||||
const result = await opencode.findDesktopBinary("darwin", existsSyncMock)
|
||||
|
||||
// then should return null
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,8 +1,45 @@
|
||||
import { existsSync } from "node:fs"
|
||||
import { homedir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
import type { CheckResult, CheckDefinition, OpenCodeInfo } from "../types"
|
||||
import { CHECK_IDS, CHECK_NAMES, MIN_OPENCODE_VERSION, OPENCODE_BINARIES } from "../constants"
|
||||
|
||||
const WINDOWS_EXECUTABLE_EXTS = [".exe", ".cmd", ".bat", ".ps1"]
|
||||
|
||||
export function getDesktopAppPaths(platform: NodeJS.Platform): string[] {
|
||||
const home = homedir()
|
||||
|
||||
switch (platform) {
|
||||
case "darwin":
|
||||
return [
|
||||
"/Applications/OpenCode.app/Contents/MacOS/OpenCode",
|
||||
join(home, "Applications", "OpenCode.app", "Contents", "MacOS", "OpenCode"),
|
||||
]
|
||||
case "win32": {
|
||||
const programFiles = process.env.ProgramFiles
|
||||
const localAppData = process.env.LOCALAPPDATA
|
||||
|
||||
const paths: string[] = []
|
||||
if (programFiles) {
|
||||
paths.push(join(programFiles, "OpenCode", "OpenCode.exe"))
|
||||
}
|
||||
if (localAppData) {
|
||||
paths.push(join(localAppData, "OpenCode", "OpenCode.exe"))
|
||||
}
|
||||
return paths
|
||||
}
|
||||
case "linux":
|
||||
return [
|
||||
"/usr/bin/opencode",
|
||||
"/usr/lib/opencode/opencode",
|
||||
join(home, "Applications", "opencode-desktop-linux-x86_64.AppImage"),
|
||||
join(home, "Applications", "opencode-desktop-linux-aarch64.AppImage"),
|
||||
]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export function getBinaryLookupCommand(platform: NodeJS.Platform): "which" | "where" {
|
||||
return platform === "win32" ? "where" : "which"
|
||||
}
|
||||
@@ -52,6 +89,19 @@ export function buildVersionCommand(
|
||||
return [binaryPath, "--version"]
|
||||
}
|
||||
|
||||
export function findDesktopBinary(
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
checkExists: (path: string) => boolean = existsSync
|
||||
): { binary: string; path: string } | null {
|
||||
const desktopPaths = getDesktopAppPaths(platform)
|
||||
for (const desktopPath of desktopPaths) {
|
||||
if (checkExists(desktopPath)) {
|
||||
return { binary: "opencode", path: desktopPath }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function findOpenCodeBinary(): Promise<{ binary: string; path: string } | null> {
|
||||
for (const binary of OPENCODE_BINARIES) {
|
||||
try {
|
||||
@@ -63,6 +113,12 @@ export async function findOpenCodeBinary(): Promise<{ binary: string; path: stri
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const desktopResult = findDesktopBinary()
|
||||
if (desktopResult) {
|
||||
return desktopResult
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user