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)
}
}