feat(doctor): add archduino doctor subcommand
This commit is contained in:
104
internal/subcommands/doctor/doctor.go
Normal file
104
internal/subcommands/doctor/doctor.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Package doctor reports module convergence and pinned-tool version drift.
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/MDietermann/ArduinoArchSetup/internal/config"
|
||||
"github.com/MDietermann/ArduinoArchSetup/internal/module"
|
||||
"github.com/MDietermann/ArduinoArchSetup/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
|
||||
}
|
||||
Reference in New Issue
Block a user