Merge pull request #855 from luojiyin1987/fix/doctor-windows-opencode
fix: handle opencode.ps1 in doctor on Windows
This commit is contained in:
@@ -43,6 +43,94 @@ describe("opencode check", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("command helpers", () => {
|
||||
it("selects where on Windows", () => {
|
||||
// #given win32 platform
|
||||
// #when selecting lookup command
|
||||
// #then should use where
|
||||
expect(opencode.getBinaryLookupCommand("win32")).toBe("where")
|
||||
})
|
||||
|
||||
it("selects which on non-Windows", () => {
|
||||
// #given linux platform
|
||||
// #when selecting lookup command
|
||||
// #then should use which
|
||||
expect(opencode.getBinaryLookupCommand("linux")).toBe("which")
|
||||
expect(opencode.getBinaryLookupCommand("darwin")).toBe("which")
|
||||
})
|
||||
|
||||
it("parses command output into paths", () => {
|
||||
// #given raw output with multiple lines and spaces
|
||||
const output = "C:\\\\bin\\\\opencode.ps1\r\nC:\\\\bin\\\\opencode.exe\n\n"
|
||||
|
||||
// #when parsing
|
||||
const paths = opencode.parseBinaryPaths(output)
|
||||
|
||||
// #then should return trimmed, non-empty paths
|
||||
expect(paths).toEqual(["C:\\\\bin\\\\opencode.ps1", "C:\\\\bin\\\\opencode.exe"])
|
||||
})
|
||||
|
||||
it("prefers exe/cmd/bat over ps1 on Windows", () => {
|
||||
// #given windows paths
|
||||
const paths = [
|
||||
"C:\\\\bin\\\\opencode.ps1",
|
||||
"C:\\\\bin\\\\opencode.cmd",
|
||||
"C:\\\\bin\\\\opencode.exe",
|
||||
]
|
||||
|
||||
// #when selecting binary
|
||||
const selected = opencode.selectBinaryPath(paths, "win32")
|
||||
|
||||
// #then should prefer exe
|
||||
expect(selected).toBe("C:\\\\bin\\\\opencode.exe")
|
||||
})
|
||||
|
||||
it("falls back to ps1 when it is the only Windows candidate", () => {
|
||||
// #given only ps1 path
|
||||
const paths = ["C:\\\\bin\\\\opencode.ps1"]
|
||||
|
||||
// #when selecting binary
|
||||
const selected = opencode.selectBinaryPath(paths, "win32")
|
||||
|
||||
// #then should return ps1 path
|
||||
expect(selected).toBe("C:\\\\bin\\\\opencode.ps1")
|
||||
})
|
||||
|
||||
it("builds PowerShell command for ps1 on Windows", () => {
|
||||
// #given a ps1 path on Windows
|
||||
const command = opencode.buildVersionCommand(
|
||||
"C:\\\\bin\\\\opencode.ps1",
|
||||
"win32"
|
||||
)
|
||||
|
||||
// #when building command
|
||||
// #then should use PowerShell
|
||||
expect(command).toEqual([
|
||||
"powershell",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"C:\\\\bin\\\\opencode.ps1",
|
||||
"--version",
|
||||
])
|
||||
})
|
||||
|
||||
it("builds direct command for non-ps1 binaries", () => {
|
||||
// #given an exe on Windows and a binary on linux
|
||||
const winCommand = opencode.buildVersionCommand(
|
||||
"C:\\\\bin\\\\opencode.exe",
|
||||
"win32"
|
||||
)
|
||||
const linuxCommand = opencode.buildVersionCommand("opencode", "linux")
|
||||
|
||||
// #when building commands
|
||||
// #then should execute directly
|
||||
expect(winCommand).toEqual(["C:\\\\bin\\\\opencode.exe", "--version"])
|
||||
expect(linuxCommand).toEqual(["opencode", "--version"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("getOpenCodeInfo", () => {
|
||||
it("returns installed: false when binary not found", async () => {
|
||||
// #given no opencode binary
|
||||
|
||||
@@ -1,14 +1,70 @@
|
||||
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 getBinaryLookupCommand(platform: NodeJS.Platform): "which" | "where" {
|
||||
return platform === "win32" ? "where" : "which"
|
||||
}
|
||||
|
||||
export function parseBinaryPaths(output: string): string[] {
|
||||
return output
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
}
|
||||
|
||||
export function selectBinaryPath(
|
||||
paths: string[],
|
||||
platform: NodeJS.Platform
|
||||
): string | null {
|
||||
if (paths.length === 0) return null
|
||||
if (platform !== "win32") return paths[0]
|
||||
|
||||
const normalized = paths.map((path) => path.toLowerCase())
|
||||
for (const ext of WINDOWS_EXECUTABLE_EXTS) {
|
||||
const index = normalized.findIndex((path) => path.endsWith(ext))
|
||||
if (index !== -1) return paths[index]
|
||||
}
|
||||
|
||||
return paths[0]
|
||||
}
|
||||
|
||||
export function buildVersionCommand(
|
||||
binaryPath: string,
|
||||
platform: NodeJS.Platform
|
||||
): string[] {
|
||||
if (
|
||||
platform === "win32" &&
|
||||
binaryPath.toLowerCase().endsWith(".ps1")
|
||||
) {
|
||||
return [
|
||||
"powershell",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
binaryPath,
|
||||
"--version",
|
||||
]
|
||||
}
|
||||
|
||||
return [binaryPath, "--version"]
|
||||
}
|
||||
|
||||
export async function findOpenCodeBinary(): Promise<{ binary: string; path: string } | null> {
|
||||
for (const binary of OPENCODE_BINARIES) {
|
||||
try {
|
||||
const proc = Bun.spawn(["which", binary], { stdout: "pipe", stderr: "pipe" })
|
||||
const lookupCommand = getBinaryLookupCommand(process.platform)
|
||||
const proc = Bun.spawn([lookupCommand, binary], { stdout: "pipe", stderr: "pipe" })
|
||||
const output = await new Response(proc.stdout).text()
|
||||
await proc.exited
|
||||
if (proc.exitCode === 0) {
|
||||
return { binary, path: output.trim() }
|
||||
const paths = parseBinaryPaths(output)
|
||||
const selectedPath = selectBinaryPath(paths, process.platform)
|
||||
if (selectedPath) {
|
||||
return { binary, path: selectedPath }
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
continue
|
||||
@@ -17,9 +73,13 @@ export async function findOpenCodeBinary(): Promise<{ binary: string; path: stri
|
||||
return null
|
||||
}
|
||||
|
||||
export async function getOpenCodeVersion(binary: string): Promise<string | null> {
|
||||
export async function getOpenCodeVersion(
|
||||
binaryPath: string,
|
||||
platform: NodeJS.Platform = process.platform
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const proc = Bun.spawn([binary, "--version"], { stdout: "pipe", stderr: "pipe" })
|
||||
const command = buildVersionCommand(binaryPath, platform)
|
||||
const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" })
|
||||
const output = await new Response(proc.stdout).text()
|
||||
await proc.exited
|
||||
if (proc.exitCode === 0) {
|
||||
@@ -61,7 +121,7 @@ export async function getOpenCodeInfo(): Promise<OpenCodeInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
const version = await getOpenCodeVersion(binaryInfo.binary)
|
||||
const version = await getOpenCodeVersion(binaryInfo.path ?? binaryInfo.binary)
|
||||
|
||||
return {
|
||||
installed: true,
|
||||
|
||||
Reference in New Issue
Block a user