fix(lsp): improve Windows server detection
This commit is contained in:
parent
00508e9959
commit
f9b9b59658
110
src/tools/lsp/config.test.ts
Normal file
110
src/tools/lsp/config.test.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
|
||||||
|
import { isServerInstalled } from "./config"
|
||||||
|
import { mkdtempSync, rmSync, writeFileSync } from "fs"
|
||||||
|
import { join } from "path"
|
||||||
|
import { tmpdir } from "os"
|
||||||
|
|
||||||
|
describe("isServerInstalled", () => {
|
||||||
|
let tempDir: string
|
||||||
|
let savedEnv: { [key: string]: string | undefined }
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tempDir = mkdtempSync(join(tmpdir(), "lsp-config-test-"))
|
||||||
|
savedEnv = {
|
||||||
|
PATH: process.env.PATH,
|
||||||
|
Path: process.env.Path,
|
||||||
|
PATHEXT: process.env.PATHEXT,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
try {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true })
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to clean up temp dir: ${e}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = ["PATH", "Path", "PATHEXT"]
|
||||||
|
for (const key of keys) {
|
||||||
|
const val = savedEnv[key]
|
||||||
|
if (val === undefined) {
|
||||||
|
delete process.env[key]
|
||||||
|
} else {
|
||||||
|
process.env[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("detects executable in PATH", () => {
|
||||||
|
const binName = "test-lsp-server"
|
||||||
|
const ext = process.platform === "win32" ? ".cmd" : ""
|
||||||
|
const binPath = join(tempDir, binName + ext)
|
||||||
|
|
||||||
|
writeFileSync(binPath, "echo hello")
|
||||||
|
|
||||||
|
const pathSep = process.platform === "win32" ? ";" : ":"
|
||||||
|
process.env.PATH = `${tempDir}${pathSep}${process.env.PATH || ""}`
|
||||||
|
|
||||||
|
expect(isServerInstalled([binName])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns false for missing executable", () => {
|
||||||
|
expect(isServerInstalled(["non-existent-server"])).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
test("Windows: detects executable with Path env var", () => {
|
||||||
|
const binName = "test-lsp-server-case"
|
||||||
|
const binPath = join(tempDir, binName + ".cmd")
|
||||||
|
writeFileSync(binPath, "echo hello")
|
||||||
|
|
||||||
|
delete process.env.PATH
|
||||||
|
process.env.Path = tempDir
|
||||||
|
|
||||||
|
expect(isServerInstalled([binName])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Windows: respects PATHEXT", () => {
|
||||||
|
const binName = "test-lsp-server-custom"
|
||||||
|
const binPath = join(tempDir, binName + ".COM")
|
||||||
|
writeFileSync(binPath, "echo hello")
|
||||||
|
|
||||||
|
process.env.PATH = tempDir
|
||||||
|
process.env.PATHEXT = ".COM;.EXE"
|
||||||
|
|
||||||
|
expect(isServerInstalled([binName])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Windows: ensures default extensions are checked even if PATHEXT is missing", () => {
|
||||||
|
const binName = "test-lsp-server-default"
|
||||||
|
const binPath = join(tempDir, binName + ".bat")
|
||||||
|
writeFileSync(binPath, "echo hello")
|
||||||
|
|
||||||
|
process.env.PATH = tempDir
|
||||||
|
delete process.env.PATHEXT
|
||||||
|
|
||||||
|
expect(isServerInstalled([binName])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Windows: ensures default extensions are checked even if PATHEXT does not include them", () => {
|
||||||
|
const binName = "test-lsp-server-ps1"
|
||||||
|
const binPath = join(tempDir, binName + ".ps1")
|
||||||
|
writeFileSync(binPath, "echo hello")
|
||||||
|
|
||||||
|
process.env.PATH = tempDir
|
||||||
|
process.env.PATHEXT = ".COM"
|
||||||
|
|
||||||
|
expect(isServerInstalled([binName])).toBe(true)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
test("Non-Windows: does not use windows extensions", () => {
|
||||||
|
const binName = "test-lsp-server-win"
|
||||||
|
const binPath = join(tempDir, binName + ".cmd")
|
||||||
|
writeFileSync(binPath, "echo hello")
|
||||||
|
|
||||||
|
process.env.PATH = tempDir
|
||||||
|
|
||||||
|
expect(isServerInstalled([binName])).toBe(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@ -170,31 +170,46 @@ export function isServerInstalled(command: string[]): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isWindows = process.platform === "win32"
|
const isWindows = process.platform === "win32"
|
||||||
const ext = isWindows ? ".exe" : ""
|
|
||||||
|
|
||||||
const pathEnv = process.env.PATH || ""
|
let exts = [""]
|
||||||
|
if (isWindows) {
|
||||||
|
const pathExt = process.env.PATHEXT || ""
|
||||||
|
if (pathExt) {
|
||||||
|
const systemExts = pathExt.split(";").filter(Boolean)
|
||||||
|
exts = [...new Set([...exts, ...systemExts, ".exe", ".cmd", ".bat", ".ps1"])]
|
||||||
|
} else {
|
||||||
|
exts = ["", ".exe", ".cmd", ".bat", ".ps1"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pathEnv = process.env.PATH || ""
|
||||||
|
if (isWindows && !pathEnv) {
|
||||||
|
pathEnv = process.env.Path || ""
|
||||||
|
}
|
||||||
|
|
||||||
const pathSeparator = isWindows ? ";" : ":"
|
const pathSeparator = isWindows ? ";" : ":"
|
||||||
const paths = pathEnv.split(pathSeparator)
|
const paths = pathEnv.split(pathSeparator)
|
||||||
|
|
||||||
for (const p of paths) {
|
for (const p of paths) {
|
||||||
if (existsSync(join(p, cmd)) || existsSync(join(p, cmd + ext))) {
|
for (const suffix of exts) {
|
||||||
return true
|
if (existsSync(join(p, cmd + suffix))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
const additionalPaths = [
|
const additionalBases = [
|
||||||
join(cwd, "node_modules", ".bin", cmd),
|
join(cwd, "node_modules", ".bin"),
|
||||||
join(cwd, "node_modules", ".bin", cmd + ext),
|
join(homedir(), ".config", "opencode", "bin"),
|
||||||
join(homedir(), ".config", "opencode", "bin", cmd),
|
join(homedir(), ".config", "opencode", "node_modules", ".bin"),
|
||||||
join(homedir(), ".config", "opencode", "bin", cmd + ext),
|
|
||||||
join(homedir(), ".config", "opencode", "node_modules", ".bin", cmd),
|
|
||||||
join(homedir(), ".config", "opencode", "node_modules", ".bin", cmd + ext),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const p of additionalPaths) {
|
for (const base of additionalBases) {
|
||||||
if (existsSync(p)) {
|
for (const suffix of exts) {
|
||||||
return true
|
if (existsSync(join(base, cmd + suffix))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user