Skip to content

Commit a508fee

Browse files
authored
Merge pull request cli#4158 from cli/extensions-ux
Enable `help` for extensions, accept full extension names as argument
2 parents c968342 + 51d6090 commit a508fee

File tree

3 files changed

+84
-6
lines changed

3 files changed

+84
-6
lines changed

cmd/gh/main.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,17 @@ func mainRun() exitCode {
102102
expandedArgs = os.Args[1:]
103103
}
104104

105-
cmd, _, err := rootCmd.Traverse(expandedArgs)
106-
if err != nil || cmd == rootCmd {
105+
// translate `gh help <command>` to `gh <command> --help` for extensions
106+
if len(expandedArgs) == 2 && expandedArgs[0] == "help" && !hasCommand(rootCmd, expandedArgs[1:]) {
107+
expandedArgs = []string{expandedArgs[1], "--help"}
108+
}
109+
110+
if !hasCommand(rootCmd, expandedArgs) {
107111
originalArgs := expandedArgs
108112
isShell := false
109113

110-
expandedArgs, isShell, err = expand.ExpandAlias(cfg, os.Args, nil)
114+
argsForExpansion := append([]string{"gh"}, expandedArgs...)
115+
expandedArgs, isShell, err = expand.ExpandAlias(cfg, argsForExpansion, nil)
111116
if err != nil {
112117
fmt.Fprintf(stderr, "failed to process aliases: %s\n", err)
113118
return exitError
@@ -141,7 +146,7 @@ func mainRun() exitCode {
141146
}
142147

143148
return exitOK
144-
} else if c, _, err := rootCmd.Traverse(expandedArgs); err == nil && c == rootCmd && len(expandedArgs) > 0 {
149+
} else if len(expandedArgs) > 0 && !hasCommand(rootCmd, expandedArgs) {
145150
extensionManager := cmdFactory.ExtensionManager
146151
if found, err := extensionManager.Dispatch(expandedArgs, os.Stdin, os.Stdout, os.Stderr); err != nil {
147152
var execError *exec.ExitError
@@ -244,6 +249,12 @@ func mainRun() exitCode {
244249
return exitOK
245250
}
246251

252+
// hasCommand returns true if args resolve to a built-in command
253+
func hasCommand(rootCmd *cobra.Command, args []string) bool {
254+
c, _, err := rootCmd.Traverse(args)
255+
return err == nil && c != rootCmd
256+
}
257+
247258
func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) {
248259
var dnsError *net.DNSError
249260
if errors.As(err, &dnsError) {

pkg/cmd/extension/command.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command {
116116
RunE: func(cmd *cobra.Command, args []string) error {
117117
var name string
118118
if len(args) > 0 {
119-
name = args[0]
119+
name = normalizeExtensionSelector(args[0])
120120
}
121121
return m.Upgrade(name, flagForce, io.Out, io.ErrOut)
122122
},
@@ -130,7 +130,7 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command {
130130
Short: "Remove an installed extension",
131131
Args: cobra.ExactArgs(1),
132132
RunE: func(cmd *cobra.Command, args []string) error {
133-
extName := args[0]
133+
extName := normalizeExtensionSelector(args[0])
134134
if err := m.Remove(extName); err != nil {
135135
return err
136136
}
@@ -167,3 +167,10 @@ func checkValidExtension(rootCmd *cobra.Command, m extensions.ExtensionManager,
167167

168168
return nil
169169
}
170+
171+
func normalizeExtensionSelector(n string) string {
172+
if idx := strings.IndexRune(n, '/'); idx >= 0 {
173+
n = n[idx+1:]
174+
}
175+
return strings.TrimPrefix(n, "gh-")
176+
}

pkg/cmd/extension/command_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,34 @@ func TestNewCmdExtension(t *testing.T) {
100100
}
101101
},
102102
},
103+
{
104+
name: "upgrade an extension gh-prefix",
105+
args: []string{"upgrade", "gh-hello"},
106+
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
107+
em.UpgradeFunc = func(name string, force bool, out, errOut io.Writer) error {
108+
return nil
109+
}
110+
return func(t *testing.T) {
111+
calls := em.UpgradeCalls()
112+
assert.Equal(t, 1, len(calls))
113+
assert.Equal(t, "hello", calls[0].Name)
114+
}
115+
},
116+
},
117+
{
118+
name: "upgrade an extension full name",
119+
args: []string{"upgrade", "monalisa/gh-hello"},
120+
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
121+
em.UpgradeFunc = func(name string, force bool, out, errOut io.Writer) error {
122+
return nil
123+
}
124+
return func(t *testing.T) {
125+
calls := em.UpgradeCalls()
126+
assert.Equal(t, 1, len(calls))
127+
assert.Equal(t, "hello", calls[0].Name)
128+
}
129+
},
130+
},
103131
{
104132
name: "upgrade all",
105133
args: []string{"upgrade", "--all"},
@@ -146,6 +174,38 @@ func TestNewCmdExtension(t *testing.T) {
146174
isTTY: false,
147175
wantStdout: "",
148176
},
177+
{
178+
name: "remove extension gh-prefix",
179+
args: []string{"remove", "gh-hello"},
180+
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
181+
em.RemoveFunc = func(name string) error {
182+
return nil
183+
}
184+
return func(t *testing.T) {
185+
calls := em.RemoveCalls()
186+
assert.Equal(t, 1, len(calls))
187+
assert.Equal(t, "hello", calls[0].Name)
188+
}
189+
},
190+
isTTY: false,
191+
wantStdout: "",
192+
},
193+
{
194+
name: "remove extension full name",
195+
args: []string{"remove", "monalisa/gh-hello"},
196+
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
197+
em.RemoveFunc = func(name string) error {
198+
return nil
199+
}
200+
return func(t *testing.T) {
201+
calls := em.RemoveCalls()
202+
assert.Equal(t, 1, len(calls))
203+
assert.Equal(t, "hello", calls[0].Name)
204+
}
205+
},
206+
isTTY: false,
207+
wantStdout: "",
208+
},
149209
{
150210
name: "list extensions",
151211
args: []string{"list"},

0 commit comments

Comments
 (0)