cmd/tilstream/cmd/doctor.go

package cmd

import (
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"

	"github.com/spf13/cobra"
)

var doctorCmd = &cobra.Command{
	Use:   "doctor",
	Short: "Diagnose the local tilstream setup",
	RunE:  runDoctor,
}

type check struct {
	name string
	run  func() checkResult
}

type checkResult struct {
	ok     bool
	msg    string
	detail string
}

func runDoctor(cmd *cobra.Command, args []string) error {
	checks := []check{
		{name: "go_runtime", run: checkGoRuntime},
		{name: "config_present", run: checkConfig},
		{name: "content_dir", run: checkContentDir},
		{name: "rsync_available", run: checkCommand("rsync", "--version")},
		{name: "git_available", run: checkCommand("git", "--version")},
	}
	failed := 0
	for _, c := range checks {
		res := c.run()
		mark := "ok"
		if !res.ok {
			mark = "FAIL"
			failed++
		}
		fmt.Fprintf(cmd.OutOrStdout(), "[%s] %s: %s\n", mark, c.name, res.msg)
		if verbose && res.detail != "" {
			fmt.Fprintln(cmd.OutOrStdout(), "       ", res.detail)
		}
	}
	if failed > 0 {
		return fmt.Errorf("%d check(s) failed", failed)
	}
	return nil
}

func checkGoRuntime() checkResult {
	return checkResult{ok: true, msg: runtime.Version(), detail: runtime.GOOS + "/" + runtime.GOARCH}
}

func checkConfig() checkResult {
	if _, err := os.Stat(cfgFile); err != nil {
		return checkResult{ok: false, msg: "not found at " + cfgFile}
	}
	return checkResult{ok: true, msg: cfgFile}
}

func checkContentDir() checkResult {
	if _, err := os.Stat("content"); err != nil {
		return checkResult{ok: false, msg: "content/ missing", detail: err.Error()}
	}
	return checkResult{ok: true, msg: "content/ present"}
}

func checkCommand(name string, args ...string) func() checkResult {
	return func() checkResult {
		path, err := exec.LookPath(name)
		if err != nil {
			return checkResult{ok: false, msg: name + " not found on PATH"}
		}
		out, err := exec.Command(name, args...).Output()
		if err != nil {
			return checkResult{ok: false, msg: name + " failed: " + err.Error()}
		}
		return checkResult{ok: true, msg: path, detail: strings.TrimSpace(string(out))}
	}
}

// Summary returns a short human-readable digest of all checks. Used by
// the JSON mode that the CI runner consumes.
func Summary() string {
	return fmt.Sprintf("tilstream doctor: %s on %s", runtime.Version(), runtime.GOOS)
}

// ExitOnFail mirrors the --strict behaviour of other CLIs I've built.
func ExitOnFail(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, "doctor:", err)
		os.Exit(2)
	}
}