From e44384671e2e1bd60310b6ad0a48ae10d6ae8d78 Mon Sep 17 00:00:00 2001 From: Adegbite Ayoade Date: Thu, 24 Sep 2026 02:56:12 +0100 Subject: [PATCH 1/2] 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)] From d239db3a8ca74a67dbcdee577bab907a8dad3a3c Mon Sep 17 00:00:00 2001 From: Adegbite Ayoade Date: Sun, 18 Oct 2026 02:28:39 +0100 Subject: [PATCH 2/2] fix(vscode): list only exported variables and stop PowerShell truncating fish's bare `set` also lists shell-local variables, so it is now `set -x`, which matches what the other families print. PowerShell's default table view truncates values at the console width, so each variable is now formatted explicitly as `NAME=value`. Adds the pwsh-off-Windows case for printEnvironmentCommand, and the SPDX headers both new files were missing. Signed-off-by: Adegbite Ayoade --- .../src/utilities/shellCommand.test.ts | 18 +++++++++++++++--- vscode/extension/src/utilities/shellCommand.ts | 12 ++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/vscode/extension/src/utilities/shellCommand.test.ts b/vscode/extension/src/utilities/shellCommand.test.ts index fa197c38ab..3c7984d4f8 100644 --- a/vscode/extension/src/utilities/shellCommand.test.ts +++ b/vscode/extension/src/utilities/shellCommand.test.ts @@ -1,6 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 + import { describe, it, expect } from 'vitest' import { detectShellFamily, printEnvironmentCommand } from './shellCommand' +const POWERSHELL_COMMAND = + "Get-ChildItem Env: | Sort-Object Name | ForEach-Object { '{0}={1}' -f $_.Name, $_.Value }" + describe('detectShellFamily', () => { it('should detect fish', () => { expect(detectShellFamily('/usr/bin/fish', false)).toBe('fish') @@ -62,8 +67,8 @@ describe('detectShellFamily', () => { }) describe('printEnvironmentCommand', () => { - it('should use set in fish', () => { - expect(printEnvironmentCommand('/usr/bin/fish', false)).toBe('set') + it('should use the exported-only set in fish', () => { + expect(printEnvironmentCommand('/usr/bin/fish', false)).toBe('set -x') }) it('should use env | sort in POSIX shells', () => { @@ -84,7 +89,14 @@ describe('printEnvironmentCommand', () => { 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', true, ), - ).toBe('Get-ChildItem Env: | Sort-Object Name') + ).toBe(POWERSHELL_COMMAND) + }) + + it('should use the PowerShell command for pwsh off Windows', () => { + expect(printEnvironmentCommand('/usr/local/bin/pwsh', false)).toBe( + POWERSHELL_COMMAND, + ) + expect(printEnvironmentCommand('pwsh', false)).toBe(POWERSHELL_COMMAND) }) it('should keep the previous behaviour when the shell is unknown', () => { diff --git a/vscode/extension/src/utilities/shellCommand.ts b/vscode/extension/src/utilities/shellCommand.ts index 296c2482ed..61ed04415f 100644 --- a/vscode/extension/src/utilities/shellCommand.ts +++ b/vscode/extension/src/utilities/shellCommand.ts @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + /** * The shell families we know how to print environment variables in. */ @@ -6,10 +8,16 @@ export type ShellFamily = 'fish' | 'powershell' | 'cmd' | 'posix' /** * Commands that list the environment variables of the current shell, one per * shell family. + * + * `set -x` is fish's exported-only listing, which matches what the other + * families print; a bare `set` would also include shell-local variables. + * PowerShell formats each variable itself because the default table view + * truncates values at the console width. */ const PRINT_ENVIRONMENT_COMMANDS: Record = { - fish: 'set', - powershell: 'Get-ChildItem Env: | Sort-Object Name', + fish: 'set -x', + powershell: + "Get-ChildItem Env: | Sort-Object Name | ForEach-Object { '{0}={1}' -f $_.Name, $_.Value }", cmd: 'set', posix: 'env | sort', }