feat(doctor): initial doctor command

This commit is contained in:
Lewis Wynne 2026-02-11 20:10:35 +00:00
parent ce7336324f
commit 0c5b73154d
4 changed files with 138 additions and 0 deletions

128
cmd/doctor.go Normal file
View file

@ -0,0 +1,128 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
)
var doctorCmd = &cobra.Command{
Use: "doctor",
Short: "Check environment health",
RunE: doctor,
SilenceUsage: true,
}
func init() {
rootCmd.AddCommand(doctorCmd)
}
func doctor(cmd *cobra.Command, args []string) error {
tty := stdoutIsTerminal()
hasError := false
emit := func(level, msg string) {
var code string
switch level {
case "ok":
code = "32"
case "info":
code = "34"
case "FAIL":
code = "31"
hasError = true
}
fmt.Fprintf(os.Stdout, "%s %s\n", keyword(code, level, tty), msg)
}
// Config
cfgPath, err := configPath()
if err != nil {
emit("FAIL", fmt.Sprintf("Cannot determine config path: %v", err))
} else if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
emit("info", "Using default configuration")
} else if err != nil {
emit("FAIL", fmt.Sprintf("Cannot access config: %v", err))
} else {
emit("ok", "Configuration loaded")
}
// Data directory
store := &Store{}
dataDir, err := store.path()
if err != nil {
emit("FAIL", fmt.Sprintf("Data directory inaccessible: %v", err))
} else {
emit("ok", "Data directory accessible")
}
// Identity
idPath, err := identityPath()
if err != nil {
emit("FAIL", fmt.Sprintf("Cannot determine identity path: %v", err))
} else if _, err := os.Stat(idPath); os.IsNotExist(err) {
emit("info", "No identity file. One will be created on first encrypt.")
} else if err != nil {
emit("FAIL", fmt.Sprintf("Cannot access identity file: %v", err))
} else {
emit("ok", "Identity file present")
}
// Git
gitInitialised := false
if dataDir != "" {
gitDir := filepath.Join(dataDir, ".git")
if _, err := os.Stat(gitDir); os.IsNotExist(err) {
emit("info", "Git not initialised")
} else if err != nil {
emit("FAIL", fmt.Sprintf("Cannot check git status: %v", err))
} else {
gitInitialised = true
emit("ok", "Git initialised")
}
}
// Git remote (only when git is initialised)
if gitInitialised {
hasOrigin, err := repoHasRemote(dataDir, "origin")
if err != nil {
emit("FAIL", fmt.Sprintf("Cannot check git remote: %v", err))
} else if hasOrigin {
emit("ok", "Git remote configured")
} else {
emit("info", "No git remote configured")
}
}
// Stores
stores, err := store.AllStores()
if err != nil {
emit("FAIL", fmt.Sprintf("Cannot list stores: %v", err))
} else if len(stores) == 0 {
emit("info", "No stores")
} else {
var parseErrors int
for _, name := range stores {
p, pErr := store.storePath(name)
if pErr != nil {
parseErrors++
continue
}
if _, rErr := readStoreFile(p, nil); rErr != nil {
parseErrors++
}
}
if parseErrors > 0 {
emit("FAIL", fmt.Sprintf("%d store(s), %d with errors", len(stores), parseErrors))
} else {
emit("ok", fmt.Sprintf("%d store(s)", len(stores)))
}
}
if hasError {
os.Exit(1)
}
return nil
}

7
testdata/doctor.ct vendored Normal file
View file

@ -0,0 +1,7 @@
# Doctor reports environment health
$ pda doctor
info Using default configuration
ok Data directory accessible
ok Identity file present
info Git not initialised
ok 5 store(s)

2
testdata/help.ct vendored
View file

@ -36,6 +36,7 @@ Git commands:
Additional Commands:
completion Generate the autocompletion script for the specified shell
doctor Check environment health
help Help about any command
version Display pda! version
@ -79,6 +80,7 @@ Git commands:
Additional Commands:
completion Generate the autocompletion script for the specified shell
doctor Check environment health
help Help about any command
version Display pda! version

1
testdata/root.ct vendored
View file

@ -35,6 +35,7 @@ Git commands:
Additional Commands:
completion Generate the autocompletion script for the specified shell
doctor Check environment health
help Help about any command
version Display pda! version