Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions vscode/extension/src/commands/printEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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(
Expand Down
106 changes: 106 additions & 0 deletions vscode/extension/src/utilities/shellCommand.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: Apache-2.0

import { describe, it, expect } from 'vitest'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add in another test to that if we have pwsh and isWindows false, it still returns the powershell command correctly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in d239db3 as should use the PowerShell command for pwsh off Windows, covering both /usr/local/bin/pwsh and a bare pwsh.

I checked it isn't a test that passes no matter what. The regression it guards is PowerShell being treated as Windows-only, so I gated the lookup behind !(family === 'powershell' && !isWindows) and confirmed the new test fails, then restored it and confirmed all 14 pass. Worth noting the command-layer coverage was the gap — detectShellFamily('pwsh', false) was already asserted, but nothing checked that the command came back right off Windows.

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')
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 the exported-only set in fish', () => {
expect(printEnvironmentCommand('/usr/bin/fish', false)).toBe('set -x')
})

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(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', () => {
expect(printEnvironmentCommand(undefined, false)).toBe('env | sort')
expect(printEnvironmentCommand(undefined, true)).toBe('set')
})
})
81 changes: 81 additions & 0 deletions vscode/extension/src/utilities/shellCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: Apache-2.0

/**
* 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.
*
* `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<ShellFamily, string> = {
fish: 'set -x',
powershell:
"Get-ChildItem Env: | Sort-Object Name | ForEach-Object { '{0}={1}' -f $_.Name, $_.Value }",
cmd: 'set',
posix: 'env | sort',
}

const SHELL_FAMILIES_BY_NAME: Record<string, ShellFamily> = {
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)]