(
params: Partial<ResolveLocalPtyShellParams> = {}
)
| 139 | * mutating `process.platform` / `process.env`. |
| 140 | */ |
| 141 | export function resolveLocalPtyShell( |
| 142 | params: Partial<ResolveLocalPtyShellParams> = {} |
| 143 | ): ResolvedPtyShell { |
| 144 | const platform = params.platform ?? process.platform; |
| 145 | const env = params.env ?? process.env; |
| 146 | const isCommandAvailable = params.isCommandAvailable ?? defaultIsCommandAvailable(platform); |
| 147 | const isPathAccessible = params.isPathAccessible ?? defaultIsPathAccessible; |
| 148 | const getBashPathFn = params.getBashPath ?? getBashPath; |
| 149 | |
| 150 | const candidates: ResolvedPtyShell[] = []; |
| 151 | |
| 152 | // User-configured shell from config.json stays highest priority, but only when valid. |
| 153 | const configuredShell = params.configuredShell?.trim(); |
| 154 | if (configuredShell) { |
| 155 | candidates.push({ command: configuredShell, args: [] }); |
| 156 | } |
| 157 | |
| 158 | // `process.env.SHELL` can be present-but-empty (""), especially in packaged apps. |
| 159 | // Treat empty/whitespace as "unset". |
| 160 | const envShell = env.SHELL?.trim(); |
| 161 | if (envShell) { |
| 162 | // On Windows, `SHELL=bash` often routes to WSL (via System32\\bash.exe). |
| 163 | // Ignore WSL shells and fall back to Git Bash/pwsh/cmd selection below. |
| 164 | if (platform !== "win32" || !looksLikeWslShell(envShell)) { |
| 165 | candidates.push({ command: envShell, args: [] }); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | if (platform === "win32") { |
| 170 | // Prefer Git Bash when available (works well with repo tooling). |
| 171 | try { |
| 172 | const bashPath = getBashPathFn().trim(); |
| 173 | if (bashPath) { |
| 174 | candidates.push({ command: bashPath, args: ["--login", "-i"] }); |
| 175 | } |
| 176 | } catch { |
| 177 | // Git Bash not available; fall back to PowerShell / cmd. |
| 178 | } |
| 179 | |
| 180 | candidates.push({ command: "pwsh", args: [] }); |
| 181 | candidates.push({ command: "powershell", args: [] }); |
| 182 | |
| 183 | candidates.push({ command: getWindowsFallbackCommand(env, isPathAccessible), args: [] }); |
| 184 | } else if (platform === "darwin") { |
| 185 | candidates.push({ command: "/bin/zsh", args: [] }); |
| 186 | } else { |
| 187 | candidates.push({ command: "/bin/bash", args: [] }); |
| 188 | } |
| 189 | |
| 190 | for (const candidate of candidates) { |
| 191 | if (isCandidateAvailable(candidate.command, { isCommandAvailable, isPathAccessible })) { |
| 192 | return candidate; |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // Last-resort fallback if all candidates above are unavailable. |
| 197 | // Keep platform-native: cmd.exe on Windows, /bin/zsh on macOS, /bin/bash elsewhere. |
| 198 | if (platform === "win32") { |
no test coverage detected