diff --git a/package-lock.json b/package-lock.json index 97cc46a..b91b766 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "ink": "^4.4.1", "node-pty": "^1.0.0", "react": "^18.2.0", + "which": "^4.0.0", "wrap-ansi": "^8.1.0", "xterm-headless": "^5.3.0" }, @@ -27,6 +28,7 @@ "@tsconfig/node18": "^18.2.2", "@types/jest": "^29.5.5", "@types/react": "^18.2.24", + "@types/which": "^3.0.3", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", "@withfig/autocomplete-types": "^1.28.0", @@ -1806,6 +1808,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/which": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.3.tgz", + "integrity": "sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.26", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", @@ -2959,6 +2967,27 @@ "node": ">= 8" } }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -4764,10 +4793,12 @@ "dev": true }, "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "engines": { + "node": ">=16" + } }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", @@ -7761,18 +7792,17 @@ } }, "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { - "node-which": "bin/node-which" + "node-which": "bin/which.js" }, "engines": { - "node": ">= 8" + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/which-boxed-primitive": { @@ -9421,6 +9451,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/which": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.3.tgz", + "integrity": "sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g==", + "dev": true + }, "@types/yargs": { "version": "17.0.26", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", @@ -10211,6 +10247,23 @@ "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "csstype": { @@ -11505,10 +11558,9 @@ "dev": true }, "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==" }, "istanbul-lib-coverage": { "version": "3.2.0", @@ -13677,12 +13729,11 @@ } }, "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "requires": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" } }, "which-boxed-primitive": { diff --git a/package.json b/package.json index a6626ff..ef7b29a 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "ink": "^4.4.1", "node-pty": "^1.0.0", "react": "^18.2.0", + "which": "^4.0.0", "wrap-ansi": "^8.1.0", "xterm-headless": "^5.3.0" }, @@ -47,6 +48,7 @@ "@tsconfig/node18": "^18.2.2", "@types/jest": "^29.5.5", "@types/react": "^18.2.24", + "@types/which": "^3.0.3", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", "@withfig/autocomplete-types": "^1.28.0", diff --git a/src/commands/root.ts b/src/commands/root.ts index bcdcddd..49205e9 100644 --- a/src/commands/root.ts +++ b/src/commands/root.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { initRender } from "../ui/ui-init.js"; import { render } from "../ui/ui-root.js"; import { Shell, supportedShells as shells } from "../utils/bindings.js"; import { inferShell } from "../utils/shell.js"; @@ -10,7 +9,10 @@ import { Command } from "commander"; export const supportedShells = shells.join(", "); export const action = (program: Command) => async () => { - const shell = ((await inferShell()) ?? (await initRender()) ?? "") as unknown as Shell; + const shell = (await inferShell()) as unknown as Shell | undefined; + if (shell == null) { + program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 }); + } if (!shells.map((s) => s.valueOf()).includes(shell)) { program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 }); } diff --git a/src/isterm/pty.ts b/src/isterm/pty.ts index 1147c2f..5136855 100644 --- a/src/isterm/pty.ts +++ b/src/isterm/pty.ts @@ -11,6 +11,7 @@ import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from ".. import xterm from "xterm-headless"; import { CommandManager, CommandState } from "./commandManager.js"; import log from "../utils/log.js"; +import { gitBashPath } from "../utils/shell.js"; const ISTermOnDataEvent = "data"; @@ -36,8 +37,8 @@ export class ISTerm implements IPty { readonly #term: xterm.Terminal; readonly #commandManager: CommandManager; - constructor({ shell, cols, rows, env }: ISTermOptions) { - this.#pty = pty.spawn(convertToPtyTarget(shell), [], { + constructor({ shell, cols, rows, env, shellTarget }: ISTermOptions & { shellTarget: string }) { + this.#pty = pty.spawn(shellTarget, [], { name: "xterm-256color", cols, rows, @@ -130,12 +131,17 @@ export class ISTerm implements IPty { } } -export const spawn = (options: ISTermOptions): ISTerm => { - return new ISTerm(options); +export const spawn = async (options: ISTermOptions): Promise => { + const shellTarget = await convertToPtyTarget(options.shell); + return new ISTerm({ ...options, shellTarget }); }; -const convertToPtyTarget = (shell: Shell): string => { - return os.platform() == "win32" ? `${shell}.exe` : shell; +const convertToPtyTarget = async (shell: Shell): Promise => { + const platform = os.platform(); + if (shell == Shell.Bash && platform == "win32") { + return await gitBashPath(); + } + return platform == "win32" ? `${shell}.exe` : shell; }; const convertToPtyEnv = (shell: Shell) => { diff --git a/src/runtime/template.ts b/src/runtime/template.ts index 80021ec..c2e6af3 100644 --- a/src/runtime/template.ts +++ b/src/runtime/template.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import fsAsync from "fs/promises"; +import fsAsync from "node:fs/promises"; import process from "node:process"; const filepathsTemplate = async (): Promise => { diff --git a/src/tests/isterm/pty.test.ts b/src/tests/isterm/pty.test.ts index b2d07dd..9b8b8d4 100644 --- a/src/tests/isterm/pty.test.ts +++ b/src/tests/isterm/pty.test.ts @@ -13,7 +13,7 @@ const bashEnv = { PS1: `${IstermPromptStart}\\u$ ${IstermPromptEnd}` }; const zshEnv = { PROMPT: `%{${IstermPromptStart}%}%/ %# %{${IstermPromptEnd}%}` }; const runTerm = async (shell: Shell, input: string[], env?: { [key: string]: string | undefined }) => { - const ptyProcess = isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, env }); + const ptyProcess = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, env }); await new Promise((r) => setTimeout(r, 1_000)); for (const data of input) { ptyProcess.write(data); diff --git a/src/ui/input.tsx b/src/ui/input.tsx deleted file mode 100644 index ecc2acb..0000000 --- a/src/ui/input.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import React, { useState, useEffect } from "react"; -import { useInput, Text } from "ink"; -import chalk from "chalk"; -import { Suggestion } from "../runtime/model.js"; - -const BlinkSpeed = 530; -const CursorColor = "#FFFFFF"; - -export default function Input({ - value, - setValue, - prompt, - activeSuggestion, - tabCompletionDropSize, -}: { - value: string; - setValue: (_: string) => void; - prompt: string; - activeSuggestion: Suggestion | undefined; - tabCompletionDropSize: number; -}) { - const [cursorLocation, setCursorLocation] = useState(value.length); - const [cursorBlink, setCursorBlink] = useState(true); - - useEffect(() => { - setTimeout(() => { - setCursorBlink(!cursorBlink); - }, BlinkSpeed); - }, [cursorBlink]); - - // TODO: arrow key navigation shortcuts (emacs & vim modes) - useInput((input, key) => { - // TODO: handle delete better on unix systems: https://github.com/vadimdemedes/ink/issues/634 - const windows = process.platform === "win32"; - const backspaceKey = windows ? key.backspace : key.backspace || key.delete; - const deleteKey = windows ? key.delete : false; - - if (backspaceKey) { - setValue([...value].slice(0, Math.max(cursorLocation - 1, 0)).join("") + [...value].slice(cursorLocation).join("")); - setCursorLocation(Math.max(cursorLocation - 1, 0)); - } else if (deleteKey) { - setValue([...value].slice(0, cursorLocation).join("") + [...value].slice(Math.min(value.length, cursorLocation + 1)).join("")); - } else if (key.leftArrow) { - setCursorLocation(Math.max(cursorLocation - 1, 0)); - } else if (key.rightArrow) { - setCursorLocation(Math.min(cursorLocation + 1, value.length)); - } else if (key.tab) { - if (activeSuggestion) { - // TOOD: support insertValue - const newValue = [...value].slice(0, cursorLocation - tabCompletionDropSize).join("") + activeSuggestion.name + " "; - setValue(newValue); - setCursorLocation(newValue.length); - } - } else if (input) { - setValue([...value].slice(0, cursorLocation).join("") + input + [...value].slice(cursorLocation).join("")); - setCursorLocation(cursorLocation + input.length); - } - }); - - const cursoredCommand = value + " "; - const cursoredText = - [...cursoredCommand].slice(0, cursorLocation).join("") + - (cursorBlink ? chalk.bgHex(CursorColor).inverse([...cursoredCommand].at(cursorLocation)) : [...cursoredCommand].at(cursorLocation)) + - [...cursoredCommand].slice(cursorLocation + 1).join(""); - - return ( - - {prompt} - {cursoredText} - - ); -} diff --git a/src/ui/suggestions.tsx b/src/ui/suggestions.tsx deleted file mode 100644 index a8fd54e..0000000 --- a/src/ui/suggestions.tsx +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import React, { useState, useCallback, useEffect } from "react"; -import { Text, useInput, Box, measureElement, DOMElement } from "ink"; -import wrapAnsi from "wrap-ansi"; -import { Suggestion } from "../runtime/model.js"; - -const MaxSuggestions = 5; -const SuggestionWidth = 40; -const DescriptionWidth = 30; -const DescriptionMaxHeight = 6; -const BorderWidth = 2; -const ActiveSuggestionBackgroundColor = "#7D56F4"; - -function Description({ description }: { description: string }) { - wrapAnsi(description, DescriptionWidth, { hard: true }); - if (description.length !== 0) { - return ( - - - {truncateDescription(description, DescriptionMaxHeight)} - - - ); - } -} - -function truncateDescription(description: string, maxHeight: number) { - const wrappedText = wrapAnsi(description, DescriptionWidth - BorderWidth, { - trim: false, - hard: true, - }); - const lines = wrappedText.split("\n"); - const truncatedLines = lines.slice(0, maxHeight); - if (lines.length > maxHeight) { - truncatedLines[maxHeight - 1] = [...truncatedLines[maxHeight - 1]].slice(0, -1).join("") + "…"; - } - return truncatedLines.join("\n"); -} - -function SuggestionList({ suggestions, activeSuggestionIdx }: { suggestions: Suggestion[]; activeSuggestionIdx: number }) { - return ( - - - {suggestions - .map((suggestion, idx) => { - const bgColor = idx === activeSuggestionIdx ? ActiveSuggestionBackgroundColor : undefined; - - return ( - - - {`${suggestion.icon} ${suggestion.name}`.padEnd(SuggestionWidth - BorderWidth, " ")} - - - ); - }) - .filter((node) => node !== undefined)} - - - ); -} - -export default function Suggestions({ - leftPadding, - setActiveSuggestion, - suggestions, -}: { - leftPadding: number; - setActiveSuggestion: (_: Suggestion) => void; - suggestions: Suggestion[]; -}) { - const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(0); - const [windowWidth, setWindowWidth] = useState(500); - - const page = Math.floor(activeSuggestionIndex / MaxSuggestions) + 1; - const pagedSuggestions = suggestions.filter((_, idx) => idx < page * MaxSuggestions && idx >= (page - 1) * MaxSuggestions); - const activePagedSuggestionIndex = activeSuggestionIndex % MaxSuggestions; - const activeDescription = pagedSuggestions.at(activePagedSuggestionIndex)?.description || ""; - - const wrappedPadding = leftPadding % windowWidth; - const maxPadding = activeDescription.length !== 0 ? windowWidth - SuggestionWidth - DescriptionWidth : windowWidth - SuggestionWidth; - const swapDescription = wrappedPadding > maxPadding; - const swappedPadding = swapDescription ? Math.max(wrappedPadding - DescriptionWidth, 0) : wrappedPadding; - const clampedLeftPadding = Math.min(Math.min(wrappedPadding, swappedPadding), maxPadding); - - useEffect(() => { - setActiveSuggestion(suggestions[activeSuggestionIndex]); - }, [activeSuggestionIndex, suggestions]); - - useEffect(() => { - if (suggestions.length <= activeSuggestionIndex) { - setActiveSuggestionIndex(Math.max(suggestions.length - 1, 0)); - } - }, [suggestions]); - - useInput((_, key) => { - if (key.upArrow) { - setActiveSuggestionIndex(Math.max(0, activeSuggestionIndex - 1)); - } - if (key.downArrow) { - setActiveSuggestionIndex(Math.min(activeSuggestionIndex + 1, suggestions.length - 1)); - } - }); - - const measureRef = useCallback((node: DOMElement) => { - if (node !== null) { - const { width } = measureElement(node); - setWindowWidth(width); - } - }, []); - - if (suggestions.length === 0) return <>; - - return ( - - - {swapDescription ? ( - <> - - - - ) : ( - <> - - - - )} - - - ); -} diff --git a/src/ui/ui-init.tsx b/src/ui/ui-init.tsx deleted file mode 100644 index 0c60fa8..0000000 --- a/src/ui/ui-init.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import React, { useState } from "react"; -import { Box, Text, render, useApp, useInput } from "ink"; - -import { supportedShells } from "../utils/bindings.js"; - -let uiResult = undefined; - -function UI() { - const { exit } = useApp(); - const [selectionIdx, setSelectionIdx] = useState(0); - const [exited, setExited] = useState(false); - - useInput(async (_, key) => { - if (key.upArrow) { - setSelectionIdx(Math.max(0, selectionIdx - 1)); - } else if (key.downArrow) { - setSelectionIdx(Math.min(supportedShells.length - 1, selectionIdx + 1)); - } else if (key.return) { - uiResult = supportedShells[selectionIdx]; - setExited(true); - setTimeout(exit, 0); - } - }); - - return ( - <> - {exited ? null : ( - - - Select your desired shell - - - {supportedShells.map((shell, idx) => { - if (idx == selectionIdx) { - return ( - - {">"} {shell} - - ); - } - return ( - - {" "} - {shell} - - ); - })} - - - )} - - ); -} - -export const initRender = async (): Promise => { - uiResult = undefined; - const { waitUntilExit } = render(); - await waitUntilExit(); - - return uiResult; -}; diff --git a/src/ui/ui-root.ts b/src/ui/ui-root.ts index 9224778..836f820 100644 --- a/src/ui/ui-root.ts +++ b/src/ui/ui-root.ts @@ -10,7 +10,7 @@ import ansi from "ansi-escapes"; import { SuggestionManager, MAX_LINES } from "./suggestionManager.js"; export const render = async (shell: Shell) => { - const term = isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns }); + const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns }); const suggestionManager = new SuggestionManager(term); let hasActiveSuggestions = false; let previousSuggestionsColumns = 0; diff --git a/src/utils/shell.ts b/src/utils/shell.ts index e5aee36..ad68926 100644 --- a/src/utils/shell.ts +++ b/src/utils/shell.ts @@ -3,10 +3,63 @@ import process from "node:process"; import find from "find-process"; +import path from "node:path"; import { supportedShells } from "./bindings.js"; +import which from "which"; +import fs from "node:fs"; export const inferShell = async () => { + try { + return path.parse(process.env.SHELL ?? "").name; + } catch { + /* empty */ + } const processResult = (await find("pid", process.ppid)).at(0); const name = processResult?.name; return name != null ? supportedShells.find((shell) => name.includes(shell)) : undefined; }; + +export const gitBashPath = async (): Promise => { + const gitBashPaths = await getGitBashPaths(); + for (const gitBashPath of gitBashPaths) { + if (fs.existsSync(gitBashPath)) { + return gitBashPath; + } + } + throw new Error("unable to find a git bash executable installed"); +}; + +const getGitBashPaths = async (): Promise => { + const gitDirs: Set = new Set(); + + const gitExePath = await which("git.exe", { nothrow: true }); + if (gitExePath) { + const gitExeDir = path.dirname(gitExePath); + gitDirs.add(path.resolve(gitExeDir, "../..")); + } + + const addValid = (set: Set, value: T | undefined) => { + if (value) set.add(value); + }; + + // Add common git install locations + addValid(gitDirs, process.env["ProgramW6432"]); + addValid(gitDirs, process.env["ProgramFiles"]); + addValid(gitDirs, process.env["ProgramFiles(X86)"]); + addValid(gitDirs, `${process.env["LocalAppData"]}\\Program`); + + const gitBashPaths: string[] = []; + for (const gitDir of gitDirs) { + gitBashPaths.push( + `${gitDir}\\Git\\bin\\bash.exe`, + `${gitDir}\\Git\\usr\\bin\\bash.exe`, + `${gitDir}\\usr\\bin\\bash.exe`, // using Git for Windows SDK + ); + } + + // Add special installs that don't follow the standard directory structure + gitBashPaths.push(`${process.env["UserProfile"]}\\scoop\\apps\\git\\current\\bin\\bash.exe`); + gitBashPaths.push(`${process.env["UserProfile"]}\\scoop\\apps\\git-with-openssh\\current\\bin\\bash.exe`); + + return gitBashPaths; +};