fix(desktop): inherit WSL shell environment
This commit is contained in:
parent
c9db6e9a1f
commit
467028a52d
3 changed files with 72 additions and 1 deletions
|
|
@ -3,6 +3,7 @@ import { existsSync } from "node:fs"
|
|||
import { join } from "node:path"
|
||||
import * as pty from "@lydell/node-pty"
|
||||
import type { WslDistroProbe, WslInstalledDistro, WslOnlineDistro, WslRuntimeCheck } from "../../preload/types"
|
||||
import { parseShellEnv } from "../shell-env"
|
||||
import { wslTerminalArgs } from "./policy"
|
||||
|
||||
export type WslCommandLine = {
|
||||
|
|
@ -32,6 +33,7 @@ export type RunWslOptions = {
|
|||
|
||||
const DEFAULT_WSL_TIMEOUT_MS = 20_000
|
||||
const DEFAULT_WSL_INSTALL_TIMEOUT_MS = 15 * 60_000
|
||||
const WSL_SHELL_ENV_TIMEOUT_MS = 5_000
|
||||
|
||||
export function wslArgs(args: string[], distro?: string | null, user?: string | null) {
|
||||
return [...(distro ? ["-d", distro] : []), ...(user ? ["--user", user] : []), "--", ...args]
|
||||
|
|
@ -202,6 +204,31 @@ export function runWslSh(script: string, distro?: string | null, opts?: RunWslOp
|
|||
return runWslInDistro(["sh", "-lc", script], distro, opts)
|
||||
}
|
||||
|
||||
export function wslShellEnvProbeArgs(mode: "-il" | "-l") {
|
||||
return ["bash", mode, "-c", "env -0"]
|
||||
}
|
||||
|
||||
export function wslShellEnvExports(env: Record<string, string>) {
|
||||
return Object.entries(env)
|
||||
.filter(([key]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
||||
.map(([key, value]) => `export ${key}=${shellEscape(value)}`)
|
||||
}
|
||||
|
||||
export async function loadWslShellEnv(distro: string) {
|
||||
// Probe separately so the long-running server stays non-interactive and does not emit shell prompts or job-control warnings.
|
||||
const probe = async (mode: "-il" | "-l") => {
|
||||
const result = await runWslInDistro(wslShellEnvProbeArgs(mode), distro, {
|
||||
timeoutMs: WSL_SHELL_ENV_TIMEOUT_MS,
|
||||
}).catch(() => undefined)
|
||||
if (!result || result.code !== 0) return undefined
|
||||
const env = parseShellEnv(Buffer.from(result.stdout))
|
||||
if (Object.keys(env).length === 0) return undefined
|
||||
return env
|
||||
}
|
||||
|
||||
return (await probe("-il")) ?? (await probe("-l")) ?? {}
|
||||
}
|
||||
|
||||
export async function probeWslRuntime(opts?: RunWslOptions): Promise<WslRuntimeCheck> {
|
||||
const version = await runWsl(["--version"], opts).catch((error) => ({
|
||||
code: 1,
|
||||
|
|
|
|||
35
packages/desktop/src/main/wsl/sidecar.test.ts
Normal file
35
packages/desktop/src/main/wsl/sidecar.test.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { expect, test } from "bun:test"
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
import { spawnSync } from "node:child_process"
|
||||
import { parseShellEnv } from "../shell-env"
|
||||
import { wslShellEnvExports, wslShellEnvProbeArgs } from "./runtime"
|
||||
|
||||
test("probes the interactive login environment used by a WSL terminal", () => {
|
||||
expect(wslShellEnvProbeArgs("-il")).toEqual(["bash", "-il", "-c", "env -0"])
|
||||
})
|
||||
|
||||
test.skipIf(process.platform === "win32")("loads the login environment before starting the server", () => {
|
||||
const home = mkdtempSync(join(tmpdir(), "opencode-wsl-env-"))
|
||||
try {
|
||||
writeFileSync(join(home, ".bash_profile"), '[ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc"\n')
|
||||
writeFileSync(join(home, ".bashrc"), "export OPENCODE_WSL_ENV_TEST='https://company.test/v1?x=$HOME&y=one two'\n")
|
||||
const command = wslShellEnvProbeArgs("-il")
|
||||
const probe = spawnSync(command[0], command.slice(1), {
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, HOME: home },
|
||||
})
|
||||
const env = parseShellEnv(Buffer.from(probe.stdout))
|
||||
const result = spawnSync("bash", ["-se"], {
|
||||
input: [...wslShellEnvExports(env), 'printf "%s" "${OPENCODE_WSL_ENV_TEST:-missing}"'].join("\n"),
|
||||
encoding: "utf8",
|
||||
})
|
||||
|
||||
expect(probe.status).toBe(0)
|
||||
expect(result.status).toBe(0)
|
||||
expect(result.stdout).toBe("https://company.test/v1?x=$HOME&y=one two")
|
||||
} finally {
|
||||
rmSync(home, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
|
@ -3,7 +3,14 @@ import { randomUUID } from "node:crypto"
|
|||
import { createServer } from "node:net"
|
||||
import { app } from "electron"
|
||||
import { checkHealth } from "../server"
|
||||
import { type WslCommandLine, resolveWslOpencode, shellEscape, wslArgs } from "./runtime"
|
||||
import {
|
||||
loadWslShellEnv,
|
||||
type WslCommandLine,
|
||||
resolveWslOpencode,
|
||||
shellEscape,
|
||||
wslArgs,
|
||||
wslShellEnvExports,
|
||||
} from "./runtime"
|
||||
import { pollWslHealth } from "./startup"
|
||||
|
||||
export type WslSidecar = {
|
||||
|
|
@ -23,7 +30,9 @@ export async function spawnWslSidecar(
|
|||
const port = await allocatePort()
|
||||
const password = randomUUID()
|
||||
const username = "opencode"
|
||||
const env = await loadWslShellEnv(distro)
|
||||
const script = [
|
||||
...wslShellEnvExports(env),
|
||||
"set -euo pipefail",
|
||||
'cd "$HOME" || cd /',
|
||||
'PATH=$(awk -v RS=: -v ORS=: \'$0 !~ /^\\/mnt\\//\' <<<"$PATH" | sed "s/:$//")',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue