fix(windows): use cross-spawn for shim-backed commands (#18010)

This commit is contained in:
Luke Parker
2026-03-19 08:49:16 +10:00
committed by GitHub
parent 8ee939c741
commit 54ed87d53c
11 changed files with 126 additions and 38 deletions

View File

@@ -5,6 +5,7 @@ import { pathToFileURL, fileURLToPath } from "url"
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
import { Log } from "../util/log"
import { Process } from "../util/process"
import { LANGUAGE_EXTENSIONS } from "./language"
import z from "zod"
import type { LSPServer } from "./server"
@@ -239,7 +240,7 @@ export namespace LSPClient {
l.info("shutting down")
connection.end()
connection.dispose()
input.server.process.kill()
await Process.stop(input.server.process)
l.info("shutdown")
},
}

View File

@@ -7,9 +7,10 @@ import { pathToFileURL, fileURLToPath } from "url"
import { LSPServer } from "./server"
import z from "zod"
import { Config } from "../config/config"
import { spawn } from "child_process"
import { Instance } from "../project/instance"
import { Flag } from "@/flag/flag"
import { Process } from "../util/process"
import { spawn as lspspawn } from "./launch"
export namespace LSP {
const log = Log.create({ service: "lsp" })
@@ -112,9 +113,8 @@ export namespace LSP {
extensions: item.extensions ?? existing?.extensions ?? [],
spawn: async (root) => {
return {
process: spawn(item.command[0], item.command.slice(1), {
process: lspspawn(item.command[0], item.command.slice(1), {
cwd: root,
windowsHide: true,
env: {
...process.env,
...item.env,
@@ -200,21 +200,20 @@ export namespace LSP {
serverID: server.id,
server: handle,
root,
}).catch((err) => {
}).catch(async (err) => {
s.broken.add(key)
handle.process.kill()
await Process.stop(handle.process)
log.error(`Failed to initialize LSP client ${server.id}`, { error: err })
return undefined
})
if (!client) {
handle.process.kill()
return undefined
}
const existing = s.clients.find((x) => x.root === root && x.serverID === server.id)
if (existing) {
handle.process.kill()
await Process.stop(handle.process)
return existing
}

View File

@@ -0,0 +1,21 @@
import type { ChildProcessWithoutNullStreams } from "child_process"
import { Process } from "../util/process"
type Child = Process.Child & ChildProcessWithoutNullStreams
export function spawn(cmd: string, args: string[], opts?: Process.Options): Child
export function spawn(cmd: string, opts?: Process.Options): Child
export function spawn(cmd: string, argsOrOpts?: string[] | Process.Options, opts?: Process.Options) {
const args = Array.isArray(argsOrOpts) ? [...argsOrOpts] : []
const cfg = Array.isArray(argsOrOpts) ? opts : argsOrOpts
const proc = Process.spawn([cmd, ...args], {
...(cfg ?? {}),
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
}) as Child
if (!proc.stdin || !proc.stdout || !proc.stderr) throw new Error("Process output not available")
return proc
}

View File

@@ -1,4 +1,4 @@
import { spawn as launch, type ChildProcessWithoutNullStreams } from "child_process"
import type { ChildProcessWithoutNullStreams } from "child_process"
import path from "path"
import os from "os"
import { Global } from "../global"
@@ -13,11 +13,7 @@ import { Archive } from "../util/archive"
import { Process } from "../util/process"
import { which } from "../util/which"
import { Module } from "@opencode-ai/util/module"
const spawn = ((cmd, args, opts) => {
if (Array.isArray(args)) return launch(cmd, [...args], { ...(opts ?? {}), windowsHide: true })
return launch(cmd, { ...(args ?? {}), windowsHide: true })
}) as typeof launch
import { spawn } from "./launch"
export namespace LSPServer {
const log = Log.create({ service: "lsp.server" })
@@ -273,7 +269,7 @@ export namespace LSPServer {
}
if (lintBin) {
const proc = Process.spawn([lintBin, "--help"], { stdout: "pipe" })
const proc = spawn(lintBin, ["--help"])
await proc.exited
if (proc.stdout) {
const help = await text(proc.stdout)