Wave 2: 五件套打通全链路(安装/检测/配置/授权/诊断)
- 五个适配器 YAML 全字段补齐(codex/claude-code/gemini/kimi/opencode) - agentdock-config:TOML/JSON 编解码 + 原子写 + 自动备份 - agentdock-diag:PATH/依赖版本/版本冲突/配置损坏四类规则 - agentdock-core:detectCli/previewAction/runAction 流式/readConfig/writeConfig/authStatus/diagnose - Tauri IPC 命令 + CLI 详情页/安装确认弹窗/配置表单/诊断页 - cargo test 75 项通过(含 4 项真机 ignored 自测),前端 build 通过
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
//! 进程探测与流式执行辅助(内部)
|
||||
//!
|
||||
//! 所有命令经 `agentdock-exec::validate_argv` 校验后执行,禁止 shell 拼接。
|
||||
//! 探测类命令(`--version`)隐藏控制台窗口(Windows CREATE_NO_WINDOW)。
|
||||
|
||||
use std::io::BufRead;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
/// 按平台分隔符拆分 PATH。
|
||||
fn path_entries() -> Vec<String> {
|
||||
std::env::var("PATH")
|
||||
.unwrap_or_default()
|
||||
.split(if cfg!(windows) { ';' } else { ':' })
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 在 PATH 中查找可执行文件(返回全部命中,用于版本冲突检测)。
|
||||
/// Windows 优先 .exe/.cmd/.bat,最后回退无扩展名。
|
||||
pub fn which_all(program: &str) -> Vec<PathBuf> {
|
||||
let direct = Path::new(program);
|
||||
if direct.is_absolute() && direct.is_file() {
|
||||
return vec![direct.to_path_buf()];
|
||||
}
|
||||
let exts: &[&str] = if cfg!(windows) { &[".exe", ".cmd", ".bat", ""] } else { &[""] };
|
||||
let mut found = Vec::new();
|
||||
for dir in path_entries() {
|
||||
for ext in exts {
|
||||
let cand = Path::new(&dir).join(format!("{program}{ext}"));
|
||||
if cand.is_file() && !found.contains(&cand) {
|
||||
found.push(cand);
|
||||
}
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
/// 运行只读探测命令并返回 (exit_ok, stdout+stderr 合并文本)。
|
||||
pub fn run_capture(exe: &Path, args: &[String]) -> std::io::Result<(bool, String)> {
|
||||
let mut cmd = Command::new(exe);
|
||||
cmd.args(args);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW
|
||||
}
|
||||
let out = cmd.output()?;
|
||||
let mut text = String::from_utf8_lossy(&out.stdout).to_string();
|
||||
if text.trim().is_empty() {
|
||||
text = String::from_utf8_lossy(&out.stderr).to_string();
|
||||
}
|
||||
Ok((out.status.success(), text))
|
||||
}
|
||||
|
||||
/// 流式执行命令,逐行回调(stdout 与 stderr 均按行输出)。返回是否成功。
|
||||
/// 顺序读:先 stdout 后 stderr;对长任务足够,且实现简单可靠、无闭包 Send 负担。
|
||||
pub fn run_streaming<F>(exe: &str, args: &[String], mut on_line: F) -> std::io::Result<bool>
|
||||
where
|
||||
F: FnMut(bool, &str),
|
||||
{
|
||||
let mut cmd = Command::new(exe);
|
||||
cmd.args(args);
|
||||
cmd.stdout(Stdio::piped());
|
||||
cmd.stderr(Stdio::piped());
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
cmd.creation_flags(0x0800_0000);
|
||||
}
|
||||
let mut child = cmd.spawn()?;
|
||||
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
let reader = std::io::BufReader::new(stdout);
|
||||
for line in reader.lines().map_while(Result::ok) {
|
||||
on_line(false, &line);
|
||||
}
|
||||
}
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
let reader = std::io::BufReader::new(stderr);
|
||||
for line in reader.lines().map_while(Result::ok) {
|
||||
on_line(true, &line);
|
||||
}
|
||||
}
|
||||
|
||||
let status = child.wait()?;
|
||||
Ok(status.success())
|
||||
}
|
||||
Reference in New Issue
Block a user