105 lines
2.8 KiB
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"
|
|
|
|
"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
|
|
}
|