Files
Archduino/internal/subcommands/doctor/doctor.go

105 lines
2.8 KiB
Go

// Package doctor reports module convergence and pinned-tool version drift.
package doctor
import (
"context"
"encoding/json"
"fmt"
"io"
"regexp"
"strings"
"gitlab.hive-brain.de/MDietermann/Archduino/internal/config"
"gitlab.hive-brain.de/MDietermann/Archduino/internal/module"
"gitlab.hive-brain.de/MDietermann/Archduino/internal/platform"
)
// Run inspects every module in the registry plus pinned tool versions and
// writes a human-readable report to w. Returns 0 when everything is
// converged with no drift; 1 otherwise. Never panics on module errors.
func Run(ctx context.Context, cfg config.Config, reg *module.Registry, runner platform.Runner, w io.Writer) int {
var converged, pending, errs, drift int
for _, m := range reg.All() {
id := m.Meta().ID
st, err := m.Detect(ctx)
if err != nil {
fmt.Fprintf(w, "[!] %s — detect failed: %v\n", id, err)
errs++
continue
}
pl, err := m.Plan(ctx, st)
if err != nil {
fmt.Fprintf(w, "[!] %s — plan failed: %v\n", id, err)
errs++
continue
}
if pl.IsEmpty() {
fmt.Fprintf(w, "[✓] %s\n", id)
converged++
} else {
fmt.Fprintf(w, "[✗] %s (%d actions pending)\n", id, len(pl.Actions))
pending++
}
}
drift += checkArduinoCLI(ctx, runner, cfg.ArduinoCLI.Version, w)
drift += checkWokwiCLI(ctx, runner, cfg.Renode.WokwiVersion, w)
fmt.Fprintf(w, "\n%d converged, %d need-install, %d drift, %d errors\n", converged, pending, drift, errs)
if pending > 0 || drift > 0 || errs > 0 {
return 1
}
return 0
}
type arduinoCLIVersion struct {
VersionString string `json:"VersionString"`
}
func checkArduinoCLI(ctx context.Context, runner platform.Runner, want string, w io.Writer) int {
if want == "" {
return 0
}
out, err := runner.Output(ctx, "arduino-cli", "version", "--format", "json")
if err != nil {
return 0 // not installed → silent skip
}
var v arduinoCLIVersion
if err := json.Unmarshal(out, &v); err != nil || v.VersionString == "" {
fmt.Fprintf(w, "[!] arduino-cli — version parse failed\n")
return 1
}
if v.VersionString != want {
fmt.Fprintf(w, "[!] arduino-cli drift: got %s, want %s\n", v.VersionString, want)
return 1
}
fmt.Fprintf(w, "[✓] arduino-cli %s\n", v.VersionString)
return 0
}
var wokwiVersionRE = regexp.MustCompile(`v?(\d+\.\d+\.\d+)`)
func checkWokwiCLI(ctx context.Context, runner platform.Runner, want string, w io.Writer) int {
if want == "" {
return 0
}
out, err := runner.Output(ctx, "wokwi-cli", "--version")
if err != nil {
return 0
}
m := wokwiVersionRE.FindStringSubmatch(strings.TrimSpace(string(out)))
if len(m) < 2 {
fmt.Fprintf(w, "[!] wokwi-cli — version parse failed\n")
return 1
}
got := m[1]
if got != want {
fmt.Fprintf(w, "[!] wokwi-cli drift: got %s, want %s\n", got, want)
return 1
}
fmt.Fprintf(w, "[✓] wokwi-cli %s\n", got)
return 0
}