package platform import ( "bytes" "context" "errors" "os/exec" "strconv" "strings" "unicode/utf16" ) // CommandError 保存后台命令失败时的原始诊断信息。 // 服务层会把这些输出映射成稳定错误码,界面再按当前语言展示友好提示。 type CommandError struct { Command string Args []string ExitCode int Output string } func (e *CommandError) Error() string { if strings.TrimSpace(e.Output) != "" { return strings.TrimSpace(e.Output) } if e.ExitCode != 0 { return e.Command + " exited with code " + strconv.Itoa(e.ExitCode) } return e.Command + " failed" } // RunHidden 在后台执行命令并合并 stdout/stderr。 // Windows 下由平台文件设置 HideWindow 和 CREATE_NO_WINDOW,避免 Git/WSL 查询弹出终端窗口。 func RunHidden(ctx context.Context, name string, args ...string) (string, error) { cmd := exec.CommandContext(ctx, name, args...) configureHidden(cmd) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr e := cmd.Run() out := strings.TrimSpace(strings.ReplaceAll(decodeOutput(stdout.Bytes()), "\x00", "")) errOut := strings.TrimSpace(strings.ReplaceAll(decodeOutput(stderr.Bytes()), "\x00", "")) if e != nil { if out == "" { out = errOut } else if errOut != "" { out += "\n" + errOut } if out == "" { out = e.Error() } exitCode := -1 var exit *exec.ExitError if errors.As(e, &exit) { exitCode = exit.ExitCode() } return "", &CommandError{Command: name, Args: args, ExitCode: exitCode, Output: out} } return out, nil } // ConfigureHidden 供需要自建 exec.Cmd 流式读取输出的调用方隐藏控制台窗口。 func ConfigureHidden(cmd *exec.Cmd) { configureHidden(cmd) } // DecodeWSLList 兼容部分 Windows 版本返回的 UTF-16LE 发行版列表。 func DecodeWSLList(b []byte) string { return decodeOutput(b) } func decodeOutput(b []byte) string { if len(b) >= 2 && (b[1] == 0 || b[0] == 0xff && b[1] == 0xfe) { if len(b)%2 != 0 { b = b[:len(b)-1] } u := make([]uint16, 0, len(b)/2) for i := 0; i+1 < len(b); i += 2 { u = append(u, uint16(b[i])|uint16(b[i+1])<<8) } return strings.TrimPrefix(string(utf16.Decode(u)), "\ufeff") } return string(b) }