diff --git a/pkg/cmd/extension/command.go b/pkg/cmd/extension/command.go index 3d5215829..ad92a8f14 100644 --- a/pkg/cmd/extension/command.go +++ b/pkg/cmd/extension/command.go @@ -101,16 +101,12 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { if err != nil { return err } + if err := checkValidExtension(cmd.Root(), m, repo.RepoName()); err != nil { return err } - cfg, err := f.Config() - if err != nil { - return err - } - protocol, _ := cfg.Get(repo.RepoHost(), "git_protocol") - return m.Install(ghrepo.FormatRemoteURL(repo, protocol), io.Out, io.ErrOut) + return m.Install(repo) }, }, func() *cobra.Command { diff --git a/pkg/cmd/extension/command_test.go b/pkg/cmd/extension/command_test.go index 9dc46b3ba..fac5d0e9a 100644 --- a/pkg/cmd/extension/command_test.go +++ b/pkg/cmd/extension/command_test.go @@ -3,14 +3,17 @@ package extension import ( "io" "io/ioutil" + "net/http" "os" "strings" "testing" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/extensions" + "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" @@ -39,13 +42,13 @@ func TestNewCmdExtension(t *testing.T) { em.ListFunc = func(bool) []extensions.Extension { return []extensions.Extension{} } - em.InstallFunc = func(s string, out, errOut io.Writer) error { + em.InstallFunc = func(_ ghrepo.Interface) error { return nil } return func(t *testing.T) { installCalls := em.InstallCalls() assert.Equal(t, 1, len(installCalls)) - assert.Equal(t, "https://github.com/owner/gh-some-ext.git", installCalls[0].URL) + assert.Equal(t, "gh-some-ext", installCalls[0].InterfaceMoqParam.RepoName()) listCalls := em.ListCalls() assert.Equal(t, 1, len(listCalls)) } @@ -281,12 +284,19 @@ func TestNewCmdExtension(t *testing.T) { assertFunc = tt.managerStubs(em) } + reg := httpmock.Registry{} + defer reg.Verify(t) + client := http.Client{Transport: ®} + f := cmdutil.Factory{ Config: func() (config.Config, error) { return config.NewBlankConfig(), nil }, IOStreams: ios, ExtensionManager: em, + HttpClient: func() (*http.Client, error) { + return &client, nil + }, } cmd := NewCmdExtension(&f) diff --git a/pkg/cmd/extension/http.go b/pkg/cmd/extension/http.go new file mode 100644 index 000000000..6f93f2303 --- /dev/null +++ b/pkg/cmd/extension/http.go @@ -0,0 +1,114 @@ +package extension + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + + "github.com/cli/cli/v2/api" + "github.com/cli/cli/v2/internal/ghinstance" + "github.com/cli/cli/v2/internal/ghrepo" +) + +func hasScript(httpClient *http.Client, repo ghrepo.Interface) (hs bool, err error) { + path := fmt.Sprintf("repos/%s/%s/contents/%s", + repo.RepoOwner(), repo.RepoName(), repo.RepoName()) + url := ghinstance.RESTPrefix(repo.RepoHost()) + path + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + + resp, err := httpClient.Do(req) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode == 404 { + return + } + + if resp.StatusCode > 299 { + err = api.HandleHTTPError(resp) + return + } + + hs = true + return +} + +type releaseAsset struct { + Name string + APIURL string `json:"url"` +} + +type release struct { + Tag string `json:"tag_name"` + Assets []releaseAsset +} + +// downloadAsset downloads a single asset to the given file path. +func downloadAsset(httpClient *http.Client, asset releaseAsset, destPath string) error { + req, err := http.NewRequest("GET", asset.APIURL, nil) + if err != nil { + return err + } + + req.Header.Set("Accept", "application/octet-stream") + + resp, err := httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode > 299 { + return api.HandleHTTPError(resp) + } + + f, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0755) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(f, resp.Body) + return err +} + +// fetchLatestRelease finds the latest published release for a repository. +func fetchLatestRelease(httpClient *http.Client, baseRepo ghrepo.Interface) (*release, error) { + path := fmt.Sprintf("repos/%s/%s/releases/latest", baseRepo.RepoOwner(), baseRepo.RepoName()) + url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode > 299 { + return nil, api.HandleHTTPError(resp) + } + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var r release + err = json.Unmarshal(b, &r) + if err != nil { + return nil, err + } + + return &r, nil +} diff --git a/pkg/cmd/extension/manager.go b/pkg/cmd/extension/manager.go index b488704e5..7e1f403e2 100644 --- a/pkg/cmd/extension/manager.go +++ b/pkg/cmd/extension/manager.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" "os/exec" "path" @@ -14,10 +15,14 @@ import ( "strings" "github.com/MakeNowJust/heredoc" + "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/extensions" "github.com/cli/cli/v2/pkg/findsh" + "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/safeexec" + "gopkg.in/yaml.v3" ) type Manager struct { @@ -25,17 +30,32 @@ type Manager struct { lookPath func(string) (string, error) findSh func() (string, error) newCommand func(string, ...string) *exec.Cmd + platform func() string + client *http.Client + config config.Config + io *iostreams.IOStreams } -func NewManager() *Manager { +func NewManager(io *iostreams.IOStreams) *Manager { return &Manager{ dataDir: config.DataDir, lookPath: safeexec.LookPath, findSh: findsh.Find, newCommand: exec.Command, + platform: func() string { + return fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH) + }, } } +func (m *Manager) SetConfig(cfg config.Config) { + m.config = cfg +} + +func (m *Manager) SetClient(client *http.Client) { + m.client = client +} + func (m *Manager) Dispatch(args []string, stdin io.Reader, stdout, stderr io.Writer) (bool, error) { if len(args) == 0 { return false, errors.New("too few arguments in list") @@ -172,7 +192,104 @@ func (m *Manager) InstallLocal(dir string) error { return makeSymlink(dir, targetLink) } -func (m *Manager) Install(cloneURL string, stdout, stderr io.Writer) error { +type binManifest struct { + Owner string + Name string + Host string + Tag string + // TODO I may end up not using this; just thinking ahead to local installs + Path string +} + +func (m *Manager) Install(repo ghrepo.Interface) error { + isBin, err := isBinExtension(m.client, repo) + if err != nil { + return fmt.Errorf("could not check for binary extension: %w", err) + } + if isBin { + return m.installBin(repo) + } + + hs, err := hasScript(m.client, repo) + if err != nil { + return err + } + if !hs { + // TODO open an issue hint, here? + return errors.New("extension is uninstallable: missing executable") + } + + protocol, _ := m.config.Get(repo.RepoHost(), "git_protocol") + return m.installGit(ghrepo.FormatRemoteURL(repo, protocol), m.io.Out, m.io.ErrOut) +} + +func (m *Manager) installBin(repo ghrepo.Interface) error { + var r *release + r, err := fetchLatestRelease(m.client, repo) + if err != nil { + return err + } + + suffix := m.platform() + var asset *releaseAsset + for _, a := range r.Assets { + if strings.HasSuffix(a.Name, suffix) { + asset = &a + break + } + } + + if asset == nil { + return fmt.Errorf("%s unsupported for %s. Open an issue: `gh issue create -R %s/%s -t'Support %s'`", + repo.RepoName(), + suffix, repo.RepoOwner(), repo.RepoName(), suffix) + } + + name := repo.RepoName() + targetDir := filepath.Join(m.installDir(), name) + // TODO clean this up if function errs? + err = os.MkdirAll(targetDir, 0755) + if err != nil { + return fmt.Errorf("failed to create installation directory: %w", err) + } + + binPath := filepath.Join(targetDir, name) + + err = downloadAsset(m.client, *asset, binPath) + if err != nil { + return fmt.Errorf("failed to download asset %s: %w", asset.Name, err) + } + + manifest := binManifest{ + Name: name, + Owner: repo.RepoOwner(), + Host: repo.RepoHost(), + Path: binPath, + Tag: r.Tag, + } + + bs, err := yaml.Marshal(manifest) + if err != nil { + return fmt.Errorf("failed to serialize manifest: %w", err) + } + + manifestPath := filepath.Join(targetDir, "manifest.yml") + + f, err := os.OpenFile(manifestPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("failed to open manifest for writing: %w", err) + } + defer f.Close() + + _, err = f.Write(bs) + if err != nil { + return fmt.Errorf("failed write manifest file: %w", err) + } + + return nil +} + +func (m *Manager) installGit(cloneURL string, stdout, stderr io.Writer) error { exe, err := m.lookPath("git") if err != nil { return err @@ -346,3 +463,77 @@ func readPathFromFile(path string) (string, error) { n, err := f.Read(b) return strings.TrimSpace(string(b[:n])), err } + +func isBinExtension(client *http.Client, repo ghrepo.Interface) (isBin bool, err error) { + var r *release + r, err = fetchLatestRelease(client, repo) + if err != nil { + httpErr, ok := err.(api.HTTPError) + if ok && httpErr.StatusCode == 404 { + err = nil + return + } + return + } + + for _, a := range r.Assets { + dists := possibleDists() + for _, d := range dists { + if strings.HasSuffix(a.Name, d) { + isBin = true + break + } + } + } + + return +} + +func possibleDists() []string { + return []string{ + "aix-ppc64", + "android-386", + "android-amd64", + "android-arm", + "android-arm64", + "darwin-amd64", + "darwin-arm64", + "dragonfly-amd64", + "freebsd-386", + "freebsd-amd64", + "freebsd-arm", + "freebsd-arm64", + "illumos-amd64", + "ios-amd64", + "ios-arm64", + "js-wasm", + "linux-386", + "linux-amd64", + "linux-arm", + "linux-arm64", + "linux-mips", + "linux-mips64", + "linux-mips64le", + "linux-mipsle", + "linux-ppc64", + "linux-ppc64le", + "linux-riscv64", + "linux-s390x", + "netbsd-386", + "netbsd-amd64", + "netbsd-arm", + "netbsd-arm64", + "openbsd-386", + "openbsd-amd64", + "openbsd-arm", + "openbsd-arm64", + "openbsd-mips64", + "plan9-386", + "plan9-amd64", + "plan9-arm", + "solaris-amd64", + "windows-386", + "windows-amd64", + "windows-arm", + } +} diff --git a/pkg/cmd/extension/manager_test.go b/pkg/cmd/extension/manager_test.go index 2fd458bf1..d78f4c2e6 100644 --- a/pkg/cmd/extension/manager_test.go +++ b/pkg/cmd/extension/manager_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io/ioutil" + "net/http" "os" "os/exec" "path/filepath" @@ -11,7 +12,12 @@ import ( "testing" "github.com/MakeNowJust/heredoc" + "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/pkg/httpmock" + "github.com/cli/cli/v2/pkg/iostreams" "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" ) func TestHelperProcess(t *testing.T) { @@ -28,7 +34,7 @@ func TestHelperProcess(t *testing.T) { os.Exit(0) } -func newTestManager(dir string) *Manager { +func newTestManager(dir string, client *http.Client, io *iostreams.IOStreams) *Manager { return &Manager{ dataDir: func() string { return dir }, lookPath: func(exe string) (string, error) { return exe, nil }, @@ -39,6 +45,12 @@ func newTestManager(dir string) *Manager { cmd.Env = []string{"GH_WANT_HELPER_PROCESS=1"} return cmd }, + config: config.NewBlankConfig(), + io: io, + client: client, + platform: func() string { + return "windows-amd64" + }, } } @@ -47,7 +59,7 @@ func TestManager_List(t *testing.T) { assert.NoError(t, stubExtension(filepath.Join(tempDir, "extensions", "gh-hello", "gh-hello"))) assert.NoError(t, stubExtension(filepath.Join(tempDir, "extensions", "gh-two", "gh-two"))) - m := newTestManager(tempDir) + m := newTestManager(tempDir, nil, nil) exts := m.List(false) assert.Equal(t, 2, len(exts)) assert.Equal(t, "hello", exts[0].Name()) @@ -59,7 +71,7 @@ func TestManager_Dispatch(t *testing.T) { extPath := filepath.Join(tempDir, "extensions", "gh-hello", "gh-hello") assert.NoError(t, stubExtension(extPath)) - m := newTestManager(tempDir) + m := newTestManager(tempDir, nil, nil) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} @@ -80,7 +92,7 @@ func TestManager_Remove(t *testing.T) { assert.NoError(t, stubExtension(filepath.Join(tempDir, "extensions", "gh-hello", "gh-hello"))) assert.NoError(t, stubExtension(filepath.Join(tempDir, "extensions", "gh-two", "gh-two"))) - m := newTestManager(tempDir) + m := newTestManager(tempDir, nil, nil) err := m.Remove("hello") assert.NoError(t, err) @@ -96,7 +108,7 @@ func TestManager_Upgrade_AllExtensions(t *testing.T) { assert.NoError(t, stubExtension(filepath.Join(tempDir, "extensions", "gh-two", "gh-two"))) assert.NoError(t, stubLocalExtension(tempDir, filepath.Join(tempDir, "extensions", "gh-local", "gh-local"))) - m := newTestManager(tempDir) + m := newTestManager(tempDir, nil, nil) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} @@ -121,7 +133,7 @@ func TestManager_Upgrade_RemoteExtension(t *testing.T) { tempDir := t.TempDir() assert.NoError(t, stubExtension(filepath.Join(tempDir, "extensions", "gh-remote", "gh-remote"))) - m := newTestManager(tempDir) + m := newTestManager(tempDir, nil, nil) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} @@ -141,7 +153,7 @@ func TestManager_Upgrade_LocalExtension(t *testing.T) { tempDir := t.TempDir() assert.NoError(t, stubLocalExtension(tempDir, filepath.Join(tempDir, "extensions", "gh-local", "gh-local"))) - m := newTestManager(tempDir) + m := newTestManager(tempDir, nil, nil) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} @@ -158,7 +170,7 @@ func TestManager_Upgrade_Force(t *testing.T) { assert.NoError(t, stubExtension(filepath.Join(tempDir, "extensions", "gh-remote", "gh-remote"))) - m := newTestManager(tempDir) + m := newTestManager(tempDir, nil, nil) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} @@ -180,7 +192,7 @@ func TestManager_Upgrade_Force(t *testing.T) { func TestManager_Upgrade_NoExtensions(t *testing.T) { tempDir := t.TempDir() - m := newTestManager(tempDir) + m := newTestManager(tempDir, nil, nil) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} @@ -190,24 +202,153 @@ func TestManager_Upgrade_NoExtensions(t *testing.T) { assert.Equal(t, "", stderr.String()) } -func TestManager_Install(t *testing.T) { +func TestManager_Install_git(t *testing.T) { tempDir := t.TempDir() - m := newTestManager(tempDir) - stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} - err := m.Install("https://github.com/owner/gh-some-ext.git", stdout, stderr) + reg := httpmock.Registry{} + defer reg.Verify(t) + client := http.Client{Transport: ®} + + io, _, stdout, stderr := iostreams.Test() + + m := newTestManager(tempDir, &client, io) + + reg.Register( + httpmock.REST("GET", "repos/owner/gh-some-ext/releases/latest"), + httpmock.JSONResponse( + release{ + Assets: []releaseAsset{ + { + Name: "not-a-binary", + APIURL: "https://example.com/release/cool", + }, + }, + })) + reg.Register( + httpmock.REST("GET", "repos/owner/gh-some-ext/contents/gh-some-ext"), + httpmock.StringResponse("script")) + + repo := ghrepo.New("owner", "gh-some-ext") + + err := m.Install(repo) assert.NoError(t, err) assert.Equal(t, fmt.Sprintf("[git clone https://github.com/owner/gh-some-ext.git %s]\n", filepath.Join(tempDir, "extensions", "gh-some-ext")), stdout.String()) assert.Equal(t, "", stderr.String()) } +func TestManager_Install_binary_unsupported(t *testing.T) { + repo := ghrepo.NewWithHost("owner", "gh-bin-ext", "example.com") + + reg := httpmock.Registry{} + defer reg.Verify(t) + client := http.Client{Transport: ®} + + reg.Register( + httpmock.REST("GET", "api/v3/repos/owner/gh-bin-ext/releases/latest"), + httpmock.JSONResponse( + release{ + Assets: []releaseAsset{ + { + Name: "gh-bin-ext-linux-amd64", + APIURL: "https://example.com/release/cool", + }, + }, + })) + reg.Register( + httpmock.REST("GET", "api/v3/repos/owner/gh-bin-ext/releases/latest"), + httpmock.JSONResponse( + release{ + Tag: "v1.0.1", + Assets: []releaseAsset{ + { + Name: "gh-bin-ext-linux-amd64", + APIURL: "https://example.com/release/cool", + }, + }, + })) + + io, _, _, _ := iostreams.Test() + tempDir := t.TempDir() + + m := newTestManager(tempDir, &client, io) + + err := m.Install(repo) + assert.Error(t, err) + + errText := "gh-bin-ext unsupported for windows-amd64. Open an issue: `gh issue create -R owner/gh-bin-ext -t'Support windows-amd64'`" + + assert.Equal(t, errText, err.Error()) +} + +func TestManager_Install_binary(t *testing.T) { + repo := ghrepo.NewWithHost("owner", "gh-bin-ext", "example.com") + + reg := httpmock.Registry{} + defer reg.Verify(t) + client := http.Client{Transport: ®} + + reg.Register( + httpmock.REST("GET", "api/v3/repos/owner/gh-bin-ext/releases/latest"), + httpmock.JSONResponse( + release{ + Assets: []releaseAsset{ + { + Name: "gh-bin-ext-windows-amd64", + APIURL: "https://example.com/release/cool", + }, + }, + })) + reg.Register( + httpmock.REST("GET", "api/v3/repos/owner/gh-bin-ext/releases/latest"), + httpmock.JSONResponse( + release{ + Tag: "v1.0.1", + Assets: []releaseAsset{ + { + Name: "gh-bin-ext-windows-amd64", + APIURL: "https://example.com/release/cool", + }, + }, + })) + reg.Register( + httpmock.REST("GET", "release/cool"), + httpmock.StringResponse("FAKE BINARY")) + + io, _, _, _ := iostreams.Test() + tempDir := t.TempDir() + + m := newTestManager(tempDir, &client, io) + + err := m.Install(repo) + assert.NoError(t, err) + + manifest, err := os.ReadFile(filepath.Join(tempDir, "extensions/gh-bin-ext/manifest.yml")) + assert.NoError(t, err) + + var bm binManifest + err = yaml.Unmarshal(manifest, &bm) + assert.NoError(t, err) + + assert.Equal(t, binManifest{ + Name: "gh-bin-ext", + Owner: "owner", + Host: "example.com", + Tag: "v1.0.1", + Path: filepath.Join(tempDir, "extensions/gh-bin-ext/gh-bin-ext"), + }, bm) + + fakeBin, err := os.ReadFile(filepath.Join(tempDir, "extensions/gh-bin-ext/gh-bin-ext")) + assert.NoError(t, err) + + assert.Equal(t, "FAKE BINARY", string(fakeBin)) +} + func TestManager_Create(t *testing.T) { tempDir := t.TempDir() oldWd, _ := os.Getwd() assert.NoError(t, os.Chdir(tempDir)) t.Cleanup(func() { _ = os.Chdir(oldWd) }) - m := newTestManager(tempDir) + m := newTestManager(tempDir, nil, nil) err := m.Create("gh-test") assert.NoError(t, err) files, err := ioutil.ReadDir(filepath.Join(tempDir, "gh-test")) diff --git a/pkg/cmd/factory/default.go b/pkg/cmd/factory/default.go index 961883b12..61bbe2c54 100644 --- a/pkg/cmd/factory/default.go +++ b/pkg/cmd/factory/default.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "os" + "time" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/context" @@ -22,7 +23,6 @@ func New(appVersion string) *cmdutil.Factory { Branch: branchFunc(), // No factory dependencies Executable: executable(), // No factory dependencies - ExtensionManager: extension.NewManager(), } f.IOStreams = ioStreams(f) // Depends on Config @@ -30,6 +30,7 @@ func New(appVersion string) *cmdutil.Factory { f.Remotes = remotesFunc(f) // Depends on Config f.BaseRepo = BaseRepoFunc(f) // Depends on Remotes f.Browser = browser(f) // Depends on Config, and IOStreams + f.ExtensionManager = extensionManager(f) // Depends on Config, HttpClient, and IOStreams return f } @@ -148,6 +149,25 @@ func branchFunc() func() (string, error) { } } +func extensionManager(f *cmdutil.Factory) *extension.Manager { + em := extension.NewManager(f.IOStreams) + + cfg, err := f.Config() + if err != nil { + return em + } + em.SetConfig(cfg) + + client, err := f.HttpClient() + if err != nil { + return em + } + + em.SetClient(api.NewCachedClient(client, time.Second*30)) + + return em +} + func ioStreams(f *cmdutil.Factory) *iostreams.IOStreams { io := iostreams.System() cfg, err := f.Config() diff --git a/pkg/extensions/extension.go b/pkg/extensions/extension.go index 54379ec7f..4e9ce89b5 100644 --- a/pkg/extensions/extension.go +++ b/pkg/extensions/extension.go @@ -2,6 +2,8 @@ package extensions import ( "io" + + "github.com/cli/cli/v2/internal/ghrepo" ) //go:generate moq -rm -out extension_mock.go . Extension @@ -16,7 +18,7 @@ type Extension interface { //go:generate moq -rm -out manager_mock.go . ExtensionManager type ExtensionManager interface { List(includeMetadata bool) []Extension - Install(url string, stdout, stderr io.Writer) error + Install(ghrepo.Interface) error InstallLocal(dir string) error Upgrade(name string, force bool, stdout, stderr io.Writer) error Remove(name string) error diff --git a/pkg/extensions/manager_mock.go b/pkg/extensions/manager_mock.go index 157262698..96d76cdf7 100644 --- a/pkg/extensions/manager_mock.go +++ b/pkg/extensions/manager_mock.go @@ -4,6 +4,7 @@ package extensions import ( + "github.com/cli/cli/v2/internal/ghrepo" "io" "sync" ) @@ -24,7 +25,7 @@ var _ ExtensionManager = &ExtensionManagerMock{} // DispatchFunc: func(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (bool, error) { // panic("mock out the Dispatch method") // }, -// InstallFunc: func(url string, stdout io.Writer, stderr io.Writer) error { +// InstallFunc: func(interfaceMoqParam ghrepo.Interface) error { // panic("mock out the Install method") // }, // InstallLocalFunc: func(dir string) error { @@ -53,7 +54,7 @@ type ExtensionManagerMock struct { DispatchFunc func(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (bool, error) // InstallFunc mocks the Install method. - InstallFunc func(url string, stdout io.Writer, stderr io.Writer) error + InstallFunc func(interfaceMoqParam ghrepo.Interface) error // InstallLocalFunc mocks the InstallLocal method. InstallLocalFunc func(dir string) error @@ -87,12 +88,8 @@ type ExtensionManagerMock struct { } // Install holds details about calls to the Install method. Install []struct { - // URL is the url argument value. - URL string - // Stdout is the stdout argument value. - Stdout io.Writer - // Stderr is the stderr argument value. - Stderr io.Writer + // InterfaceMoqParam is the interfaceMoqParam argument value. + InterfaceMoqParam ghrepo.Interface } // InstallLocal holds details about calls to the InstallLocal method. InstallLocal []struct { @@ -205,37 +202,29 @@ func (mock *ExtensionManagerMock) DispatchCalls() []struct { } // Install calls InstallFunc. -func (mock *ExtensionManagerMock) Install(url string, stdout io.Writer, stderr io.Writer) error { +func (mock *ExtensionManagerMock) Install(interfaceMoqParam ghrepo.Interface) error { if mock.InstallFunc == nil { panic("ExtensionManagerMock.InstallFunc: method is nil but ExtensionManager.Install was just called") } callInfo := struct { - URL string - Stdout io.Writer - Stderr io.Writer + InterfaceMoqParam ghrepo.Interface }{ - URL: url, - Stdout: stdout, - Stderr: stderr, + InterfaceMoqParam: interfaceMoqParam, } mock.lockInstall.Lock() mock.calls.Install = append(mock.calls.Install, callInfo) mock.lockInstall.Unlock() - return mock.InstallFunc(url, stdout, stderr) + return mock.InstallFunc(interfaceMoqParam) } // InstallCalls gets all the calls that were made to Install. // Check the length with: // len(mockedExtensionManager.InstallCalls()) func (mock *ExtensionManagerMock) InstallCalls() []struct { - URL string - Stdout io.Writer - Stderr io.Writer + InterfaceMoqParam ghrepo.Interface } { var calls []struct { - URL string - Stdout io.Writer - Stderr io.Writer + InterfaceMoqParam ghrepo.Interface } mock.lockInstall.RLock() calls = mock.calls.Install