feat: improved error messaging, and automatic doctor runs on fatal failure

This commit is contained in:
Lewis Wynne 2026-02-12 00:17:33 +00:00
parent 6ad6876051
commit d992074c9c
4 changed files with 39 additions and 9 deletions

View file

@ -38,18 +38,27 @@ func runDoctor(w io.Writer) bool {
} }
hasError := false hasError := false
lastFail := false
emit := func(level, msg string) { emit := func(level, msg string) {
var code string var code string
switch level { switch level {
case "ok": case "ok":
code = "32" code = "32"
lastFail = false
case "WARN": case "WARN":
code = "33" code = "33"
lastFail = false
case "FAIL": case "FAIL":
code = "31" code = "31"
hasError = true hasError = true
lastFail = true
}
if lastFail && tty {
fmt.Fprintf(w, "%s \033[1m%s\033[0m\n", keyword(code, level, tty), msg)
} else {
fmt.Fprintf(w, "%s %s\n", keyword(code, level, tty), msg)
} }
fmt.Fprintf(w, "%s %s\n", keyword(code, level, tty), msg)
} }
tree := func(items []string) { tree := func(items []string) {
@ -58,7 +67,11 @@ func runDoctor(w io.Writer) bool {
if i == len(items)-1 { if i == len(items)-1 {
connector = "└── " connector = "└── "
} }
fmt.Fprintf(w, " %s%s\n", connector, item) if lastFail && tty {
fmt.Fprintf(w, "\033[1m %s%s\033[0m\n", connector, item)
} else {
fmt.Fprintf(w, " %s%s\n", connector, item)
}
} }
} }
@ -117,6 +130,7 @@ func runDoctor(w io.Writer) bool {
} }
if configErr != nil { if configErr != nil {
issues = append(issues, fmt.Sprintf("Parse error: %v", configErr)) issues = append(issues, fmt.Sprintf("Parse error: %v", configErr))
issues = append(issues, "While broken, ONLY 'doctor', 'config edit', and 'config init' will function")
issues = append(issues, "Fix with 'pda config edit' or 'pda config init --new'") issues = append(issues, "Fix with 'pda config edit' or 'pda config init --new'")
} }
if unexpectedFiles(cfgDir, map[string]bool{ if unexpectedFiles(cfgDir, map[string]bool{

View file

@ -14,6 +14,9 @@ func TestDoctorCleanEnv(t *testing.T) {
configDir := t.TempDir() configDir := t.TempDir()
t.Setenv("PDA_DATA", dataDir) t.Setenv("PDA_DATA", dataDir)
t.Setenv("PDA_CONFIG", configDir) t.Setenv("PDA_CONFIG", configDir)
saved := configErr
configErr = nil
t.Cleanup(func() { configErr = saved })
var buf bytes.Buffer var buf bytes.Buffer
hasError := runDoctor(&buf) hasError := runDoctor(&buf)
@ -40,6 +43,9 @@ func TestDoctorWithStores(t *testing.T) {
configDir := t.TempDir() configDir := t.TempDir()
t.Setenv("PDA_DATA", dataDir) t.Setenv("PDA_DATA", dataDir)
t.Setenv("PDA_CONFIG", configDir) t.Setenv("PDA_CONFIG", configDir)
saved := configErr
configErr = nil
t.Cleanup(func() { configErr = saved })
content := "{\"key\":\"foo\",\"value\":\"bar\",\"encoding\":\"text\"}\n" + content := "{\"key\":\"foo\",\"value\":\"bar\",\"encoding\":\"text\"}\n" +
"{\"key\":\"baz\",\"value\":\"qux\",\"encoding\":\"text\"}\n" "{\"key\":\"baz\",\"value\":\"qux\",\"encoding\":\"text\"}\n"

View file

@ -31,24 +31,33 @@ func stdoutIsTerminal() bool {
} }
// keyword returns a right-aligned, colored keyword (color only on TTY). // keyword returns a right-aligned, colored keyword (color only on TTY).
// All keywords are bold except dim (code "2").
// //
// FAIL red (stderr) // FAIL bold red (stderr)
// hint dim (stderr) // hint dim (stderr)
// WARN yellow (stderr) // WARN bold yellow (stderr)
// info blue (stderr) // info bold blue (stderr)
// ok green (stderr) // ok bold green (stderr)
// ? cyan (stdout) // ? bold cyan (stdout)
// > dim (stdout) // > dim (stdout)
func keyword(code, word string, tty bool) string { func keyword(code, word string, tty bool) string {
padded := fmt.Sprintf("%4s", word) padded := fmt.Sprintf("%4s", word)
if tty { if tty {
if code != "2" {
code = "1;" + code
}
return fmt.Sprintf("\033[%sm%s\033[0m", code, padded) return fmt.Sprintf("\033[%sm%s\033[0m", code, padded)
} }
return padded return padded
} }
func printError(err error) { func printError(err error) {
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("31", "FAIL", stderrIsTerminal()), err) tty := stderrIsTerminal()
if tty {
fmt.Fprintf(os.Stderr, "%s \033[1m%s\033[0m\n", keyword("31", "FAIL", true), err)
} else {
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("31", "FAIL", false), err)
}
} }
func printHint(format string, args ...any) { func printHint(format string, args ...any) {

View file

@ -23,6 +23,7 @@ THE SOFTWARE.
package cmd package cmd
import ( import (
"fmt"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -40,7 +41,7 @@ func Execute() {
if configErr != nil { if configErr != nil {
cmd, _, _ := rootCmd.Find(os.Args[1:]) cmd, _, _ := rootCmd.Find(os.Args[1:])
if !configSafeCmd(cmd) { if !configSafeCmd(cmd) {
infof("Running pda! doctor") printError(fmt.Errorf("fatal problem: running pda! doctor automatically"))
runDoctor(os.Stderr) runDoctor(os.Stderr)
os.Exit(1) os.Exit(1)
} }