mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat(tools): prefer xAI native Grok installer with npm fallback
Treat Grok like Claude/OpenCode: install via the official xAI installer (POSIX shell / Windows PowerShell), keep npm as fallback, discover ~/.grok/bin, and anchor updates to `grok update` only for native installs.
This commit is contained in:
+192
-28
@@ -441,6 +441,8 @@ const CLAUDE_INSTALL_UNIX: &str =
|
||||
"bash -c 'tmp=$(mktemp) && curl -fsSL https://claude.ai/install.sh -o $tmp && bash $tmp; status=$?; rm -f $tmp; exit $status'";
|
||||
const OPENCODE_INSTALL_UNIX: &str =
|
||||
"bash -c 'tmp=$(mktemp) && curl -fsSL https://opencode.ai/install -o $tmp && bash $tmp; status=$?; rm -f $tmp; exit $status'";
|
||||
const GROK_INSTALL_UNIX: &str =
|
||||
"bash -c 'tmp=$(mktemp) && curl -fsSL https://x.ai/cli/install.sh -o $tmp && bash $tmp; status=$?; rm -f $tmp; exit $status'";
|
||||
|
||||
/// Hermes 官方安装器会自带/选择合适的 Python 运行时。不要再用
|
||||
/// `python3 -m pip ... || python -m pip ...`:Hermes PyPI 包要求 Python >=3.11,
|
||||
@@ -454,6 +456,8 @@ const HERMES_UPDATE_UNIX: &str =
|
||||
#[cfg(target_os = "windows")]
|
||||
const HERMES_INSTALL_WINDOWS_SCRIPT: &str =
|
||||
"irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1 | iex";
|
||||
#[cfg(target_os = "windows")]
|
||||
const GROK_INSTALL_WINDOWS_SCRIPT: &str = "irm https://x.ai/cli/install.ps1 | iex";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn powershell_encoded_command(script: &str) -> String {
|
||||
@@ -474,6 +478,14 @@ fn hermes_install_windows_command() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn grok_install_windows_command() -> String {
|
||||
format!(
|
||||
"powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand {}",
|
||||
powershell_encoded_command(GROK_INSTALL_WINDOWS_SCRIPT)
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn hermes_update_windows_command() -> String {
|
||||
// fallback 是 powershell.exe,不是 .cmd/.bat;这里不需要 `call`。PowerShell 的
|
||||
@@ -502,7 +514,7 @@ fn npm_install_command_for(tool: &str) -> Option<&'static str> {
|
||||
|
||||
fn official_update_args(tool: &str) -> Option<&'static str> {
|
||||
match tool {
|
||||
"claude" | "codex" | "hermes" => Some("update"),
|
||||
"claude" | "codex" | "grok" | "hermes" => Some("update"),
|
||||
"openclaw" => Some("update --yes"),
|
||||
"opencode" => Some("upgrade"),
|
||||
_ => None,
|
||||
@@ -534,6 +546,20 @@ fn tool_action_shell_command_for_shell(
|
||||
action: ToolLifecycleAction,
|
||||
shell: LifecycleCommandShell,
|
||||
) -> Option<String> {
|
||||
// xAI's primary Windows distribution is the native PowerShell installer.
|
||||
// Keep npm as the network/policy fallback, matching the POSIX installer chain.
|
||||
#[cfg(target_os = "windows")]
|
||||
if tool == "grok"
|
||||
&& matches!(action, ToolLifecycleAction::Install)
|
||||
&& matches!(shell, LifecycleCommandShell::WindowsBatch)
|
||||
{
|
||||
return Some(chain_update_commands(
|
||||
grok_install_windows_command(),
|
||||
npm_install_command_for(tool)?.to_string(),
|
||||
shell,
|
||||
));
|
||||
}
|
||||
|
||||
if tool == "hermes" {
|
||||
return Some(
|
||||
match (action, shell) {
|
||||
@@ -1454,6 +1480,22 @@ fn opencode_extra_search_paths(
|
||||
paths
|
||||
}
|
||||
|
||||
/// Grok's official installer writes the launcher to `$GROK_BIN_DIR` or, by
|
||||
/// default, `~/.grok/bin`. Keep these ahead of generic npm/Node locations so
|
||||
/// version probing and anchored updates can see the native distribution even
|
||||
/// when the GUI process inherited a stale PATH.
|
||||
fn grok_extra_search_paths(
|
||||
home: &Path,
|
||||
grok_bin_dir: Option<std::ffi::OsString>,
|
||||
) -> Vec<std::path::PathBuf> {
|
||||
let mut paths = Vec::new();
|
||||
push_env_single_dir(&mut paths, grok_bin_dir);
|
||||
if !home.as_os_str().is_empty() {
|
||||
push_unique_path(&mut paths, home.join(".grok").join("bin"));
|
||||
}
|
||||
paths
|
||||
}
|
||||
|
||||
fn tool_executable_candidates(tool: &str, dir: &Path) -> Vec<std::path::PathBuf> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
@@ -1503,6 +1545,12 @@ fn build_tool_search_paths(tool: &str) -> Vec<std::path::PathBuf> {
|
||||
|
||||
// 常见的安装路径(原生安装优先)
|
||||
let mut search_paths: Vec<std::path::PathBuf> = Vec::new();
|
||||
if tool == "grok" {
|
||||
let extra_paths = grok_extra_search_paths(&home, std::env::var_os("GROK_BIN_DIR"));
|
||||
for path in extra_paths {
|
||||
push_unique_path(&mut search_paths, path);
|
||||
}
|
||||
}
|
||||
if !home.as_os_str().is_empty() {
|
||||
push_unique_path(&mut search_paths, home.join(".local/bin"));
|
||||
push_unique_path(&mut search_paths, home.join(".npm-global/bin"));
|
||||
@@ -1987,6 +2035,19 @@ fn brew_formula_from_path(real: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
/// xAI's native installer uses `~/.grok/bin` for its launchers and
|
||||
/// `~/.grok/downloads/grok-<platform>` for the downloaded binary. The launcher
|
||||
/// directory also covers the default Windows layout, where the executable is
|
||||
/// copied instead of symlinked. Checking the real target additionally supports
|
||||
/// a custom `$GROK_BIN_DIR` on POSIX, whose launcher still points into the
|
||||
/// standard downloads directory.
|
||||
fn is_grok_native_install(bin_path: &str, real_target: &str) -> bool {
|
||||
[bin_path, real_target].iter().any(|path| {
|
||||
let normalized = path.replace('\\', "/").to_ascii_lowercase();
|
||||
normalized.contains("/.grok/bin/") || normalized.contains("/.grok/downloads/grok-")
|
||||
})
|
||||
}
|
||||
|
||||
/// 含空格才用 POSIX 单引号包一层,否则保持裸路径——命令展示更干净。
|
||||
/// claude / brew / volta / bun / npm 五个锚定分支共用,避免"含空格"判定漂移。
|
||||
///
|
||||
@@ -2275,14 +2336,14 @@ fn package_manager_anchored_command_from_paths(
|
||||
/// 判定顺序(命中即返回):
|
||||
/// ① Hermes → `<bin_path 绝对> update`;Hermes CLI 自己知道安装环境,避免 cc-switch
|
||||
/// 猜系统 `python3`/`python` 时撞上 Python 版本或 pyenv shim 问题。
|
||||
/// ② Claude 原生安装器(`~/.local/share/claude/versions/`)→ `<bin_path 绝对> update`;
|
||||
/// ② Claude / Grok 原生安装器 → `<bin_path 绝对> update`;
|
||||
/// bin_path 指向 launcher,launcher 内部 dispatch update 子命令。它不归 npm 管,
|
||||
/// 且在 PATH 里比 nvm/homebrew 更靠前,用 npm 升级会装到别处且被原生那份遮蔽。
|
||||
/// ③ Homebrew formula(真身在 `Cellar/<formula>/`)→ `<bin_path 同目录>/brew upgrade <formula>`;
|
||||
/// formula 由 Homebrew 拥有,避免 self-update 尝试改动包管理器管理的安装。
|
||||
/// ④ 其余支持官方自升级的工具 → `<bin_path 绝对> update/upgrade || <原锚定包管理器命令>`;
|
||||
/// Codex 的 self-update 只在部分 release 可用,所以保留 npm/brew/bun/volta fallback。
|
||||
/// ⑤ 不支持官方自升级的 npm 全局包(例如 Gemini CLI / Grok Build) → 锚定到
|
||||
/// ⑤ 不支持官方自升级的 npm 全局包(例如 Gemini CLI,以及非 native 的 Grok Build) → 锚定到
|
||||
/// "那处 bin 目录的 npm"。
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn anchored_command_from_paths(tool: &str, bin_path: &str, real_target: &str) -> Option<String> {
|
||||
@@ -2297,6 +2358,9 @@ fn anchored_command_from_paths(tool: &str, bin_path: &str, real_target: &str) ->
|
||||
{
|
||||
return anchored_official_update_command(tool, bin_path);
|
||||
}
|
||||
if tool == "grok" && is_grok_native_install(bin_path, real_target) {
|
||||
return anchored_official_update_command(tool, bin_path);
|
||||
}
|
||||
let package_command = package_manager_anchored_command_from_paths(tool, bin_path, real_target);
|
||||
if brew_formula_from_path(real_target).is_some() {
|
||||
return package_command;
|
||||
@@ -2344,7 +2408,7 @@ fn package_manager_anchored_command_from_paths(tool: &str, bin_path: &str) -> Op
|
||||
|
||||
/// Windows 版锚定命令生成。对平台确认可静默运行的工具优先使用官方 CLI 自升级;
|
||||
/// 对 npm/Volta/pnpm 这类可确认写回位置的安装,再接一个包管理器 fallback。不存在 brew/bun/claude-native
|
||||
/// (Windows 没 Homebrew、Bun for Windows 仍 preview、claude.ai/install.sh 是 bash 脚本)。
|
||||
/// (Windows 没 Homebrew、Bun for Windows 仍 preview;Grok native 使用 PowerShell installer)。
|
||||
/// Scoop/Chocolatey/winget/nvm-windows/MS Store node 都归 npm 类——它们都只是"如何装
|
||||
/// node"的不同入口,全局包真正的 idiom 仍是 sibling `npm.cmd`。
|
||||
///
|
||||
@@ -2353,16 +2417,15 @@ fn package_manager_anchored_command_from_paths(tool: &str, bin_path: &str) -> Op
|
||||
/// 装 `.cmd`、Volta 装 `.exe`,纯字符串拼接无法消歧。这一平台差异**被刻意保留**:
|
||||
/// 测试用 tempdir 隔离 fs,生产侧 TOCTOU 是 by design(见 `sibling_bin_with_ext` doc)。
|
||||
///
|
||||
/// `_real_target` 占位维持与 POSIX 版的签名对称——Windows 上未观测到需要真身路径
|
||||
/// 区分的等价类(无 Cellar、无 claude-native installer)。若未来加 Scoop persist 锚定
|
||||
/// (scoop 装的工具真身在 `<scoop_root>/persist/<app>/...`),从这里启用 `_real_target`。
|
||||
/// `real_target` 维持与 POSIX 版的签名对称,并辅助识别 Grok native。若未来加 Scoop
|
||||
/// persist 锚定(scoop 装的工具真身在 `<scoop_root>/persist/<app>/...`),也从这里取真身。
|
||||
///
|
||||
/// **关键不变量同 POSIX 版:返回的命令必须用绝对路径,不依赖 PATH**。Windows GUI
|
||||
/// 进程 PATH 由 Service Control Manager / explorer.exe 给,通常不含用户 `%LOCALAPPDATA%`
|
||||
/// 下的 Volta/pnpm 路径;`$SHELL -lic` 的探测时 PATH 与执行时 PATH 不对称。
|
||||
///
|
||||
/// 判定顺序(命中即返回):
|
||||
/// ① hermes → `<bin_path> update`;Hermes CLI 自己处理安装环境。
|
||||
/// ① hermes / Grok native → `<bin_path> update`;CLI 自己处理安装环境。
|
||||
/// ② 支持官方自升级且 Windows 可安全静默执行的工具 → `<bin_path> update/upgrade || call <包管理器 fallback>`。
|
||||
/// ③ 其余 npm 工具 → sibling `npm.cmd`/`.exe` i -g <pkg>@latest。
|
||||
///
|
||||
@@ -2370,10 +2433,13 @@ fn package_manager_anchored_command_from_paths(tool: &str, bin_path: &str) -> Op
|
||||
/// 扩展名存在时,支持官方自升级的工具仍返回 `<bin_path> update/upgrade`,其余工具
|
||||
/// 才返 None 让上游兜回静态命令、`anchored=false`。
|
||||
#[cfg(target_os = "windows")]
|
||||
fn anchored_command_from_paths(tool: &str, bin_path: &str, _real_target: &str) -> Option<String> {
|
||||
fn anchored_command_from_paths(tool: &str, bin_path: &str, real_target: &str) -> Option<String> {
|
||||
if tool == "hermes" {
|
||||
return anchored_official_update_command(tool, bin_path);
|
||||
}
|
||||
if tool == "grok" && is_grok_native_install(bin_path, real_target) {
|
||||
return anchored_official_update_command(tool, bin_path);
|
||||
}
|
||||
let package_command = package_manager_anchored_command_from_paths(tool, bin_path);
|
||||
if prefers_official_update(tool, LifecycleCommandShell::WindowsBatch) {
|
||||
let update = anchored_official_update_command(tool, bin_path)?;
|
||||
@@ -2444,17 +2510,17 @@ fn static_fallback_command(tool: &str) -> String {
|
||||
///
|
||||
/// 设计理由:
|
||||
/// - install 没有锚点可言(从无到有),但**有"上游推荐方式"这一事实** ——
|
||||
/// Anthropic 和 SST(OpenCode)都已将自家 native installer 列为首推、把 npm 列为传统方式。
|
||||
/// Anthropic、xAI 和 SST(OpenCode)都已将自家 native installer 列为首推、把 npm 列为替代方式。
|
||||
/// 把这层认知补进来,让 install 表与 update 端的锚定决策树共用同一份"上游事实"。
|
||||
/// - Hermes 使用官方 installer,避免用系统 Python/pip 安装时踩 Python >=3.11 与 pyenv
|
||||
/// `python` shim 问题;更新路径若能锚定已安装 CLI,则走 `<hermes> update`。
|
||||
/// **Hermes 没有 npm 包,install 端不享受 `||` 降级**——上游 installer 不可达就只能等。
|
||||
/// - 对**有 npm 包**的工具(claude/opencode),短路链(POSIX `||`)保证官方脚本不可达/
|
||||
/// - 对**有 npm 包**的工具(claude/grok/opencode),短路链(POSIX `||`)保证官方脚本不可达/
|
||||
/// 防火墙拦截时仍能装上,降级到裸 `npm i -g`。官方脚本本身不用 pipe,
|
||||
/// 所以这条路径在 WSL 的 `sh -c` 子 shell 中也不依赖外层 `pipefail`。
|
||||
/// - Windows 原生不启用:claude.ai/install.sh、opencode.ai/install 都是 bash 脚本,
|
||||
/// Windows 原生继续走 `tool_action_shell_command` 的 npm/PowerShell 命令;WSL 作为
|
||||
/// Linux 环境复用这套 POSIX 安装优先级。
|
||||
/// - Windows 上 Claude/OpenCode 原生不启用(对应 installer 都是 bash 脚本);Grok
|
||||
/// 使用官方 PowerShell installer,并同样保留 npm fallback。WSL 作为 Linux 环境
|
||||
/// 复用这套 POSIX 安装优先级。
|
||||
fn installer_with_npm_fallback(installer: &str, tool: &str) -> String {
|
||||
match npm_install_command_for(tool) {
|
||||
Some(npm) => chain_update_commands(
|
||||
@@ -2469,6 +2535,7 @@ fn installer_with_npm_fallback(installer: &str, tool: &str) -> String {
|
||||
fn posix_install_command_for(tool: &str) -> String {
|
||||
match tool {
|
||||
"claude" => installer_with_npm_fallback(CLAUDE_INSTALL_UNIX, tool),
|
||||
"grok" => installer_with_npm_fallback(GROK_INSTALL_UNIX, tool),
|
||||
"opencode" => installer_with_npm_fallback(OPENCODE_INSTALL_UNIX, tool),
|
||||
"hermes" => HERMES_INSTALL_UNIX.to_string(),
|
||||
_ => static_fallback_command_for(tool, ToolLifecycleAction::Install),
|
||||
@@ -3697,23 +3764,27 @@ mod tests {
|
||||
npm_install_command_for("grok"),
|
||||
Some("npm i -g @xai-official/grok@latest")
|
||||
);
|
||||
assert_eq!(official_update_args("grok"), None);
|
||||
assert_eq!(official_update_args("grok"), Some("update"));
|
||||
|
||||
for shell in [
|
||||
LifecycleCommandShell::Posix,
|
||||
LifecycleCommandShell::WindowsBatch,
|
||||
] {
|
||||
for action in [ToolLifecycleAction::Install, ToolLifecycleAction::Update] {
|
||||
assert_eq!(
|
||||
tool_action_shell_command_for_shell("grok", ToolLifecycleAction::Install, shell,)
|
||||
.as_deref(),
|
||||
Some("npm i -g @xai-official/grok@latest")
|
||||
);
|
||||
assert_eq!(
|
||||
tool_action_shell_command_for_shell("grok", ToolLifecycleAction::Update, shell,)
|
||||
tool_action_shell_command_for_shell("grok", action, LifecycleCommandShell::Posix)
|
||||
.as_deref(),
|
||||
Some("npm i -g @xai-official/grok@latest")
|
||||
);
|
||||
}
|
||||
|
||||
// Static update remains package-manager based. Only a path positively
|
||||
// identified as xAI's native install may run `grok update`.
|
||||
assert_eq!(
|
||||
tool_action_shell_command_for_shell(
|
||||
"grok",
|
||||
ToolLifecycleAction::Update,
|
||||
LifecycleCommandShell::WindowsBatch,
|
||||
)
|
||||
.as_deref(),
|
||||
Some("npm i -g @xai-official/grok@latest")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3971,6 +4042,35 @@ mod tests {
|
||||
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grok_native_windows_uses_self_update() {
|
||||
let (_dir, _sub, bin_path) = setup_sibling(".grok/bin", "grok.exe", &["npm.cmd"]);
|
||||
let cmd = anchored_command_from_paths("grok", &bin_path, &bin_path);
|
||||
let expected = format!("{} update", expect_quoted_path(&bin_path));
|
||||
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grok_windows_install_prefers_powershell_with_npm_fallback() {
|
||||
let install = static_fallback_command_for("grok", ToolLifecycleAction::Install);
|
||||
let native = grok_install_windows_command();
|
||||
assert!(
|
||||
install.starts_with(&native),
|
||||
"native installer first: {install}"
|
||||
);
|
||||
assert!(
|
||||
install.ends_with("|| call npm i -g @xai-official/grok@latest"),
|
||||
"npm fallback should remain available: {install}"
|
||||
);
|
||||
let expected_encoded = powershell_encoded_command(GROK_INSTALL_WINDOWS_SCRIPT);
|
||||
assert_eq!(
|
||||
native
|
||||
.split_once("-EncodedCommand ")
|
||||
.map(|(_, encoded)| encoded),
|
||||
Some(expected_encoded.as_str())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_no_sibling_uses_cli_update_without_package_fallback() {
|
||||
// sibling 包管理器不存在(纯独立二进制)时,仍可锚定到 CLI 自身跑官方 update。
|
||||
@@ -4293,7 +4393,13 @@ mod tests {
|
||||
assert_eq!(codex, "npm i -g @openai/codex@latest");
|
||||
|
||||
let grok = wsl_tool_action_shell_command("grok", ToolLifecycleAction::Install).unwrap();
|
||||
assert_eq!(grok, "npm i -g @xai-official/grok@latest");
|
||||
assert!(
|
||||
grok.starts_with(
|
||||
"bash -c 'tmp=$(mktemp) && curl -fsSL https://x.ai/cli/install.sh "
|
||||
) && grok.contains(" || npm i -g @xai-official/grok@latest"),
|
||||
"WSL grok install should prefer native POSIX installer with npm fallback: {grok}"
|
||||
);
|
||||
assert!(!grok.contains("| bash"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -4410,6 +4516,28 @@ mod tests {
|
||||
assert_eq!(cmd.as_deref(), Some("/Users/me/.local/bin/claude update"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grok_native_installer_uses_self_update() {
|
||||
// ~/.grok/bin/grok is a launcher symlink into ~/.grok/downloads.
|
||||
// Updating it through npm would create or mutate a different install.
|
||||
let cmd = anchored_command_from_paths(
|
||||
"grok",
|
||||
"/Users/me/.grok/bin/grok",
|
||||
"/Users/me/.grok/downloads/grok-macos-aarch64",
|
||||
);
|
||||
assert_eq!(cmd.as_deref(), Some("/Users/me/.grok/bin/grok update"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grok_custom_bin_dir_is_native_when_target_is_official_download() {
|
||||
let cmd = anchored_command_from_paths(
|
||||
"grok",
|
||||
"/Users/me/bin/grok",
|
||||
"/Users/me/.grok/downloads/grok-macos-aarch64",
|
||||
);
|
||||
assert_eq!(cmd.as_deref(), Some("/Users/me/bin/grok update"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_homebrew_formula_uses_brew_upgrade() {
|
||||
// /opt/homebrew/bin/gemini → Cellar/gemini-cli/...:是 brew formula 而非 npm 全局包,
|
||||
@@ -4975,9 +5103,24 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grok_install_keeps_static_npm() {
|
||||
fn grok_install_prefers_native_with_npm_fallback() {
|
||||
let cmd = install_command_for("grok");
|
||||
assert_eq!(cmd, "npm i -g @xai-official/grok@latest");
|
||||
assert!(
|
||||
cmd.contains("https://x.ai/cli/install.sh"),
|
||||
"should include official installer URL: {cmd}"
|
||||
);
|
||||
assert!(
|
||||
cmd.contains("@xai-official/grok@latest"),
|
||||
"should keep npm package as fallback: {cmd}"
|
||||
);
|
||||
let parts: Vec<&str> = cmd.split("||").collect();
|
||||
assert_eq!(parts.len(), 2, "should be a two-step short-circuit chain");
|
||||
assert!(parts[0].contains("install.sh"), "native first: {cmd}");
|
||||
assert!(
|
||||
!parts[0].contains('|'),
|
||||
"native installer should avoid pipe: {cmd}"
|
||||
);
|
||||
assert!(parts[1].contains("npm i -g"), "npm second: {cmd}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -5149,6 +5292,27 @@ mod tests {
|
||||
assert_eq!(count, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grok_extra_search_paths_prefers_override_then_default_native_dir() {
|
||||
let home = PathBuf::from("/home/tester");
|
||||
let paths =
|
||||
grok_extra_search_paths(&home, Some(std::ffi::OsString::from("/custom/grok/bin")));
|
||||
|
||||
assert_eq!(paths[0], PathBuf::from("/custom/grok/bin"));
|
||||
assert_eq!(paths[1], PathBuf::from("/home/tester/.grok/bin"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grok_extra_search_paths_deduplicates_default_override() {
|
||||
let home = PathBuf::from("/home/tester");
|
||||
let paths = grok_extra_search_paths(
|
||||
&home,
|
||||
Some(std::ffi::OsString::from("/home/tester/.grok/bin")),
|
||||
);
|
||||
|
||||
assert_eq!(paths, vec![PathBuf::from("/home/tester/.grok/bin")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_path_env_search_paths_include_path_entries_and_dedupe() {
|
||||
let temp = tempfile::tempdir().expect("temp dir should be created");
|
||||
|
||||
Reference in New Issue
Block a user