UNPKG

@jjeem/detect-shell

Version:

detect shells available on the system

124 lines (110 loc) 3.88 kB
import os from 'os'; import {readFile, stat, lstat} from './pfs'; import {normalize, basename} from 'path'; import {enumeratePowerShellInstallations} from './powershell'; interface IShellDefinition { label: string; path: string; } const platform = { isWindows: process.platform === 'win32', isMacintosh: process.platform === 'darwin', isLinux: process.platform === 'linux' }; export function getWindowsBuildNumber(): number { const osVersion = /(\d+)\.(\d+)\.(\d+)/g.exec(os.release()); let buildNumber: number = 0; if (osVersion && osVersion.length === 4) { buildNumber = parseInt(osVersion[3]); } return buildNumber; } export function detectAvailableShells(): Promise<IShellDefinition[]> { return platform.isWindows ? detectAvailableWindowsShells() : detectAvailableUnixShells(); } async function detectAvailableWindowsShells(): Promise<IShellDefinition[]> { const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); const system32Path = `${process.env['windir']}\\${ is32ProcessOn64Windows ? 'Sysnative' : 'System32' }`; let useWSLexe = false; if (getWindowsBuildNumber() >= 16299) { useWSLexe = true; } const expectedLocations: {[key: string]: string[]} = { 'Command Prompt': [`${system32Path}\\cmd.exe`], 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], 'Git Bash': [ `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe` ], Cygwin: [ `${process.env['HOMEDRIVE']}\\cygwin64\\bin\\bash.exe`, `${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe` ], Nushell: [`${process.env['ProgramFiles']}\\nu\\bin\\nu.exe`] }; // console.table(expectedLocations); // Add all of the different kinds of PowerShells for await (const pwshExe of enumeratePowerShellInstallations()) { expectedLocations[pwshExe.displayName] = [pwshExe.exePath]; } const promises: Promise<IShellDefinition | undefined>[] = []; Object.keys(expectedLocations).forEach((key) => promises.push(validateShellPaths(key, expectedLocations[key])) ); const shells = await Promise.all(promises); return shells.filter((e) => !!e) as IShellDefinition[]; } async function detectAvailableUnixShells(): Promise<IShellDefinition[]> { const contents = await readFile('/etc/shells', 'utf8'); const shells = contents .split('\n') .filter((e) => e.trim().indexOf('#') !== 0 && e.trim().length > 0); return shells.map((e) => { return { label: basename(e), path: e }; }); } async function validateShellPaths( label: string, potentialPaths: string[] ): Promise<IShellDefinition | undefined> { if (potentialPaths.length === 0) { return Promise.resolve(undefined); } const current = potentialPaths.shift()!; if (current! === '') { return validateShellPaths(label, potentialPaths); } try { const result = await stat(normalize(current)); if (result.isFile() || result.isSymbolicLink()) { return { label, path: current }; } } catch (e) { // Also try using lstat as some symbolic links on Windows // throw 'permission denied' using 'stat' but don't throw // using 'lstat' try { const result = await lstat(normalize(current)); if (result.isFile() || result.isSymbolicLink()) { return { label, path: current }; } } catch (e) { // noop } } return validateShellPaths(label, potentialPaths); }