From 00e97121ec8cf4de86bdda704fb711302a8dfc96 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Tue, 17 May 2022 23:35:47 -0700 Subject: [PATCH] Allow passing full extension name (#5464) --- pkg/cmd/extension/command.go | 88 ++++++++++++++++++++----------- pkg/cmd/extension/command_test.go | 33 ++++++++++++ 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/pkg/cmd/extension/command.go b/pkg/cmd/extension/command.go index 78f9f89bc..cefda803b 100644 --- a/pkg/cmd/extension/command.go +++ b/pkg/cmd/extension/command.go @@ -31,7 +31,8 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { executable of the same name. All arguments passed to the %[1]sgh %[1]s invocation will be forwarded to the %[1]sgh-%[1]s executable of the extension. - An extension cannot override any of the core gh commands. + An extension cannot override any of the core gh commands. If an extension name conflicts + with a core gh command you can use %[1]sgh extension exec %[1]s. See the list of available extensions at . `, "`"), @@ -79,21 +80,21 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { Use: "install ", Short: "Install a gh extension from a repository", Long: heredoc.Doc(` - Install a GitHub repository locally as a GitHub CLI extension. + Install a GitHub repository locally as a GitHub CLI extension. - The repository argument can be specified in "owner/repo" format as well as a full URL. - The URL format is useful when the repository is not hosted on github.com. + The repository argument can be specified in "owner/repo" format as well as a full URL. + The URL format is useful when the repository is not hosted on github.com. - To install an extension in development from the current directory, use "." as the - value of the repository argument. + To install an extension in development from the current directory, use "." as the + value of the repository argument. - See the list of available extensions at . - `), + See the list of available extensions at . + `), Example: heredoc.Doc(` - $ gh extension install owner/gh-extension - $ gh extension install https://git.example.com/owner/gh-extension - $ gh extension install . - `), + $ gh extension install owner/gh-extension + $ gh extension install https://git.example.com/owner/gh-extension + $ gh extension install . + `), Args: cmdutil.MinimumArgs(1, "must specify a repository to install from"), RunE: func(cmd *cobra.Command, args []string) error { if args[0] == "." { @@ -216,6 +217,31 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { return nil }, }, + &cobra.Command{ + Use: "exec [args]", + Short: "Execute an installed extension", + Long: heredoc.Doc(` + Execute an extension using the short name. For example, if the extension repository is + "owner/gh-extension", you should pass "extension". You can use this command when + the short name conflicts with a core gh command. + + All arguments after the extension name will be forwarded to the executable + of the extension. + `), + Example: heredoc.Doc(` + # execute a label extension instead of the core gh label command + $ gh extension exec label + `), + Args: cobra.MinimumNArgs(1), + DisableFlagParsing: true, + RunE: func(cmd *cobra.Command, args []string) error { + if found, err := m.Dispatch(args, io.In, io.Out, io.ErrOut); !found { + return fmt.Errorf("extension %q not found", args[0]) + } else { + return err + } + }, + }, func() *cobra.Command { promptCreate := func() (string, extensions.ExtTemplateType, error) { var extName string @@ -241,17 +267,17 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { Use: "create []", Short: "Create a new extension", Example: heredoc.Doc(` - # Use interactively - gh extension create + # Use interactively + gh extension create - # Create a script-based extension - gh extension create foobar + # Create a script-based extension + gh extension create foobar - # Create a Go extension - gh extension create --precompiled=go foobar + # Create a Go extension + gh extension create --precompiled=go foobar - # Create a non-Go precompiled extension - gh extension create --precompiled=other foobar + # Create a non-Go precompiled extension + gh extension create --precompiled=other foobar `), Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { @@ -317,19 +343,19 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { } link := "https://docs.github.com/github-cli/github-cli/creating-github-cli-extensions" out := heredoc.Docf(` - %[1]s Created directory %[2]s - %[1]s Initialized git repository - %[1]s Set up extension scaffolding - %[6]s - %[2]s is ready for development! + %[1]s Created directory %[2]s + %[1]s Initialized git repository + %[1]s Set up extension scaffolding + %[6]s + %[2]s is ready for development! - %[4]s - %[5]s - - commit and use 'gh repo create' to share your extension with others + %[4]s + %[5]s + - commit and use 'gh repo create' to share your extension with others - For more information on writing extensions: - %[3]s - `, cs.SuccessIcon(), fullName, link, cs.Bold("Next Steps"), steps, goBinChecks) + For more information on writing extensions: + %[3]s + `, cs.SuccessIcon(), fullName, link, cs.Bold("Next Steps"), steps, goBinChecks) fmt.Fprint(io.Out, out) return nil }, diff --git a/pkg/cmd/extension/command_test.go b/pkg/cmd/extension/command_test.go index d9a9e8677..fdf7588ef 100644 --- a/pkg/cmd/extension/command_test.go +++ b/pkg/cmd/extension/command_test.go @@ -2,6 +2,7 @@ package extension import ( "errors" + "fmt" "io" "net/http" "os" @@ -515,6 +516,38 @@ func TestNewCmdExtension(t *testing.T) { isTTY: false, wantStdout: "", }, + { + name: "exec extension missing", + args: []string{"exec", "invalid"}, + managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { + em.DispatchFunc = func(args []string, stdin io.Reader, stdout, stderr io.Writer) (bool, error) { + return false, nil + } + return func(t *testing.T) { + calls := em.DispatchCalls() + assert.Equal(t, 1, len(calls)) + assert.EqualValues(t, []string{"invalid"}, calls[0].Args) + } + }, + wantErr: true, + errMsg: `extension "invalid" not found`, + }, + { + name: "exec extension with arguments", + args: []string{"exec", "test", "arg1", "arg2", "--flag1"}, + managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { + em.DispatchFunc = func(args []string, stdin io.Reader, stdout, stderr io.Writer) (bool, error) { + fmt.Fprintf(stdout, "test output") + return true, nil + } + return func(t *testing.T) { + calls := em.DispatchCalls() + assert.Equal(t, 1, len(calls)) + assert.EqualValues(t, []string{"test", "arg1", "arg2", "--flag1"}, calls[0].Args) + } + }, + wantStdout: "test output", + }, } for _, tt := range tests {