import { spawnSync } from "child_process" import path from "path" export const WINDOWS_CMD_EXTENSIONS = new Set([".cmd", ".bat"]) export const WINDOWS_POWERSHELL_EXTENSIONS = new Set([".ps1"]) const VERSION_REGEX = /([0-9]+\.[0-9]+\.[0-9A-Za-z.-]+)/ const WSL_UNC_PATH_REGEX = /^\\\\wsl(?:\.localhost|\$)\\([^\\/]+)(?:[\\/](.*))?$/i const WSL_PATH_ENV_KEYS = new Set(["OPENCODE_CONFIG_DIR", "NODE_EXTRA_CA_CERTS"]) export interface SpawnSpec { command: string args: string[] options: { windowsVerbatimArguments?: boolean } cwd?: string env?: NodeJS.ProcessEnv wsl?: { distro: string pidMarker?: string } } interface BuildSpawnSpecOptions { cwd?: string env?: NodeJS.ProcessEnv propagateEnvKeys?: string[] wslPidMarker?: string } interface WslPath { distro: string linuxPath: string } export type WslWorkingDirectory = | { kind: "linux"; path: string } | { kind: "windows"; path: string } export function parseWslUncPath(input: string): WslPath | null { const normalized = input.trim().replace(/\//g, "\\") const match = normalized.match(WSL_UNC_PATH_REGEX) if (!match) { return null } const distro = match[1] ?? "" const remainder = match[2] ?? "" const segments = remainder.split(/\\+/).filter((segment) => segment.length > 0) return { distro, linuxPath: segments.length > 0 ? `/${segments.join("/")}` : "/", } } export function resolveWslWorkingDirectory(folder: string, distro: string): WslWorkingDirectory | null { const wslFolder = parseWslUncPath(folder) if (wslFolder) { return wslFolder.distro.toLowerCase() === distro.toLowerCase() ? { kind: "linux", path: wslFolder.linuxPath } : null } const windowsFolder = normalizeWindowsPath(folder) return windowsFolder ? { kind: "windows", path: windowsFolder } : null } export function buildWindowsSpawnSpec(binaryPath: string, args: string[], options: BuildSpawnSpecOptions = {}): SpawnSpec { const wslPath = parseWslUncPath(binaryPath) if (wslPath) { return buildWslSpawnSpec(wslPath, args, options) } const extension = path.extname(binaryPath).toLowerCase() if (WINDOWS_CMD_EXTENSIONS.has(extension)) { const comspec = process.env.ComSpec || "cmd.exe" // cmd.exe requires the full command as a single string. // Using the ""