mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-24 16:54:43 +00:00
fix(desktop): respect HERMES_GIT_BASH_PATH in findGitBash()
Port the HERMES_GIT_BASH_PATH env var check from main.cjs to main.ts after the TS conversion. Also extract findGitBash to a dedicated module for testability and add focused regression tests for override precedence and invalid-override fallback.
This commit is contained in:
parent
4276fe8ded
commit
6b278eeccc
3 changed files with 116 additions and 41 deletions
51
apps/desktop/electron/find-git-bash.test.ts
Normal file
51
apps/desktop/electron/find-git-bash.test.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import assert from 'node:assert/strict'
|
||||
|
||||
import { test } from 'vitest'
|
||||
|
||||
import { findGitBash } from './find-git-bash'
|
||||
|
||||
const yes = () => true
|
||||
const no = () => false
|
||||
|
||||
test('HERMES_GIT_BASH_PATH override takes precedence', () => {
|
||||
const result = findGitBash({
|
||||
isWindows: true,
|
||||
env: { HERMES_GIT_BASH_PATH: 'D:\\CustomGit\\bin\\bash.exe' },
|
||||
fileExists: yes,
|
||||
findOnPath: () => null
|
||||
})
|
||||
assert.equal(result, 'D:\\CustomGit\\bin\\bash.exe')
|
||||
})
|
||||
|
||||
test('HERMES_GIT_BASH_PATH invalid path falls through to candidates', () => {
|
||||
const env = {
|
||||
HERMES_GIT_BASH_PATH: 'X:\\Missing\\bash.exe',
|
||||
LOCALAPPDATA: 'C:\\Users\\test\\AppData\\Local',
|
||||
ProgramFiles: 'C:\\Program Files',
|
||||
'ProgramFiles(x86)': 'C:\\Program Files (x86)'
|
||||
}
|
||||
|
||||
const fileExists = (p: string) => p !== 'X:\\Missing\\bash.exe' && p.includes('Program Files\\Git\\bin\\bash.exe')
|
||||
const result = findGitBash({ isWindows: true, env, fileExists, findOnPath: () => null })
|
||||
assert.equal(result, 'C:\\Program Files\\Git\\bin\\bash.exe')
|
||||
})
|
||||
|
||||
test('HERMES_GIT_BASH_PATH empty string is ignored', () => {
|
||||
const result = findGitBash({
|
||||
isWindows: true,
|
||||
env: { HERMES_GIT_BASH_PATH: '', LOCALAPPDATA: '' },
|
||||
fileExists: no,
|
||||
findOnPath: () => 'C:\\msys64\\usr\\bin\\bash.exe'
|
||||
})
|
||||
assert.equal(result, 'C:\\msys64\\usr\\bin\\bash.exe')
|
||||
})
|
||||
|
||||
test('non-Windows uses findOnPath', () => {
|
||||
const result = findGitBash({
|
||||
isWindows: false,
|
||||
env: {},
|
||||
fileExists: no,
|
||||
findOnPath: () => '/usr/bin/bash'
|
||||
})
|
||||
assert.equal(result, '/usr/bin/bash')
|
||||
})
|
||||
55
apps/desktop/electron/find-git-bash.ts
Normal file
55
apps/desktop/electron/find-git-bash.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import path from 'node:path'
|
||||
|
||||
export interface GitBashOptions {
|
||||
isWindows: boolean
|
||||
env: Record<string, string | undefined>
|
||||
fileExists: (filePath: string) => boolean
|
||||
findOnPath?: (command: string) => string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate bash.exe on Windows.
|
||||
* Resolution order (first match wins):
|
||||
* 1. HERMES_GIT_BASH_PATH env var override
|
||||
* 2. PortableGit under %LOCALAPPDATA%\hermes\git\ (install.ps1)
|
||||
* 3. Standard Git for Windows install locations
|
||||
* 4. %LOCALAPPDATA%\Programs\Git\ (user-scoped)
|
||||
* 5. bash on PATH
|
||||
*/
|
||||
export function findGitBash(opts: GitBashOptions): string | null {
|
||||
const { isWindows, env, fileExists, findOnPath } = opts
|
||||
|
||||
if (!isWindows) {
|
||||
return findOnPath ? findOnPath('bash') : null
|
||||
}
|
||||
|
||||
// Respect HERMES_GIT_BASH_PATH if set (mirrors tools/environments/local.py:_find_bash).
|
||||
const gitBashPath = env.HERMES_GIT_BASH_PATH
|
||||
if (gitBashPath && fileExists(gitBashPath)) return gitBashPath
|
||||
|
||||
const localAppData = env.LOCALAPPDATA || ''
|
||||
const candidates: string[] = []
|
||||
|
||||
if (localAppData) {
|
||||
candidates.push(path.join(localAppData, 'hermes', 'git', 'bin', 'bash.exe'))
|
||||
candidates.push(path.join(localAppData, 'hermes', 'git', 'usr', 'bin', 'bash.exe'))
|
||||
}
|
||||
|
||||
candidates.push(path.join(env['ProgramFiles'] || 'C:\\Program Files', 'Git', 'bin', 'bash.exe'))
|
||||
candidates.push(path.join(env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Git', 'bin', 'bash.exe'))
|
||||
|
||||
if (localAppData) {
|
||||
candidates.push(path.join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'))
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (fileExists(candidate)) return candidate
|
||||
}
|
||||
|
||||
if (findOnPath) {
|
||||
const onPath = findOnPath('bash')
|
||||
if (onPath) return onPath
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
@ -78,6 +78,7 @@ import {
|
|||
} from './desktop-uninstall'
|
||||
import { installEmbedReferer } from './embed-referer'
|
||||
import { createEventDeduper } from './event-dedupe'
|
||||
import { findGitBash as _findGitBash } from './find-git-bash'
|
||||
import { readDirForIpc } from './fs-read-dir'
|
||||
import { probeGatewayWebSocket } from './gateway-ws-probe'
|
||||
import { scanGitRepos } from './git-repo-scan'
|
||||
|
|
@ -1915,48 +1916,16 @@ function findSystemPython() {
|
|||
return null
|
||||
}
|
||||
|
||||
// findGitBash — locate bash.exe on Windows. Hermes' terminal tool requires
|
||||
// bash (POSIX shell), and on Windows that's almost always Git for Windows'
|
||||
// bundled Git Bash. We check the same set of locations tools/environments/
|
||||
// local.py:_find_bash() checks at runtime, so a positive result here means
|
||||
// the agent will be able to start a terminal too.
|
||||
//
|
||||
// On non-Windows hosts bash is part of the OS and this just returns the
|
||||
// first bash on PATH.
|
||||
// findGitBash — locate bash.exe on Windows. Resolves HERMES_GIT_BASH_PATH
|
||||
// first (mirrors tools/environments/local.py:_find_bash), then PortableGit,
|
||||
// standard install locations, and finally PATH.
|
||||
function findGitBash() {
|
||||
if (!IS_WINDOWS) {
|
||||
return findOnPath('bash')
|
||||
}
|
||||
|
||||
// install.ps1 drops PortableGit at %LOCALAPPDATA%\hermes\git\... — checked
|
||||
// first so users who installed via install.ps1 are detected before we
|
||||
// start probing system-wide locations.
|
||||
const localAppData = process.env.LOCALAPPDATA || ''
|
||||
const candidates = []
|
||||
|
||||
if (localAppData) {
|
||||
candidates.push(path.join(localAppData, 'hermes', 'git', 'bin', 'bash.exe'))
|
||||
candidates.push(path.join(localAppData, 'hermes', 'git', 'usr', 'bin', 'bash.exe'))
|
||||
}
|
||||
|
||||
// Standard Git for Windows install locations.
|
||||
candidates.push(path.join(process.env['ProgramFiles'] || 'C:\\Program Files', 'Git', 'bin', 'bash.exe'))
|
||||
candidates.push(path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Git', 'bin', 'bash.exe'))
|
||||
|
||||
if (localAppData) {
|
||||
candidates.push(path.join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'))
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (fileExists(candidate)) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort — bash on PATH (covers WSL bash, MSYS2, custom installs).
|
||||
// On WSL hosts findOnPath itself filters out Windows-binary paths via
|
||||
// isWindowsBinaryPathInWsl, so we won't hand back a wsl.exe shim either.
|
||||
return findOnPath('bash')
|
||||
return _findGitBash({
|
||||
isWindows: IS_WINDOWS,
|
||||
env: process.env,
|
||||
fileExists,
|
||||
findOnPath
|
||||
})
|
||||
}
|
||||
|
||||
function getVenvPython(venvRoot) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue