byob-go-cli

version subcommand + --version flag on root

byob-release.2 command-shaperelease

Problem: users expect both mytool --version (a one-liner) and mytool version (a richer block showing commit, build date, Go version, OS/arch). Implementing them separately drifts. Implementing only --version fails the user who pastes the version subcommand into a bug report and wants the full block.

Idea: wire --version to cobra's built-in Version field (one-line format string); ship a version subcommand that prints the richer block. Both read from the same build.Info() accessor (byob-release.1) so they can't disagree.

Format:

mytool <version>                      # --version (short)

mytool <version>                      # mytool version (long)
  commit: <sha>
  built:  <iso-date>
  go:     <go-version>
  os:     <goos>/<goarch>

Tradeoffs: two code paths for the same data; kept trivially consistent by routing both to one formatter function. Worth the minor duplication for the UX win.

Design

// pkg/cmd/root/root.go — inside NewCmdRoot, after creating `root`:
root.Version = build.Info().Version
root.SetVersionTemplate("mytool {{.Version}}\n")
root.AddCommand(cmdversion.NewCmdVersion(f))

// pkg/cmd/version/version.go
func NewCmdVersion(f *Factory) *cobra.Command {
    return &cobra.Command{
        Use:   "version",
        Short: "Print version, commit, and build info",
        RunE: func(c *cobra.Command, args []string) error {
            info := build.Info()
            fmt.Fprintf(f.IOStreams.Out, "mytool %s\n", info.Version)
            fmt.Fprintf(f.IOStreams.Out, "  commit: %s\n", info.Commit)
            fmt.Fprintf(f.IOStreams.Out, "  built:  %s\n", info.Date)
            fmt.Fprintf(f.IOStreams.Out, "  go:     %s\n", runtime.Version())
            fmt.Fprintf(f.IOStreams.Out, "  os:     %s/%s\n", runtime.GOOS, runtime.GOARCH)
            return nil
        },
    }
}

version output goes to Out (not ErrOut): this is data a user would reasonably grep or include in a bug report.