@@ -11,6 +11,7 @@ import (
1111 "github.com/moderation-api/cli/internal/clierrors"
1212 "github.com/moderation-api/cli/internal/config"
1313 "github.com/moderation-api/cli/internal/flagutil"
14+ "github.com/moderation-api/cli/internal/output"
1415 "github.com/moderation-api/cli/internal/sdk/models/operations"
1516 "github.com/moderation-api/cli/internal/usage"
1617)
@@ -26,7 +27,7 @@ files, then the keychain), checks it against the API, and checks the webhook
2627signing secret, .gitignore, the SDK dependency and the modapi login session.
2728
2829Each check has a status (ok, warn, fail or skip) and, when it is not ok, a
29- remedy to run. doctor exits 1 when a check fails. Use -o json for the result
30+ fix to run. doctor exits 1 when a check fails. Use -o json for the result
3031as data.` ,
3132 Example : ` modapi doctor
3233 modapi doctor -o json` ,
@@ -85,18 +86,19 @@ func runDoctor(cmd *cobra.Command, _ []string) error {
8586 add (checkSDK (detected ))
8687 add (checkLogin ())
8788
88- if err := render (cmd , result , func (w io.Writer ) { printDoctor (w , & result ) }); err != nil {
89+ colors := doctorColors (cmd )
90+ if err := render (cmd , result , func (w io.Writer ) { printDoctor (w , & result , colors ) }); err != nil {
8991 return err
9092 }
9193 if ! result .OK {
92- failed := 0
93- for _ , c := range result .Checks {
94- if c .Status == "fail" {
95- failed ++
96- }
94+ failed := countStatus (result .Checks , "fail" )
95+ err := withHints (fmt .Errorf ("%d check(s) failed" , failed ),
96+ "Run the fix listed for each problem, then run `modapi doctor` again" )
97+ if ! output .IsMachineMode (cmd ) {
98+ // The summary above already says what failed and how to fix it.
99+ return shownError {err }
97100 }
98- return clierrors .WithExitCode (withHints (fmt .Errorf ("%d check(s) failed" , failed ),
99- "Run the remedy listed for each failed check, then run `modapi doctor` again" ), 1 )
101+ return clierrors .WithExitCode (err , 1 )
100102 }
101103 return nil
102104}
@@ -206,15 +208,94 @@ func checkLogin() check {
206208 }
207209}
208210
209- func printDoctor (w io.Writer , r * doctorResult ) {
210- marks := map [string ]string {"ok" : "✓" , "warn" : "!" , "fail" : "✗" , "skip" : "-" }
211- for _ , c := range r .Checks {
212- fmt .Fprintf (w , "%s %s\n " , marks [c .Status ], c .Message )
213- if c .Remedy != "" {
214- fmt .Fprintf (w , " → %s\n " , c .Remedy )
211+ // shownError ends a command with exit code 1 after its output already
212+ // explained the failure, so no error block is printed below it.
213+ type shownError struct { error }
214+
215+ func (shownError ) Rendered () bool { return true }
216+ func (shownError ) ExitCode () int { return 1 }
217+ func (e shownError ) Unwrap () error { return e .error }
218+
219+ type palette struct { red , yellow , green , dim , bold , reset string }
220+
221+ func doctorColors (cmd * cobra.Command ) palette {
222+ flag , _ := flagutil .GetStringFlag (cmd , "color" )
223+ if flag == "" {
224+ flag = "auto"
225+ }
226+ if ! output .ShouldColorize (flag ) {
227+ return palette {}
228+ }
229+ return palette {red : "\x1b [31m" , yellow : "\x1b [33m" , green : "\x1b [32m" , dim : "\x1b [2m" , bold : "\x1b [1m" , reset : "\x1b [0m" }
230+ }
231+
232+ func countStatus (checks []check , status string ) int {
233+ n := 0
234+ for _ , c := range checks {
235+ if c .Status == status {
236+ n ++
215237 }
216238 }
217- if r .OK {
218- fmt .Fprintln (w , "\n All required checks passed." )
239+ return n
240+ }
241+
242+ // printDoctor lists the problems first, then the warnings, then what passed,
243+ // each with the command that fixes it, and ends with a one-line summary.
244+ func printDoctor (w io.Writer , r * doctorResult , p palette ) {
245+ groups := []struct {
246+ status , title , mark , color string
247+ }{
248+ {"fail" , "Problems" , "✗" , p .red },
249+ {"warn" , "Warnings" , "!" , p .yellow },
250+ {"skip" , "Not set up" , "-" , p .dim },
251+ {"ok" , "Passed" , "✓" , p .green },
252+ }
253+ first := true
254+ for _ , g := range groups {
255+ if countStatus (r .Checks , g .status ) == 0 {
256+ continue
257+ }
258+ if ! first {
259+ fmt .Fprintln (w )
260+ }
261+ first = false
262+ fmt .Fprintf (w , "%s%s%s\n " , p .bold , g .title , p .reset )
263+ for _ , c := range r .Checks {
264+ if c .Status != g .status {
265+ continue
266+ }
267+ fmt .Fprintf (w , " %s%s%s %s\n " , g .color , g .mark , p .reset , c .Message )
268+ if c .Remedy != "" && c .Status != "ok" {
269+ fmt .Fprintf (w , " %sFix:%s %s\n " , p .dim , p .reset , c .Remedy )
270+ }
271+ }
272+ }
273+
274+ failed , warned := countStatus (r .Checks , "fail" ), countStatus (r .Checks , "warn" )
275+ fmt .Fprintln (w )
276+ switch {
277+ case failed > 0 :
278+ fmt .Fprintf (w , "%s%s%s%s\n " , p .red , p .bold , summary (failed , warned ), p .reset )
279+ case warned > 0 :
280+ fmt .Fprintf (w , "%sReady to use. %s%s\n " , p .yellow , summary (0 , warned ), p .reset )
281+ default :
282+ fmt .Fprintf (w , "%sReady to use. All checks passed.%s\n " , p .green , p .reset )
283+ }
284+ }
285+
286+ func summary (failed , warned int ) string {
287+ plural := func (n int , word string ) string {
288+ if n == 1 {
289+ return fmt .Sprintf ("1 %s" , word )
290+ }
291+ return fmt .Sprintf ("%d %ss" , n , word )
292+ }
293+ switch {
294+ case failed > 0 && warned > 0 :
295+ return plural (failed , "problem" ) + ", " + plural (warned , "warning" ) + "."
296+ case failed > 0 :
297+ return plural (failed , "problem" ) + "."
298+ default :
299+ return plural (warned , "warning" ) + "."
219300 }
220301}
0 commit comments