package main import ( "context" "errors" "io" "os" "os/exec" "time" "go.uber.org/zap" ) const promptCommandTimeout = 5 * time.Second type promptRequest struct { title string message string subtitle string } type promptChannel struct { name string available func() error run func(promptRequest) error } type commandRunner interface { LookPath(file string) (string, error) Run(timeout time.Duration, env []string, name string, args ...string) error } type defaultCommandRunner struct{} var buildPromptChannels = platformStartupChannels func (defaultCommandRunner) LookPath(file string) (string, error) { return exec.LookPath(file) } func (defaultCommandRunner) Run(timeout time.Duration, env []string, name string, args ...string) error { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() cmd := exec.CommandContext(ctx, name, args...) if len(env) > 0 { cmd.Env = append(os.Environ(), env...) } if err := cmd.Run(); err != nil { return err } if err := ctx.Err(); err != nil { return err } return nil } func showError(title, message string) { reportPrompt(promptRequest{title: title, message: message}, os.Stderr, dialogLogger()) } func reportStartupFailure(err error, logger *zap.Logger) { if err == nil { return } var startupErr *startupError if !errors.As(err, &startupErr) { startupErr = newStartupError(phaseServer, startupServerErrorMessage(), err) } if logger == nil { logger = dialogLogger() } logger.Error("desktop 启动失败", zap.String("phase", startupErr.Phase()), zap.Error(startupErr)) reportPrompt(promptRequest{ title: startupTitle(), message: startupErr.UserMessage(), subtitle: startupErr.Phase(), }, os.Stderr, logger) } func reportPrompt(req promptRequest, fallback io.Writer, logger *zap.Logger) { runPromptPipeline(req, buildPromptChannels(defaultCommandRunner{}), fallback, logger) } func runPromptPipeline(req promptRequest, channels []promptChannel, fallback io.Writer, logger *zap.Logger) { if logger == nil { logger = dialogLogger() } for _, channel := range channels { if channel.available != nil { if err := channel.available(); err != nil { logger.Warn("提示通道不可用", zap.String("channel", channel.name), zap.Error(err)) continue } } if err := channel.run(req); err != nil { logger.Warn("提示通道执行失败", zap.String("channel", channel.name), zap.Error(err)) continue } return } writePromptFallback(fallback, req.title, req.message) } func writePromptFallback(w io.Writer, title, message string) { if w == nil { return } if _, err := io.WriteString(w, "错误: "+title+": "+message+"\n"); err != nil { return } }