fix(cli): -h/--help exits 0 instead of error code

parseDoctorArgs hand-rolled its own flag loop and surfaced help text
as an error; parseTestArgs used flag.ContinueOnError but propagated
flag.ErrHelp to main() which printed "error: flag: help requested"
and exited 1.

Switch parseDoctorArgs to flag.NewFlagSet matching parseTestArgs, then
recognise flag.ErrHelp in main() so all subcommands exit 0 on -h.
This commit is contained in:
pj committed 2026-05-03 10:57:13 +07:00
1 parent 65adfb9b2e
commit c55925d550
3 files changed
+31 -28

No files matched your search

+8 -18
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"flag"
"fmt" "fmt"
"io" "io"
"os" "os"
@@ -111,24 +112,13 @@ type doctorOptions struct {
platform string platform string
} }
func parseDoctorArgs(args []string) (doctorOptions, error) { func parseDoctorArgs(args []string, stderr io.Writer) (doctorOptions, error) {
options := doctorOptions{platform: "all"} flagSet := flag.NewFlagSet("doctor", flag.ContinueOnError)
for index := 0; index < len(args); index++ { flagSet.SetOutput(stderr)
argument := args[index] var options doctorOptions
switch { flagSet.StringVar(&options.platform, "platform", "all", "target platform: web, android, ios, all")
case argument == "--platform": if err := flagSet.Parse(args); err != nil {
index++ return doctorOptions{}, err
if index >= len(args) {
return doctorOptions{}, fmt.Errorf("--platform requires a value")
}
options.platform = args[index]
case len(argument) > len("--platform=") && argument[:len("--platform=")] == "--platform=":
options.platform = argument[len("--platform="):]
case argument == "-h" || argument == "--help":
return doctorOptions{}, fmt.Errorf("doctor [--platform=web|android|ios|all]")
default:
return doctorOptions{}, fmt.Errorf("unknown doctor argument: %q", argument)
}
} }
switch options.platform { switch options.platform {
case "web", "android", "ios", "all": case "web", "android", "ios", "all":
+15 -4
View File
@@ -4,6 +4,8 @@ import (
"bytes" "bytes"
"context" "context"
"errors" "errors"
"flag"
"io"
"strings" "strings"
"testing" "testing"
) )
@@ -139,7 +141,7 @@ func TestDoctorChecksFor_UnknownPlatform(t *testing.T) {
} }
func TestParseDoctorArgs_DefaultAll(t *testing.T) { func TestParseDoctorArgs_DefaultAll(t *testing.T) {
options, err := parseDoctorArgs(nil) options, err := parseDoctorArgs(nil, io.Discard)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -153,7 +155,7 @@ func TestParseDoctorArgs_ExplicitPlatform(t *testing.T) {
{"--platform", "web"}, {"--platform", "web"},
{"--platform=web"}, {"--platform=web"},
} { } {
options, err := parseDoctorArgs(form) options, err := parseDoctorArgs(form, io.Discard)
if err != nil { if err != nil {
t.Fatalf("%v: %v", form, err) t.Fatalf("%v: %v", form, err)
} }
@@ -164,10 +166,19 @@ func TestParseDoctorArgs_ExplicitPlatform(t *testing.T) {
} }
func TestParseDoctorArgs_RejectsUnknown(t *testing.T) { func TestParseDoctorArgs_RejectsUnknown(t *testing.T) {
if _, err := parseDoctorArgs([]string{"--platform=fuchsia"}); err == nil { if _, err := parseDoctorArgs([]string{"--platform=fuchsia"}, io.Discard); err == nil {
t.Error("expected error for unsupported platform") t.Error("expected error for unsupported platform")
} }
if _, err := parseDoctorArgs([]string{"--bogus"}); err == nil { if _, err := parseDoctorArgs([]string{"--bogus"}, io.Discard); err == nil {
t.Error("expected error for unknown argument") t.Error("expected error for unknown argument")
} }
} }
func TestParseDoctorArgs_HelpReturnsErrHelp(t *testing.T) {
if _, err := parseDoctorArgs([]string{"-h"}, io.Discard); !errors.Is(err, flag.ErrHelp) {
t.Errorf("expected flag.ErrHelp for -h, got %v", err)
}
if _, err := parseDoctorArgs([]string{"--help"}, io.Discard); !errors.Is(err, flag.ErrHelp) {
t.Errorf("expected flag.ErrHelp for --help, got %v", err)
}
}
+8 -6
View File
@@ -76,15 +76,12 @@ func runTest(options testOptions, stdout io.Writer) error {
return runTestPipeline(ctx, options, stdout) return runTestPipeline(ctx, options, stdout)
} }
func runDoctor(args []string, stdout io.Writer) error { func runDoctor(args []string, stdout, stderr io.Writer) error {
options, err := parseDoctorArgs(args) options, err := parseDoctorArgs(args, stderr)
if err != nil { if err != nil {
return err return err
} }
checks := doctorChecksFor(options.platform) checks := doctorChecksFor(options.platform)
if checks == nil {
return fmt.Errorf("no checks for platform %q", options.platform)
}
return runDoctorChecks(context.Background(), checks, stdout) return runDoctorChecks(context.Background(), checks, stdout)
} }
@@ -107,7 +104,7 @@ func run(args []string, stdout, stderr io.Writer) error {
} }
return runInspect(options, stdout) return runInspect(options, stdout)
case "doctor": case "doctor":
return runDoctor(args[2:], stdout) return runDoctor(args[2:], stdout, stderr)
case "version", "-v", "--version": case "version", "-v", "--version":
fmt.Fprintln(stdout, Version) fmt.Fprintln(stdout, Version)
return nil return nil
@@ -118,6 +115,11 @@ func run(args []string, stdout, stderr io.Writer) error {
func main() { func main() {
if err := run(os.Args, os.Stdout, os.Stderr); err != nil { if err := run(os.Args, os.Stdout, os.Stderr); err != nil {
// flag.ErrHelp means -h/--help was requested; flag already printed
// usage to stderr, so exit 0 rather than treating it as a failure.
if errors.Is(err, flag.ErrHelp) {
return
}
fmt.Fprintf(os.Stderr, "error: %v\n", err) fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1) os.Exit(1)
} }