From f0a1963a3ad133165deeea9330e6dc3a10960654 Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:38:44 -0700 Subject: [PATCH] feat: add version json output Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cmd/version.go | 17 +++++++++++++++-- cmd/version_test.go | 32 +++++++++++++++++++++++++++----- docs/version-json.md | 9 +++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 docs/version-json.md diff --git a/cmd/version.go b/cmd/version.go index 135e38b7..b2f42a03 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "github.com/jongio/grut/internal/config" @@ -8,13 +9,23 @@ import ( "github.com/spf13/cobra" ) +type versionInfo struct { + Version string `json:"version"` +} + // newVersionCmd creates the version command. func newVersionCmd() *cobra.Command { - return &cobra.Command{ + var jsonOutput bool + cmd := &cobra.Command{ Use: cmdVersion, Short: "Print the version of grut", Run: func(cmd *cobra.Command, args []string) { - fmt.Println(config.AppVersion) + if jsonOutput { + _ = json.NewEncoder(cmd.OutOrStdout()).Encode(versionInfo{Version: config.AppVersion}) + return + } + + fmt.Fprintln(cmd.OutOrStdout(), config.AppVersion) // Show update notification inline. if info := update.CheckForUpdate(config.AppVersion); info != nil { fmt.Fprintf(cmd.ErrOrStderr(), @@ -23,4 +34,6 @@ func newVersionCmd() *cobra.Command { } }, } + cmd.Flags().BoolVar(&jsonOutput, "json", false, "Print version information as JSON") + return cmd } diff --git a/cmd/version_test.go b/cmd/version_test.go index 8afbcee3..c6d2023a 100644 --- a/cmd/version_test.go +++ b/cmd/version_test.go @@ -1,6 +1,8 @@ package cmd import ( + "bytes" + "encoding/json" "testing" "github.com/jongio/grut/internal/config" @@ -13,17 +15,15 @@ import ( // --------------------------------------------------------------------------- func TestVersionCmd_OutputContainsAppVersion(t *testing.T) { - // The version command uses fmt.Println which writes to os.Stdout, not - // cmd.OutOrStdout(). We verify it runs without error and check the - // version string is set correctly via the root command's Version field. root, cleanup := buildRootCommand() defer cleanup() + var out bytes.Buffer + root.SetOut(&out) root.SetArgs([]string{"version"}) err := root.Execute() require.NoError(t, err) - // The root command's Version field is what --version and the version - // subcommand use. Verify it's set correctly. + assert.Contains(t, out.String(), config.AppVersion) assert.Equal(t, config.AppVersion, root.Version) } @@ -43,6 +43,28 @@ func TestVersionCmd_StructureHasCorrectUse(t *testing.T) { assert.Equal(t, "version", cmd.Use) } +func TestVersionCmd_JSONOutput(t *testing.T) { + cmd := newVersionCmd() + var out bytes.Buffer + cmd.SetOut(&out) + cmd.SetArgs([]string{"--json"}) + + err := cmd.Execute() + + require.NoError(t, err) + var got versionInfo + require.NoError(t, json.Unmarshal(out.Bytes(), &got)) + assert.Equal(t, config.AppVersion, got.Version) +} + +func TestVersionCmd_JSONFlagRegistered(t *testing.T) { + cmd := newVersionCmd() + flag := cmd.Flags().Lookup("json") + require.NotNil(t, flag) + assert.Equal(t, "bool", flag.Value.Type()) + assert.Equal(t, "false", flag.DefValue) +} + func TestVersionCmd_HasRunNotRunE(t *testing.T) { // version uses Run (not RunE) because it always succeeds. cmd := newVersionCmd() diff --git a/docs/version-json.md b/docs/version-json.md new file mode 100644 index 00000000..791d39df --- /dev/null +++ b/docs/version-json.md @@ -0,0 +1,9 @@ +# Version JSON + +Use JSON output when scripts need the installed grut version: + +```bash +grut version --json +``` + +The output contains a stable `version` field and does not include update notices.