diff --git a/src/tools/lsp/lsp-process.test.ts b/src/tools/lsp/lsp-process.test.ts new file mode 100644 index 000000000..406a7f7fc --- /dev/null +++ b/src/tools/lsp/lsp-process.test.ts @@ -0,0 +1,37 @@ +import { mkdtempSync, rmSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" + +import { describe, expect, it, spyOn } from "bun:test" + +describe("spawnProcess", () => { + it("proceeds to node spawn on Windows when command is available", async () => { + // #given + const originalPlatform = process.platform + const rootDir = mkdtempSync(join(tmpdir(), "lsp-process-test-")) + const childProcess = await import("node:child_process") + const nodeSpawnSpy = spyOn(childProcess, "spawn") + + try { + Object.defineProperty(process, "platform", { value: "win32" }) + const { spawnProcess } = await import("./lsp-process") + + // #when + let result: ReturnType | null = null + expect(() => { + result = spawnProcess(["node", "--version"], { + cwd: rootDir, + env: process.env, + }) + }).not.toThrow(/Binary 'node' not found/) + + // #then + expect(nodeSpawnSpy).toHaveBeenCalled() + expect(result).not.toBeNull() + } finally { + Object.defineProperty(process, "platform", { value: originalPlatform }) + nodeSpawnSpy.mockRestore() + rmSync(rootDir, { recursive: true, force: true }) + } + }) +}) diff --git a/src/tools/lsp/lsp-process.ts b/src/tools/lsp/lsp-process.ts index a193aa968..358c5c7e5 100644 --- a/src/tools/lsp/lsp-process.ts +++ b/src/tools/lsp/lsp-process.ts @@ -1,5 +1,5 @@ import { spawn as bunSpawn } from "bun" -import { spawn as nodeSpawn, spawnSync, type ChildProcess } from "node:child_process" +import { spawn as nodeSpawn, type ChildProcess } from "node:child_process" import { existsSync, statSync } from "fs" import { log } from "../../shared/logger" // Bun spawn segfaults on Windows (oven-sh/bun#25798) — unfixed as of v1.3.8+ @@ -21,24 +21,6 @@ export function validateCwd(cwd: string): { valid: boolean; error?: string } { return { valid: false, error: `Cannot access working directory: ${cwd} (${err instanceof Error ? err.message : String(err)})` } } } -function isBinaryAvailableOnWindows(command: string): boolean { - if (process.platform !== "win32") return true - - if (command.includes("/") || command.includes("\\")) { - return existsSync(command) - } - - try { - const result = spawnSync("where", [command], { - shell: true, - windowsHide: true, - timeout: 5000, - }) - return result.status === 0 - } catch { - return true - } -} interface StreamReader { read(): Promise<{ done: boolean; value: Uint8Array | undefined }> } @@ -158,13 +140,6 @@ export function spawnProcess( } if (shouldUseNodeSpawn()) { const [cmd, ...args] = command - if (!isBinaryAvailableOnWindows(cmd)) { - throw new Error( - `[LSP] Binary '${cmd}' not found on Windows. ` + - `Ensure the LSP server is installed and available in PATH. ` + - `For npm packages, try: npm install -g ${cmd}` - ) - } log("[LSP] Using Node.js child_process on Windows to avoid Bun spawn segfault") const proc = nodeSpawn(cmd, args, { cwd: options.cwd,