|
1 | | -import {exec, spawn} from 'child_process'; |
| 1 | +import {exec, spawn, spawnSync} from 'child_process'; |
2 | 2 | // Type definitions |
3 | 3 | import type {CommandExecutionResult} from './types/bash.types.js'; |
4 | 4 | // Utility functions |
@@ -151,6 +151,58 @@ export class TerminalCommandService { |
151 | 151 | } |
152 | 152 | } |
153 | 153 |
|
| 154 | + /** |
| 155 | + * Select an available local shell on Windows. |
| 156 | + * Tries preferred shell first, then falls back to alternatives. |
| 157 | + */ |
| 158 | + private selectAvailableWindowsShell( |
| 159 | + preferred: 'pwsh' | 'powershell' | null, |
| 160 | + ): { |
| 161 | + shell: 'pwsh' | 'powershell' | 'cmd'; |
| 162 | + isPowerShell: boolean; |
| 163 | + } { |
| 164 | + const candidates: Array<'pwsh' | 'powershell' | 'cmd'> = []; |
| 165 | + if (preferred === 'pwsh') { |
| 166 | + candidates.push('pwsh', 'powershell', 'cmd'); |
| 167 | + } else if (preferred === 'powershell') { |
| 168 | + candidates.push('powershell', 'pwsh', 'cmd'); |
| 169 | + } else { |
| 170 | + candidates.push('powershell', 'pwsh', 'cmd'); |
| 171 | + } |
| 172 | + |
| 173 | + for (const candidate of candidates) { |
| 174 | + try { |
| 175 | + if (candidate === 'cmd') { |
| 176 | + const probe = spawnSync('cmd', ['/c', 'echo'], { |
| 177 | + windowsHide: true, |
| 178 | + stdio: 'ignore', |
| 179 | + }); |
| 180 | + if (!probe.error) { |
| 181 | + return {shell: 'cmd', isPowerShell: false}; |
| 182 | + } |
| 183 | + continue; |
| 184 | + } |
| 185 | + |
| 186 | + const probe = spawnSync( |
| 187 | + candidate, |
| 188 | + ['-NoProfile', '-Command', '$PSVersionTable.PSVersion.ToString()'], |
| 189 | + { |
| 190 | + windowsHide: true, |
| 191 | + stdio: 'ignore', |
| 192 | + }, |
| 193 | + ); |
| 194 | + |
| 195 | + if (!probe.error) { |
| 196 | + return {shell: candidate, isPowerShell: true}; |
| 197 | + } |
| 198 | + } catch { |
| 199 | + // Ignore probe errors and continue fallback. |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + return {shell: 'cmd', isPowerShell: false}; |
| 204 | + } |
| 205 | + |
154 | 206 | /** |
155 | 207 | * Execute a terminal command in the working directory |
156 | 208 | * Supports both local and remote SSH directories |
@@ -227,15 +279,15 @@ export class TerminalCommandService { |
227 | 279 | let shellArgs: string[]; |
228 | 280 |
|
229 | 281 | if (isWindows) { |
230 | | - const psType = detectWindowsPowerShell(); |
231 | | - if (psType) { |
232 | | - // Use PowerShell (pwsh for 7.x, powershell for 5.x) |
233 | | - shell = psType === 'pwsh' ? 'pwsh' : 'powershell'; |
| 282 | + const preferredPowerShell = detectWindowsPowerShell(); |
| 283 | + const selectedShell = |
| 284 | + this.selectAvailableWindowsShell(preferredPowerShell); |
| 285 | + shell = selectedShell.shell; |
| 286 | + |
| 287 | + if (selectedShell.isPowerShell) { |
234 | 288 | const utf8WrappedCommand = `& { $OutputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); ${command} }`; |
235 | 289 | shellArgs = ['-NoProfile', '-Command', utf8WrappedCommand]; |
236 | 290 | } else { |
237 | | - // Fallback to cmd if not in PowerShell environment |
238 | | - shell = 'cmd'; |
239 | 291 | const utf8Command = `chcp 65001>nul && ${command}`; |
240 | 292 | shellArgs = ['/c', utf8Command]; |
241 | 293 | } |
|
0 commit comments