mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
fix: detect Scoop Git Bash on Windows (#529)
* fix: detect Scoop Git Bash on Windows * fix: harden Git Bash shim detection * fix(kaos): preserve Git Bash PATH priority
This commit is contained in:
parent
9aba465fd8
commit
3b62b123e6
5 changed files with 292 additions and 45 deletions
6
.changeset/detect-scoop-git-bash.md
Normal file
6
.changeset/detect-scoop-git-bash.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"@moonshot-ai/kaos": patch
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Detect Git Bash installed through Scoop and other Git shims on Windows.
|
||||
|
|
@ -155,13 +155,13 @@ The CLI also reads several standard system variables to detect the runtime envir
|
|||
|
||||
- `HOME`: used to resolve the default data path
|
||||
- `VISUAL`, `EDITOR`: external editor command (`VISUAL` takes precedence)
|
||||
- `PATH`: used to locate dependencies such as `rg` and `git`
|
||||
- `PATH`: used to locate dependencies such as `rg` and `git`; on Windows, Git Bash detection checks each `git.exe` found on `PATH`, including package-manager shims such as Scoop
|
||||
- `NO_COLOR`, `FORCE_COLOR`: control color output (following the [no-color.org](https://no-color.org) convention)
|
||||
- `CI`: when non-empty and not `"0"`, disables theme detection and falls back to the dark theme
|
||||
- `TERM_PROGRAM`, `TERM`, `TMUX`: detect terminal features and notification support
|
||||
- `DISPLAY`, `WAYLAND_DISPLAY`, `XDG_SESSION_TYPE`: detect Linux graphical sessions (for clipboard and image features)
|
||||
- `WSL_DISTRO_NAME`, `WSLENV`: detect WSL for the clipboard PowerShell bridge
|
||||
- `LOCALAPPDATA`: used on Windows when probing for the Git Bash installation path
|
||||
- `LOCALAPPDATA`: used on Windows as a fallback when probing for the Git Bash installation path
|
||||
|
||||
## HTTP proxy
|
||||
|
||||
|
|
|
|||
|
|
@ -155,13 +155,13 @@ CLI 还会读取一些标准系统变量来检测运行环境,不会修改它
|
|||
|
||||
- `HOME`:解析默认数据路径
|
||||
- `VISUAL`、`EDITOR`:外部编辑器命令(`VISUAL` 优先)
|
||||
- `PATH`:定位 `rg`、`git` 等依赖
|
||||
- `PATH`:定位 `rg`、`git` 等依赖;在 Windows 上,Git Bash 探测会检查 `PATH` 中找到的每个 `git.exe`,包括 Scoop 等包管理器提供的 shim
|
||||
- `NO_COLOR`、`FORCE_COLOR`:控制颜色输出(遵循 [no-color.org](https://no-color.org) 约定)
|
||||
- `CI`:非空且非 `"0"` 时关闭主题检测,回退深色主题
|
||||
- `TERM_PROGRAM`、`TERM`、`TMUX`:检测终端特性和通知支持
|
||||
- `DISPLAY`、`WAYLAND_DISPLAY`、`XDG_SESSION_TYPE`:检测 Linux 图形会话(用于剪贴板和图片功能)
|
||||
- `WSL_DISTRO_NAME`、`WSLENV`:检测 WSL,用于剪贴板 PowerShell 桥接
|
||||
- `LOCALAPPDATA`:Windows 上探测 Git Bash 安装路径
|
||||
- `LOCALAPPDATA`:Windows 上探测 Git Bash 安装路径时作为 fallback 使用
|
||||
|
||||
## HTTP 代理
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
* Environment — cross-platform probe of OS / shell.
|
||||
*
|
||||
* Detection is a pure function of injected probes (`platform` / `arch` /
|
||||
* `release` / `env` / `isFile` / `findExecutable`) so the same suite runs
|
||||
* identically on any host OS. `detectEnvironmentFromNode()` bundles the
|
||||
* Node defaults for production callers.
|
||||
* `release` / `env` / `isFile` / `execFileText`) so the same suite runs
|
||||
* identically on any host OS. `detectEnvironmentFromNode()` bundles the Node
|
||||
* defaults for production callers.
|
||||
*
|
||||
* On Windows the probe expects Git Bash (the canonical POSIX shell that
|
||||
* ships with Git for Windows). If it cannot be located the function
|
||||
|
|
@ -12,9 +12,11 @@
|
|||
* user-facing install hint. Set `KIMI_SHELL_PATH` to override.
|
||||
*/
|
||||
|
||||
import { execFile as nodeExecFile } from 'node:child_process';
|
||||
import { constants as fsConstants } from 'node:fs';
|
||||
import { access } from 'node:fs/promises';
|
||||
import * as nodeOs from 'node:os';
|
||||
import * as nodePath from 'node:path';
|
||||
|
||||
import { KaosShellNotFoundError } from './errors';
|
||||
|
||||
|
|
@ -40,9 +42,15 @@ export interface EnvironmentDeps {
|
|||
readonly release: string;
|
||||
readonly env: Record<string, string | undefined>;
|
||||
readonly isFile: (path: string) => Promise<boolean>;
|
||||
readonly findExecutable: (name: string) => Promise<string | undefined>;
|
||||
readonly execFileText: (
|
||||
file: string,
|
||||
args: readonly string[],
|
||||
timeoutMs: number,
|
||||
) => Promise<string | undefined>;
|
||||
}
|
||||
|
||||
const GIT_EXEC_PATH_TIMEOUT_MS = 5_000;
|
||||
|
||||
function resolveOsKind(platform: string): OsKind {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
|
|
@ -91,17 +99,34 @@ async function locateWindowsGitBash(deps: EnvironmentDeps): Promise<string> {
|
|||
}
|
||||
}
|
||||
|
||||
const gitExe = await deps.findExecutable('git.exe');
|
||||
if (gitExe !== undefined) {
|
||||
const inferred = inferGitBashFromGitExe(gitExe);
|
||||
const gitExecutables = await findExecutablesOnPath(
|
||||
'git.exe',
|
||||
deps.env['PATH'],
|
||||
deps.platform,
|
||||
deps.isFile,
|
||||
);
|
||||
|
||||
for (const gitExe of gitExecutables) {
|
||||
const inferred = gitBashCandidatesFromGitExe(gitExe);
|
||||
if (inferred !== undefined) {
|
||||
for (const path of inferred) {
|
||||
checked.push(path);
|
||||
if (await deps.isFile(path)) {
|
||||
return path;
|
||||
for (const candidate of inferred) {
|
||||
checked.push(candidate);
|
||||
if (await deps.isFile(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const gitExecPath = await readGitExecPath(deps, gitExe);
|
||||
if (gitExecPath === undefined) {
|
||||
continue;
|
||||
}
|
||||
for (const candidate of gitBashCandidatesFromGitExecPath(gitExecPath)) {
|
||||
checked.push(candidate);
|
||||
if (await deps.isFile(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const candidates: string[] = [
|
||||
|
|
@ -127,24 +152,81 @@ async function locateWindowsGitBash(deps: EnvironmentDeps): Promise<string> {
|
|||
);
|
||||
}
|
||||
|
||||
// Most Git for Windows installs put `git.exe` in `<root>\cmd\git.exe`,
|
||||
// with bash at `<root>\bin\bash.exe` (a wrapper) or `<root>\usr\bin\bash.exe`
|
||||
// (the real MSYS2 shell). Walk back to the parent of `cmd` / `bin` and
|
||||
// return both candidates so the caller can try them in preference order.
|
||||
function inferGitBashFromGitExe(gitExe: string): string[] | undefined {
|
||||
const sep = gitExe.includes('\\') ? '\\' : '/';
|
||||
const parts = gitExe.split(sep);
|
||||
for (let i = parts.length - 2; i >= 0; i -= 1) {
|
||||
const segment = parts[i];
|
||||
if (segment === 'cmd' || segment === 'bin') {
|
||||
const root = parts.slice(0, i).join(sep);
|
||||
const prefix = root.length === 0 ? '' : `${root}${sep}`;
|
||||
return [`${prefix}bin${sep}bash.exe`, `${prefix}usr${sep}bin${sep}bash.exe`];
|
||||
async function readGitExecPath(
|
||||
deps: EnvironmentDeps,
|
||||
gitExe: string,
|
||||
): Promise<string | undefined> {
|
||||
if (deps.platform === 'win32' && !isAbsoluteWindowsPath(gitExe)) return undefined;
|
||||
|
||||
const stdout = await deps.execFileText(gitExe, ['--exec-path'], GIT_EXEC_PATH_TIMEOUT_MS);
|
||||
if (stdout === undefined) return undefined;
|
||||
|
||||
for (const line of stdout.split(/\r?\n/)) {
|
||||
const execPath = line.trim();
|
||||
if (execPath.length > 0) {
|
||||
return execPath;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Most Git for Windows installs put `git.exe` in `<root>\cmd\git.exe`,
|
||||
// with bash at `<root>\bin\bash.exe`. Portable installs sometimes put
|
||||
// both in `<root>\bin\`. Only infer from those anchored layouts; package
|
||||
// manager shims live elsewhere and must resolve through `git --exec-path`.
|
||||
function gitBashCandidatesFromGitExe(gitExe: string): readonly string[] | undefined {
|
||||
const normalizedGitExe = nodePath.win32.normalize(normalizeWindowsPath(gitExe));
|
||||
const gitDir = nodePath.win32.dirname(normalizedGitExe);
|
||||
const gitDirName = nodePath.win32.basename(gitDir).toLowerCase();
|
||||
if (gitDirName !== 'cmd' && gitDirName !== 'bin') {
|
||||
return undefined;
|
||||
}
|
||||
return gitBashCandidatesFromGitRoot(nodePath.win32.dirname(gitDir));
|
||||
}
|
||||
|
||||
function gitBashCandidatesFromGitExecPath(execPath: string): readonly string[] {
|
||||
const normalized = nodePath.win32.normalize(normalizeWindowsPath(execPath));
|
||||
const parts = normalized.split('\\');
|
||||
for (let i = parts.length - 1; i >= 0; i -= 1) {
|
||||
const segment = parts[i]?.toLowerCase();
|
||||
if (segment === 'mingw32' || segment === 'mingw64') {
|
||||
const root = parts.slice(0, i).join('\\');
|
||||
if (root.length > 0) {
|
||||
return gitBashCandidatesFromGitRoot(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gitBashCandidatesFromGitRoot(nodePath.win32.join(normalized, '..', '..'));
|
||||
}
|
||||
|
||||
function gitBashCandidatesFromGitRoot(root: string): readonly string[] {
|
||||
return [
|
||||
nodePath.win32.normalize(nodePath.win32.join(root, 'bin', 'bash.exe')),
|
||||
nodePath.win32.normalize(nodePath.win32.join(root, 'usr', 'bin', 'bash.exe')),
|
||||
];
|
||||
}
|
||||
|
||||
function normalizeWindowsPath(path: string): string {
|
||||
return path.replaceAll('/', '\\');
|
||||
}
|
||||
|
||||
function isAbsoluteWindowsPath(path: string): boolean {
|
||||
return nodePath.win32.isAbsolute(normalizeWindowsPath(path));
|
||||
}
|
||||
|
||||
function dedupeWindowsPaths(paths: readonly string[]): readonly string[] {
|
||||
const deduped: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const path of paths) {
|
||||
const key = normalizeWindowsPath(path).toLowerCase();
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
deduped.push(path);
|
||||
}
|
||||
return deduped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Production convenience — derive the deps bag from Node's ambient surface.
|
||||
*
|
||||
|
|
@ -175,27 +257,50 @@ export function detectEnvironmentFromNode(): Promise<Environment> {
|
|||
release: nodeOs.release(),
|
||||
env,
|
||||
isFile,
|
||||
findExecutable: (name: string) => findExecutableOnPath(name, env['PATH'], platform, isFile),
|
||||
execFileText,
|
||||
});
|
||||
return detectedEnvironment;
|
||||
}
|
||||
|
||||
async function findExecutableOnPath(
|
||||
async function findExecutablesOnPath(
|
||||
name: string,
|
||||
pathEnv: string | undefined,
|
||||
platform: string,
|
||||
isFile: (p: string) => Promise<boolean>,
|
||||
): Promise<string | undefined> {
|
||||
if (pathEnv === undefined || pathEnv.length === 0) return undefined;
|
||||
): Promise<readonly string[]> {
|
||||
if (pathEnv === undefined || pathEnv.length === 0) return [];
|
||||
const listSep = platform === 'win32' ? ';' : ':';
|
||||
const dirSep = platform === 'win32' ? '\\' : '/';
|
||||
const paths: string[] = [];
|
||||
for (const rawDir of pathEnv.split(listSep)) {
|
||||
const dir = rawDir.trim();
|
||||
if (dir.length === 0) continue;
|
||||
if (platform === 'win32' && !isAbsoluteWindowsPath(dir)) continue;
|
||||
const candidate = dir.endsWith(dirSep) ? `${dir}${name}` : `${dir}${dirSep}${name}`;
|
||||
if (await isFile(candidate)) {
|
||||
return candidate;
|
||||
paths.push(candidate);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
return platform === 'win32' ? dedupeWindowsPaths(paths) : paths;
|
||||
}
|
||||
|
||||
async function execFileText(
|
||||
file: string,
|
||||
args: readonly string[],
|
||||
timeoutMs: number,
|
||||
): Promise<string | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
nodeExecFile(
|
||||
file,
|
||||
[...args],
|
||||
{ encoding: 'utf8', timeout: timeoutMs, windowsHide: true },
|
||||
(error, stdout) => {
|
||||
if (error !== null) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
resolve(stdout);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
* - macOS / Linux / Windows / unknown → `osKind`
|
||||
* - POSIX path probing prefers /bin/bash, falls back to /usr/bin/bash,
|
||||
* /usr/local/bin/bash, then /bin/sh (with shellName 'sh').
|
||||
* - Windows resolves Git Bash via `KIMI_SHELL_PATH`, `git.exe` on PATH,
|
||||
* or well-known install locations; throws `KaosShellNotFoundError`
|
||||
* - Windows resolves Git Bash via `KIMI_SHELL_PATH`, `git.exe` on PATH
|
||||
* (including `git --exec-path` for shims), or well-known install
|
||||
* locations; throws `KaosShellNotFoundError`
|
||||
* if none are present.
|
||||
* - `osArch` / `osVersion` are populated from the Node OS APIs.
|
||||
*
|
||||
|
|
@ -32,23 +33,29 @@ interface StubOpts {
|
|||
readonly release?: string;
|
||||
readonly env?: Record<string, string | undefined>;
|
||||
readonly existingPaths?: readonly string[];
|
||||
readonly executables?: Readonly<Record<string, string>>;
|
||||
readonly execFileResults?: Readonly<Record<string, string>>;
|
||||
readonly execFileText?: Parameters<typeof detectEnvironment>[0]['execFileText'];
|
||||
}
|
||||
|
||||
/** Build a stub deps bag mimicking Node's `os` + `process` surface. */
|
||||
function stubDeps(opts: StubOpts): Parameters<typeof detectEnvironment>[0] {
|
||||
const existing = new Set(opts.existingPaths ?? []);
|
||||
const executables = opts.executables ?? {};
|
||||
return {
|
||||
platform: opts.platform,
|
||||
arch: opts.arch ?? 'x86_64',
|
||||
release: opts.release ?? '1.2.3',
|
||||
env: opts.env ?? {},
|
||||
isFile: async (path: string) => existing.has(path),
|
||||
findExecutable: async (name: string) => executables[name],
|
||||
execFileText:
|
||||
opts.execFileText ??
|
||||
(async (file: string, args: readonly string[]) => opts.execFileResults?.[execFileKey(file, args)]),
|
||||
};
|
||||
}
|
||||
|
||||
function execFileKey(file: string, args: readonly string[]): string {
|
||||
return [file, ...args].join('\0');
|
||||
}
|
||||
|
||||
describe('detectEnvironment', () => {
|
||||
it('reports osKind "macOS" on darwin', async () => {
|
||||
const env: Environment = await detectEnvironment(
|
||||
|
|
@ -146,23 +153,152 @@ describe('detectEnvironment', () => {
|
|||
});
|
||||
|
||||
it('infers Git Bash from git.exe on PATH when override is absent', async () => {
|
||||
const gitExe = 'C:\\Program Files\\Git\\cmd\\git.exe';
|
||||
const env = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
executables: { 'git.exe': 'C:\\Program Files\\Git\\cmd\\git.exe' },
|
||||
existingPaths: ['C:\\Program Files\\Git\\bin\\bash.exe'],
|
||||
env: { PATH: 'C:\\Program Files\\Git\\cmd' },
|
||||
existingPaths: [gitExe, 'C:\\Program Files\\Git\\bin\\bash.exe'],
|
||||
execFileText: async (file: string) => {
|
||||
throw new Error(`unexpected execFileText call for ${file}`);
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(env.shellName).toBe('bash');
|
||||
expect(env.shellPath).toBe('C:\\Program Files\\Git\\bin\\bash.exe');
|
||||
});
|
||||
|
||||
it('infers Git Bash from usr/bin when bin/bash.exe is missing', async () => {
|
||||
it('resolves a Scoop git shim through git --exec-path', async () => {
|
||||
const gitExe = 'C:\\Users\\me\\scoop\\shims\\git.exe';
|
||||
const env = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
executables: { 'git.exe': 'D:\\Program Files\\Git\\cmd\\git.exe' },
|
||||
existingPaths: ['D:\\Program Files\\Git\\usr\\bin\\bash.exe'],
|
||||
env: { PATH: 'C:\\Users\\me\\scoop\\shims' },
|
||||
execFileResults: {
|
||||
[execFileKey(gitExe, ['--exec-path'])]:
|
||||
'C:/Users/me/scoop/apps/git/current/mingw64/libexec/git-core\n',
|
||||
},
|
||||
existingPaths: [gitExe, 'C:\\Users\\me\\scoop\\apps\\git\\current\\bin\\bash.exe'],
|
||||
}),
|
||||
);
|
||||
expect(env.shellName).toBe('bash');
|
||||
expect(env.shellPath).toBe('C:\\Users\\me\\scoop\\apps\\git\\current\\bin\\bash.exe');
|
||||
});
|
||||
|
||||
it('resolves a Scoop git shim through usr/bin when bin/bash.exe is missing', async () => {
|
||||
const gitExe = 'C:\\Users\\me\\scoop\\shims\\git.exe';
|
||||
const env = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
env: { PATH: 'C:\\Users\\me\\scoop\\shims' },
|
||||
execFileResults: {
|
||||
[execFileKey(gitExe, ['--exec-path'])]:
|
||||
'C:/Users/me/scoop/apps/git/current/mingw64/libexec/git-core\n',
|
||||
},
|
||||
existingPaths: [gitExe, 'C:\\Users\\me\\scoop\\apps\\git\\current\\usr\\bin\\bash.exe'],
|
||||
}),
|
||||
);
|
||||
expect(env.shellName).toBe('bash');
|
||||
expect(env.shellPath).toBe('C:\\Users\\me\\scoop\\apps\\git\\current\\usr\\bin\\bash.exe');
|
||||
});
|
||||
|
||||
it('does not treat shim-adjacent bash.exe as the Git installation shell', async () => {
|
||||
const gitExe = 'C:\\Users\\me\\scoop\\shims\\git.exe';
|
||||
const env = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
env: { PATH: 'C:\\Users\\me\\scoop\\shims' },
|
||||
execFileResults: {
|
||||
[execFileKey(gitExe, ['--exec-path'])]:
|
||||
'C:/Users/me/scoop/apps/git/current/mingw64/libexec/git-core\n',
|
||||
},
|
||||
existingPaths: [
|
||||
gitExe,
|
||||
'C:\\Users\\me\\scoop\\bin\\bash.exe',
|
||||
'C:\\Users\\me\\scoop\\apps\\git\\current\\bin\\bash.exe',
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(env.shellName).toBe('bash');
|
||||
expect(env.shellPath).toBe('C:\\Users\\me\\scoop\\apps\\git\\current\\bin\\bash.exe');
|
||||
});
|
||||
|
||||
it('checks later git.exe matches when the first one cannot resolve Git Bash', async () => {
|
||||
const scoopGit = 'C:\\Users\\me\\scoop\\shims\\git.exe';
|
||||
const portableGit = 'D:\\PortableGit\\cmd\\git.exe';
|
||||
const env = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
env: { PATH: 'C:\\Users\\me\\scoop\\shims;D:\\PortableGit\\cmd' },
|
||||
existingPaths: [scoopGit, portableGit, 'D:\\PortableGit\\bin\\bash.exe'],
|
||||
}),
|
||||
);
|
||||
expect(env.shellName).toBe('bash');
|
||||
expect(env.shellPath).toBe('D:\\PortableGit\\bin\\bash.exe');
|
||||
});
|
||||
|
||||
it('keeps PATH order when an earlier shim resolves through git --exec-path', async () => {
|
||||
const scoopGit = 'C:\\Users\\me\\scoop\\shims\\git.exe';
|
||||
const portableGit = 'D:\\PortableGit\\cmd\\git.exe';
|
||||
const env = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
env: { PATH: 'C:\\Users\\me\\scoop\\shims;D:\\PortableGit\\cmd' },
|
||||
execFileResults: {
|
||||
[execFileKey(scoopGit, ['--exec-path'])]:
|
||||
'C:/Users/me/scoop/apps/git/current/mingw64/libexec/git-core\n',
|
||||
},
|
||||
existingPaths: [
|
||||
scoopGit,
|
||||
portableGit,
|
||||
'C:\\Users\\me\\scoop\\apps\\git\\current\\bin\\bash.exe',
|
||||
'D:\\PortableGit\\bin\\bash.exe',
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(env.shellName).toBe('bash');
|
||||
expect(env.shellPath).toBe('C:\\Users\\me\\scoop\\apps\\git\\current\\bin\\bash.exe');
|
||||
});
|
||||
|
||||
it('skips relative Windows PATH entries before git --exec-path probing', async () => {
|
||||
const relativeGit = 'tools\\git.exe';
|
||||
const error = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
env: { PATH: 'tools' },
|
||||
existingPaths: [relativeGit],
|
||||
execFileText: async (file: string) => {
|
||||
throw new Error(`unexpected execFileText call for ${file}`);
|
||||
},
|
||||
}),
|
||||
).then(
|
||||
() => {
|
||||
throw new Error('expected throw');
|
||||
},
|
||||
(error: unknown) => error,
|
||||
);
|
||||
expect(error).toBeInstanceOf(KaosShellNotFoundError);
|
||||
});
|
||||
|
||||
it('scans PATH directly for git.exe candidates', async () => {
|
||||
const env = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
env: { PATH: 'D:\\PortableGit\\cmd' },
|
||||
existingPaths: ['D:\\PortableGit\\cmd\\git.exe', 'D:\\PortableGit\\bin\\bash.exe'],
|
||||
}),
|
||||
);
|
||||
expect(env.shellName).toBe('bash');
|
||||
expect(env.shellPath).toBe('D:\\PortableGit\\bin\\bash.exe');
|
||||
});
|
||||
|
||||
it('infers Git Bash from usr/bin when bin/bash.exe is missing', async () => {
|
||||
const gitExe = 'D:\\Program Files\\Git\\cmd\\git.exe';
|
||||
const env = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
env: { PATH: 'D:\\Program Files\\Git\\cmd' },
|
||||
existingPaths: [gitExe, 'D:\\Program Files\\Git\\usr\\bin\\bash.exe'],
|
||||
}),
|
||||
);
|
||||
expect(env.shellName).toBe('bash');
|
||||
|
|
@ -200,7 +336,7 @@ describe('detectEnvironment', () => {
|
|||
expect(env.shellPath).toBe('C:\\Users\\me\\AppData\\Local\\Programs\\Git\\bin\\bash.exe');
|
||||
});
|
||||
|
||||
it('falls back to usr/bin under LOCALAPPDATA when bin/bash.exe is missing', async () => {
|
||||
it('falls back to usr/bin under LOCALAPPDATA when bin/bash.exe is missing', async () => {
|
||||
const env = await detectEnvironment(
|
||||
stubDeps({
|
||||
platform: 'win32',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue