Skip to content

Commit 8f85be0

Browse files
authored
feat(internal/cli): add version command (#2894)
Refactor command initialization, make `version` command as a default command in each command set. For #2416
1 parent 000d3a0 commit 8f85be0

File tree

8 files changed

+108
-50
lines changed

8 files changed

+108
-50
lines changed

cmd/automation/doc.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,13 @@ Flags:
5454
5555
-project string
5656
Google Cloud Platform project ID (default "cloud-sdk-librarian-prod")
57+
58+
# version
59+
60+
Version prints version information for the automation binary.
61+
62+
Usage:
63+
64+
automation version
5765
*/
5866
package main

internal/automation/automation.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,16 @@ import (
2121
)
2222

2323
func newAutomationCommand() *cli.Command {
24-
cmd := &cli.Command{
25-
Short: "automation manages Cloud Build resources to run Librarian CLI.",
26-
UsageLine: "automation <command> [arguments]",
27-
Long: automationLongHelp,
28-
Commands: []*cli.Command{
29-
newCmdGenerate(),
30-
newCmdPublishRelease(),
31-
},
24+
commands := []*cli.Command{
25+
newCmdGenerate(),
26+
newCmdPublishRelease(),
3227
}
3328

34-
cmd.Init()
35-
return cmd
29+
return cli.NewCommandSet(
30+
commands,
31+
"automation manages Cloud Build resources to run Librarian CLI.",
32+
"automation <command> [arguments]",
33+
automationLongHelp)
3634
}
3735

3836
func newCmdGenerate() *cli.Command {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package automation
16+
17+
import "testing"
18+
19+
func TestAutomationCmdRun(t *testing.T) {
20+
cmd := newAutomationCommand()
21+
if err := cmd.Run(t.Context(), []string{"version"}); err != nil {
22+
t.Fatal(err)
23+
}
24+
}

internal/automation/help.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,4 @@ any repository listed in internal/automation/prod/repositories.yaml.`
2020
generateLongHelp = `The generate command runs a Cloud Build job to generate Cloud Client Libraries.`
2121
publishLongHelp = `The publish-release command runs a Cloud Build job to create a tag on a merged release pull
2222
request.`
23-
versionLongHelp = "Version prints version information for the automation binary."
2423
)

internal/cli/cli.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,37 @@ type Command struct {
5353
Config *config.Config
5454
}
5555

56+
// NewCommandSet creates and initializes a root *Command object. It automatically appends a "version" subcommand to
57+
// the list.
58+
func NewCommandSet(commands []*Command, short, usageLine, long string) *Command {
59+
cmd := &Command{
60+
Short: short,
61+
UsageLine: usageLine,
62+
Long: long,
63+
}
64+
verifyCommandDocs(cmd)
65+
pkg := strings.Split(cmd.Short, " ")[0]
66+
cmd.Commands = append(make([]*Command, 0, len(commands)+1), commands...)
67+
cmd.Commands = append(cmd.Commands, newCmdVersion(pkg))
68+
69+
cmd.Init()
70+
return cmd
71+
}
72+
73+
func newCmdVersion(pkg string) *Command {
74+
cmdVersion := &Command{
75+
Short: "version prints the version information",
76+
UsageLine: fmt.Sprintf("%s version", pkg),
77+
Long: fmt.Sprintf("Version prints version information for the %s binary.", pkg),
78+
Action: func(ctx context.Context, cmd *Command) error {
79+
fmt.Println(Version())
80+
return nil
81+
},
82+
}
83+
cmdVersion.Init()
84+
return cmdVersion
85+
}
86+
5687
// Run executes the command with the provided arguments.
5788
func (c *Command) Run(ctx context.Context, args []string) error {
5889
cmd, remaining, err := lookupCommand(c, args)
@@ -87,10 +118,7 @@ func (c *Command) Name() string {
87118
}
88119

89120
func (c *Command) usage(w io.Writer) {
90-
if c.Short == "" || c.UsageLine == "" || c.Long == "" {
91-
panic(fmt.Sprintf("command %q is missing documentation", c.Name()))
92-
}
93-
121+
verifyCommandDocs(c)
94122
fmt.Fprintf(w, "%s\n\n", c.Long)
95123
fmt.Fprintf(w, "Usage:\n\n %s", c.UsageLine)
96124
if len(c.Commands) > 0 {
@@ -129,6 +157,12 @@ func hasFlags(fs *flag.FlagSet) bool {
129157
return visited
130158
}
131159

160+
func verifyCommandDocs(c *Command) {
161+
if c.Short == "" || c.UsageLine == "" || c.Long == "" {
162+
panic(fmt.Sprintf("command %q is missing documentation", c.Name()))
163+
}
164+
}
165+
132166
// lookupCommand looks up the command specified by the given arguments.
133167
// It returns the command, the remaining arguments, and an error if the command
134168
// is not found.

internal/cli/cli_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,3 +370,10 @@ func TestRun(t *testing.T) {
370370
})
371371
}
372372
}
373+
374+
func TestNewCommandSet(t *testing.T) {
375+
cmd := NewCommandSet(nil, "short", "usage", "long usageLine")
376+
if len(cmd.Commands) != 1 || cmd.Commands[0].Name() != "version" {
377+
t.Errorf("NewCommandSet(nil) did not produce a single 'version' command")
378+
}
379+
}

internal/librarian/help.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ package librarian
1717
const (
1818
librarianLongHelp = "Librarian manages client libraries for Google APIs."
1919

20-
versionLongHelp = "Version prints version information for the librarian binary."
21-
2220
releaseLongHelp = "Manages releases of libraries."
2321

2422
generateLongHelp = `The generate command is the primary tool for all code generation

internal/librarian/librarian.go

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,41 +41,17 @@ func setupLogger(verbose bool) {
4141
}
4242

4343
func newLibrarianCommand() *cli.Command {
44-
cmdVersion := &cli.Command{
45-
Short: "version prints the version information",
46-
UsageLine: "librarian version",
47-
Long: versionLongHelp,
48-
Action: func(ctx context.Context, cmd *cli.Command) error {
49-
fmt.Println(cli.Version())
50-
return nil
51-
},
52-
}
53-
cmdVersion.Init()
54-
55-
cmdRelease := &cli.Command{
56-
Short: "release manages releases of libraries.",
57-
UsageLine: "librarian release <command> [arguments]",
58-
Long: releaseLongHelp,
59-
Commands: []*cli.Command{
60-
newCmdStage(),
61-
newCmdTag(),
62-
},
44+
commands := []*cli.Command{
45+
newCmdGenerate(),
46+
newCmdRelease(),
47+
newCmdUpdateImage(),
6348
}
64-
cmdRelease.Init()
6549

66-
cmd := &cli.Command{
67-
Short: "librarian manages client libraries for Google APIs",
68-
UsageLine: "librarian <command> [arguments]",
69-
Long: librarianLongHelp,
70-
Commands: []*cli.Command{
71-
newCmdGenerate(),
72-
cmdRelease,
73-
newCmdUpdateImage(),
74-
cmdVersion,
75-
},
76-
}
77-
cmd.Init()
78-
return cmd
50+
return cli.NewCommandSet(
51+
commands,
52+
"librarian manages client libraries for Google APIs",
53+
"librarian <command> [arguments]",
54+
librarianLongHelp)
7955
}
8056

8157
func newCmdGenerate() *cli.Command {
@@ -116,6 +92,20 @@ func newCmdGenerate() *cli.Command {
11692
return cmdGenerate
11793
}
11894

95+
func newCmdRelease() *cli.Command {
96+
cmdRelease := &cli.Command{
97+
Short: "release manages releases of libraries.",
98+
UsageLine: "librarian release <command> [arguments]",
99+
Long: releaseLongHelp,
100+
Commands: []*cli.Command{
101+
newCmdStage(),
102+
newCmdTag(),
103+
},
104+
}
105+
cmdRelease.Init()
106+
return cmdRelease
107+
}
108+
119109
func newCmdTag() *cli.Command {
120110
var verbose bool
121111
cmdTag := &cli.Command{

0 commit comments

Comments
 (0)