From e44384671e2e1bd60310b6ad0a48ae10d6ae8d78 Mon Sep 17 00:00:00 2001 From: Adegbite Ayoade Date: Thu, 24 Sep 2026 02:56:12 +0100 Subject: [PATCH] fix(vscode): pick the print environment command by shell The print environment command opened a terminal with the user's default shell and then branched only on the platform, sending `set` on Windows and `env | sort` everywhere else. That assumes every non-Windows shell is a POSIX shell and every Windows shell is cmd. Resolve `vscode.env.shell` to a shell family instead and send the command that family accepts: `set` for fish, `env | sort` for POSIX shells, `set` for cmd and `Get-ChildItem Env: | Sort-Object Name` for PowerShell. `set` in PowerShell is an alias for `Set-Variable`, which prompts for a variable name rather than printing the environment, so the previous command did not work in the default Windows shell. An unrecognised shell falls back on the platform command, which keeps the previous behaviour for any shell not listed. Signed-off-by: Adegbite Ayoade --- .../src/commands/printEnvironment.ts | 12 +-- .../src/utilities/shellCommand.test.ts | 94 +++++++++++++++++++ .../extension/src/utilities/shellCommand.ts | 73 ++++++++++++++ 3 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 vscode/extension/src/utilities/shellCommand.test.ts create mode 100644 vscode/extension/src/utilities/shellCommand.ts diff --git a/vscode/extension/src/commands/printEnvironment.ts b/vscode/extension/src/commands/printEnvironment.ts index 41e74e40d3..39fd171bd1 100644 --- a/vscode/extension/src/commands/printEnvironment.ts +++ b/vscode/extension/src/commands/printEnvironment.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode' import { getSqlmeshEnvironment } from '../utilities/sqlmesh/sqlmesh' import { isErr } from '@bus/result' import { IS_WINDOWS } from '../utilities/isWindows' +import { printEnvironmentCommand } from '../utilities/shellCommand' export function printEnvironment() { return async () => { @@ -23,14 +24,9 @@ export function printEnvironment() { // Show the terminal terminal.show() - // Run the appropriate command to display environment variables - if (IS_WINDOWS) { - // On Windows, use 'set' command - terminal.sendText('set') - } else { - // On Unix-like systems, use 'env' command - terminal.sendText('env | sort') - } + // Run the command the user's shell understands, since the terminal is + // opened with the default shell rather than a shell we pick + terminal.sendText(printEnvironmentCommand(vscode.env.shell, IS_WINDOWS)) // Show a notification vscode.window.showInformationMessage( diff --git a/vscode/extension/src/utilities/shellCommand.test.ts b/vscode/extension/src/utilities/shellCommand.test.ts new file mode 100644 index 0000000000..fa197c38ab --- /dev/null +++ b/vscode/extension/src/utilities/shellCommand.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect } from 'vitest' +import { detectShellFamily, printEnvironmentCommand } from './shellCommand' + +describe('detectShellFamily', () => { + it('should detect fish', () => { + expect(detectShellFamily('/usr/bin/fish', false)).toBe('fish') + expect(detectShellFamily('/opt/homebrew/bin/fish', false)).toBe('fish') + }) + + it('should detect POSIX shells', () => { + expect(detectShellFamily('/bin/bash', false)).toBe('posix') + expect(detectShellFamily('/bin/zsh', false)).toBe('posix') + expect(detectShellFamily('/bin/sh', false)).toBe('posix') + expect(detectShellFamily('/usr/bin/dash', false)).toBe('posix') + expect(detectShellFamily('/usr/bin/ksh', false)).toBe('posix') + }) + + it('should detect Windows shells, ignoring the executable extension', () => { + expect(detectShellFamily('C:\\Windows\\System32\\cmd.exe', true)).toBe( + 'cmd', + ) + expect( + detectShellFamily( + 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', + true, + ), + ).toBe('powershell') + expect( + detectShellFamily('C:\\Program Files\\PowerShell\\7\\pwsh.exe', true), + ).toBe('powershell') + }) + + it('should detect a POSIX shell installed on Windows', () => { + expect( + detectShellFamily('C:\\Program Files\\Git\\bin\\bash.exe', true), + ).toBe('posix') + }) + + it('should accept a bare shell name without a path', () => { + expect(detectShellFamily('fish', false)).toBe('fish') + expect(detectShellFamily('pwsh', false)).toBe('powershell') + }) + + it('should ignore the case of the shell name', () => { + expect(detectShellFamily('/usr/bin/FISH', false)).toBe('fish') + expect(detectShellFamily('C:\\WINDOWS\\SYSTEM32\\CMD.EXE', true)).toBe( + 'cmd', + ) + }) + + it('should fall back on the platform for an unknown shell', () => { + expect(detectShellFamily('/usr/bin/elvish', false)).toBe('posix') + expect(detectShellFamily('C:\\tools\\elvish.exe', true)).toBe('cmd') + }) + + it('should fall back on the platform when no shell is reported', () => { + expect(detectShellFamily('', false)).toBe('posix') + expect(detectShellFamily('', true)).toBe('cmd') + expect(detectShellFamily(undefined, false)).toBe('posix') + expect(detectShellFamily(undefined, true)).toBe('cmd') + }) +}) + +describe('printEnvironmentCommand', () => { + it('should use set in fish', () => { + expect(printEnvironmentCommand('/usr/bin/fish', false)).toBe('set') + }) + + it('should use env | sort in POSIX shells', () => { + expect(printEnvironmentCommand('/bin/bash', false)).toBe('env | sort') + expect(printEnvironmentCommand('/bin/zsh', false)).toBe('env | sort') + expect(printEnvironmentCommand('/bin/sh', false)).toBe('env | sort') + }) + + it('should use set in cmd', () => { + expect( + printEnvironmentCommand('C:\\Windows\\System32\\cmd.exe', true), + ).toBe('set') + }) + + it('should not use set in PowerShell, where it prompts for a variable name', () => { + expect( + printEnvironmentCommand( + 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', + true, + ), + ).toBe('Get-ChildItem Env: | Sort-Object Name') + }) + + it('should keep the previous behaviour when the shell is unknown', () => { + expect(printEnvironmentCommand(undefined, false)).toBe('env | sort') + expect(printEnvironmentCommand(undefined, true)).toBe('set') + }) +}) diff --git a/vscode/extension/src/utilities/shellCommand.ts b/vscode/extension/src/utilities/shellCommand.ts new file mode 100644 index 0000000000..296c2482ed --- /dev/null +++ b/vscode/extension/src/utilities/shellCommand.ts @@ -0,0 +1,73 @@ +/** + * The shell families we know how to print environment variables in. + */ +export type ShellFamily = 'fish' | 'powershell' | 'cmd' | 'posix' + +/** + * Commands that list the environment variables of the current shell, one per + * shell family. + */ +const PRINT_ENVIRONMENT_COMMANDS: Record = { + fish: 'set', + powershell: 'Get-ChildItem Env: | Sort-Object Name', + cmd: 'set', + posix: 'env | sort', +} + +const SHELL_FAMILIES_BY_NAME: Record = { + fish: 'fish', + powershell: 'powershell', + pwsh: 'powershell', + cmd: 'cmd', + bash: 'posix', + zsh: 'posix', + sh: 'posix', + dash: 'posix', + ksh: 'posix', + ash: 'posix', +} + +/** + * Extracts the executable name from a shell path, which may use either + * separator and may carry a Windows executable extension. + */ +const shellName = (shellPath: string): string => { + const base = shellPath.split(/[\\/]/).pop() ?? '' + return base.toLowerCase().replace(/\.exe$/, '') +} + +/** + * Resolves a shell path to the family whose syntax it accepts. + * + * Unrecognized shells fall back on the platform default, which keeps POSIX + * shells we have not listed working. + * + * @param shellPath The path reported by `vscode.env.shell`, which is empty in + * environments without a shell + * @param isWindows Whether the extension host runs on Windows + */ +export const detectShellFamily = ( + shellPath: string | undefined, + isWindows: boolean, +): ShellFamily => { + const family = shellPath + ? SHELL_FAMILIES_BY_NAME[shellName(shellPath)] + : undefined + if (family) { + return family + } + return isWindows ? 'cmd' : 'posix' +} + +/** + * Returns the command that prints all environment variables in the given + * shell. + * + * @param shellPath The path reported by `vscode.env.shell`, which is empty in + * environments without a shell + * @param isWindows Whether the extension host runs on Windows + */ +export const printEnvironmentCommand = ( + shellPath: string | undefined, + isWindows: boolean, +): string => PRINT_ENVIRONMENT_COMMANDS[detectShellFamily(shellPath, isWindows)]