Add godoc comments to exported symbols in pkg/cmd/extension
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
d6ab1b044a
commit
520482c143
7 changed files with 68 additions and 2 deletions
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
const pagingOffset = 24
|
||||
|
||||
// ExtBrowseOpts holds the options for the extension browse command.
|
||||
type ExtBrowseOpts struct {
|
||||
Cmd *cobra.Command
|
||||
Browser ibrowser
|
||||
|
|
@ -63,6 +64,7 @@ type extEntry struct {
|
|||
description string
|
||||
}
|
||||
|
||||
// Title returns the display title for the extension entry including status indicators.
|
||||
func (e extEntry) Title() string {
|
||||
var installed string
|
||||
var official string
|
||||
|
|
@ -78,6 +80,7 @@ func (e extEntry) Title() string {
|
|||
return fmt.Sprintf("%s%s%s", e.FullName, official, installed)
|
||||
}
|
||||
|
||||
// Description returns the extension description or a default placeholder.
|
||||
func (e extEntry) Description() string {
|
||||
if e.description == "" {
|
||||
return "no description provided"
|
||||
|
|
@ -103,9 +106,14 @@ type wGroup interface {
|
|||
|
||||
type fakeGroup struct{}
|
||||
|
||||
// Add is a no-op implementation of the wGroup interface.
|
||||
func (w *fakeGroup) Add(int) {}
|
||||
func (w *fakeGroup) Done() {}
|
||||
func (w *fakeGroup) Wait() {}
|
||||
|
||||
// Done is a no-op implementation of the wGroup interface.
|
||||
func (w *fakeGroup) Done() {}
|
||||
|
||||
// Wait is a no-op implementation of the wGroup interface.
|
||||
func (w *fakeGroup) Wait() {}
|
||||
|
||||
func newExtList(opts ExtBrowseOpts, ui uiRegistry, extEntries []extEntry) *extList {
|
||||
ui.List.SetTitleColor(tcell.ColorWhite)
|
||||
|
|
@ -219,10 +227,12 @@ func (el *extList) toggleSelected(verb string) {
|
|||
}
|
||||
}
|
||||
|
||||
// InstallSelected installs the currently highlighted extension.
|
||||
func (el *extList) InstallSelected() {
|
||||
el.toggleSelected("install")
|
||||
}
|
||||
|
||||
// RemoveSelected removes the currently highlighted extension.
|
||||
func (el *extList) RemoveSelected() {
|
||||
el.toggleSelected("remove")
|
||||
}
|
||||
|
|
@ -233,15 +243,18 @@ func (el *extList) toggleInstalled(ix int) {
|
|||
el.extEntries[ix] = ee
|
||||
}
|
||||
|
||||
// Focus sets the application focus to the extension list.
|
||||
func (el *extList) Focus() {
|
||||
el.app.SetFocus(el.ui.List)
|
||||
}
|
||||
|
||||
// Refresh resets the list and reapplies the current filter.
|
||||
func (el *extList) Refresh() {
|
||||
el.Reset()
|
||||
el.Filter(el.filter)
|
||||
}
|
||||
|
||||
// Reset clears the list and repopulates it with all extension entries.
|
||||
func (el *extList) Reset() {
|
||||
el.ui.List.Clear()
|
||||
for _, ee := range el.extEntries {
|
||||
|
|
@ -249,10 +262,12 @@ func (el *extList) Reset() {
|
|||
}
|
||||
}
|
||||
|
||||
// PageDown moves the list selection down by one page.
|
||||
func (el *extList) PageDown() {
|
||||
el.ui.List.SetCurrentItem(el.ui.List.GetCurrentItem() + pagingOffset)
|
||||
}
|
||||
|
||||
// PageUp moves the list selection up by one page.
|
||||
func (el *extList) PageUp() {
|
||||
i := el.ui.List.GetCurrentItem() - pagingOffset
|
||||
if i < 0 {
|
||||
|
|
@ -261,10 +276,12 @@ func (el *extList) PageUp() {
|
|||
el.ui.List.SetCurrentItem(i)
|
||||
}
|
||||
|
||||
// ScrollDown moves the list selection down by one item.
|
||||
func (el *extList) ScrollDown() {
|
||||
el.ui.List.SetCurrentItem(el.ui.List.GetCurrentItem() + 1)
|
||||
}
|
||||
|
||||
// ScrollUp moves the list selection up by one item.
|
||||
func (el *extList) ScrollUp() {
|
||||
i := el.ui.List.GetCurrentItem() - 1
|
||||
if i < 0 {
|
||||
|
|
@ -273,6 +290,7 @@ func (el *extList) ScrollUp() {
|
|||
el.ui.List.SetCurrentItem(i)
|
||||
}
|
||||
|
||||
// FindSelected returns the currently selected extension entry and its index.
|
||||
func (el *extList) FindSelected() (extEntry, int) {
|
||||
if el.ui.List.GetItemCount() == 0 {
|
||||
return extEntry{}, -1
|
||||
|
|
@ -286,6 +304,7 @@ func (el *extList) FindSelected() (extEntry, int) {
|
|||
return extEntry{}, -1
|
||||
}
|
||||
|
||||
// Filter narrows the displayed list to entries matching the given text.
|
||||
func (el *extList) Filter(text string) {
|
||||
el.filter = text
|
||||
if text == "" {
|
||||
|
|
@ -377,6 +396,7 @@ func getExtensions(opts ExtBrowseOpts) ([]extEntry, error) {
|
|||
return extEntries, nil
|
||||
}
|
||||
|
||||
// ExtBrowse launches the interactive TUI for browsing and managing extensions.
|
||||
func ExtBrowse(opts ExtBrowseOpts) error {
|
||||
if opts.Debug {
|
||||
f, err := os.CreateTemp("", "extBrowse-*.txt")
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ func newReadmeGetter(client *http.Client, cacheTTL time.Duration) *readmeGetter
|
|||
}
|
||||
}
|
||||
|
||||
// Get fetches the README content for the given repository full name.
|
||||
func (g *readmeGetter) Get(repoFullName string) (string, error) {
|
||||
repo, err := ghrepo.FromFullName(repoFullName)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
var alreadyInstalledError = errors.New("alreadyInstalledError")
|
||||
|
||||
// NewCmdExtension creates the cobra command for managing gh extensions.
|
||||
func NewCmdExtension(f *cmdutil.Factory) *cobra.Command {
|
||||
m := f.ExtensionManager
|
||||
io := f.IOStreams
|
||||
|
|
|
|||
|
|
@ -16,14 +16,19 @@ import (
|
|||
|
||||
const manifestName = "manifest.yml"
|
||||
|
||||
// ExtensionKind indicates the type of a gh CLI extension.
|
||||
type ExtensionKind int
|
||||
|
||||
const (
|
||||
// GitKind represents a git-based extension.
|
||||
GitKind ExtensionKind = iota
|
||||
// BinaryKind represents a precompiled binary extension.
|
||||
BinaryKind
|
||||
// LocalKind represents a locally developed extension.
|
||||
LocalKind
|
||||
)
|
||||
|
||||
// Extension represents an installed gh CLI extension.
|
||||
type Extension struct {
|
||||
path string
|
||||
kind ExtensionKind
|
||||
|
|
@ -40,22 +45,27 @@ type Extension struct {
|
|||
owner string
|
||||
}
|
||||
|
||||
// Name returns the extension name without the "gh-" prefix.
|
||||
func (e *Extension) Name() string {
|
||||
return strings.TrimPrefix(filepath.Base(e.path), "gh-")
|
||||
}
|
||||
|
||||
// Path returns the filesystem path to the extension executable.
|
||||
func (e *Extension) Path() string {
|
||||
return e.path
|
||||
}
|
||||
|
||||
// IsLocal returns true if the extension is a locally developed extension.
|
||||
func (e *Extension) IsLocal() bool {
|
||||
return e.kind == LocalKind
|
||||
}
|
||||
|
||||
// IsBinary returns true if the extension is a precompiled binary.
|
||||
func (e *Extension) IsBinary() bool {
|
||||
return e.kind == BinaryKind
|
||||
}
|
||||
|
||||
// URL returns the repository URL of the extension.
|
||||
func (e *Extension) URL() string {
|
||||
e.mu.RLock()
|
||||
if e.url != "" {
|
||||
|
|
@ -85,6 +95,7 @@ func (e *Extension) URL() string {
|
|||
return e.url
|
||||
}
|
||||
|
||||
// CurrentVersion returns the currently installed version of the extension.
|
||||
func (e *Extension) CurrentVersion() string {
|
||||
e.mu.RLock()
|
||||
if e.currentVersion != "" {
|
||||
|
|
@ -113,6 +124,7 @@ func (e *Extension) CurrentVersion() string {
|
|||
return e.currentVersion
|
||||
}
|
||||
|
||||
// LatestVersion returns the latest available version of the extension.
|
||||
func (e *Extension) LatestVersion() string {
|
||||
e.mu.RLock()
|
||||
if e.latestVersion != "" {
|
||||
|
|
@ -147,6 +159,7 @@ func (e *Extension) LatestVersion() string {
|
|||
return e.latestVersion
|
||||
}
|
||||
|
||||
// IsPinned returns true if the extension is pinned to a specific version.
|
||||
func (e *Extension) IsPinned() bool {
|
||||
e.mu.RLock()
|
||||
if e.isPinned != nil {
|
||||
|
|
@ -179,6 +192,7 @@ func (e *Extension) IsPinned() bool {
|
|||
return *e.isPinned
|
||||
}
|
||||
|
||||
// Owner returns the GitHub owner of the extension repository.
|
||||
func (e *Extension) Owner() string {
|
||||
e.mu.RLock()
|
||||
if e.owner != "" {
|
||||
|
|
@ -211,6 +225,7 @@ func (e *Extension) Owner() string {
|
|||
return e.owner
|
||||
}
|
||||
|
||||
// UpdateAvailable returns true if a newer version of the extension exists.
|
||||
func (e *Extension) UpdateAvailable() bool {
|
||||
if e.IsLocal() ||
|
||||
e.CurrentVersion() == "" ||
|
||||
|
|
|
|||
|
|
@ -21,14 +21,17 @@ type gitExecuter struct {
|
|||
client *git.Client
|
||||
}
|
||||
|
||||
// CheckoutBranch checks out the specified branch in the repository.
|
||||
func (g *gitExecuter) CheckoutBranch(branch string) error {
|
||||
return g.client.CheckoutBranch(context.Background(), branch)
|
||||
}
|
||||
|
||||
// Clone clones a repository from the given URL with optional arguments.
|
||||
func (g *gitExecuter) Clone(cloneURL string, cloneArgs []string) (string, error) {
|
||||
return g.client.Clone(context.Background(), cloneURL, cloneArgs)
|
||||
}
|
||||
|
||||
// CommandOutput runs a git command and returns its output.
|
||||
func (g *gitExecuter) CommandOutput(args []string) ([]byte, error) {
|
||||
cmd, err := g.client.Command(context.Background(), args...)
|
||||
if err != nil {
|
||||
|
|
@ -37,24 +40,29 @@ func (g *gitExecuter) CommandOutput(args []string) ([]byte, error) {
|
|||
return cmd.Output()
|
||||
}
|
||||
|
||||
// Config retrieves a git configuration value by name.
|
||||
func (g *gitExecuter) Config(name string) (string, error) {
|
||||
return g.client.Config(context.Background(), name)
|
||||
}
|
||||
|
||||
// Fetch fetches refs from the specified remote.
|
||||
func (g *gitExecuter) Fetch(remote string, refspec string) error {
|
||||
return g.client.Fetch(context.Background(), remote, refspec)
|
||||
}
|
||||
|
||||
// ForRepo returns a new gitClient scoped to the given repository directory.
|
||||
func (g *gitExecuter) ForRepo(repoDir string) gitClient {
|
||||
gc := g.client.Copy()
|
||||
gc.RepoDir = repoDir
|
||||
return &gitExecuter{client: gc}
|
||||
}
|
||||
|
||||
// Pull pulls changes from the specified remote and branch.
|
||||
func (g *gitExecuter) Pull(remote, branch string) error {
|
||||
return g.client.Pull(context.Background(), remote, branch)
|
||||
}
|
||||
|
||||
// Remotes returns the set of configured git remotes.
|
||||
func (g *gitExecuter) Remotes() (git.RemoteSet, error) {
|
||||
return g.client.Remotes(context.Background())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,17 +30,20 @@ import (
|
|||
// ErrInitialCommitFailed indicates the initial commit when making a new extension failed.
|
||||
var ErrInitialCommitFailed = errors.New("initial commit failed")
|
||||
|
||||
// ErrExtensionExecutableNotFound indicates that an installed extension has no executable.
|
||||
type ErrExtensionExecutableNotFound struct {
|
||||
Dir string
|
||||
Name string
|
||||
}
|
||||
|
||||
// Error returns a descriptive message about the missing executable.
|
||||
func (e *ErrExtensionExecutableNotFound) Error() string {
|
||||
return fmt.Sprintf("an extension has been installed but there is no executable: executable file named \"%s\" in %s is required to run the extension after install. Perhaps you need to build it?\n", e.Name, e.Dir)
|
||||
}
|
||||
|
||||
const darwinAmd64 = "darwin-amd64"
|
||||
|
||||
// Manager handles installation, upgrade, and removal of gh CLI extensions.
|
||||
type Manager struct {
|
||||
dataDir func() string
|
||||
updateDir func() string
|
||||
|
|
@ -55,6 +58,7 @@ type Manager struct {
|
|||
dryRunMode bool
|
||||
}
|
||||
|
||||
// NewManager creates a new extension Manager with default settings.
|
||||
func NewManager(ios *iostreams.IOStreams, gc *git.Client) *Manager {
|
||||
return &Manager{
|
||||
dataDir: config.DataDir,
|
||||
|
|
@ -76,18 +80,22 @@ func NewManager(ios *iostreams.IOStreams, gc *git.Client) *Manager {
|
|||
}
|
||||
}
|
||||
|
||||
// SetConfig sets the configuration used by the Manager.
|
||||
func (m *Manager) SetConfig(cfg gh.Config) {
|
||||
m.config = cfg
|
||||
}
|
||||
|
||||
// SetClient sets the HTTP client used by the Manager.
|
||||
func (m *Manager) SetClient(client *http.Client) {
|
||||
m.client = client
|
||||
}
|
||||
|
||||
// EnableDryRunMode enables dry-run mode so that no changes are persisted.
|
||||
func (m *Manager) EnableDryRunMode() {
|
||||
m.dryRunMode = true
|
||||
}
|
||||
|
||||
// Dispatch executes an installed extension by name with the given I/O streams.
|
||||
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")
|
||||
|
|
@ -133,6 +141,7 @@ func (m *Manager) Dispatch(args []string, stdin io.Reader, stdout, stderr io.Wri
|
|||
return true, externalCmd.Run()
|
||||
}
|
||||
|
||||
// List returns all installed extensions.
|
||||
func (m *Manager) List() []extensions.Extension {
|
||||
exts, _ := m.list(false)
|
||||
r := make([]extensions.Extension, len(exts))
|
||||
|
|
@ -205,6 +214,7 @@ func (m *Manager) populateLatestVersions(exts []*Extension) {
|
|||
wg.Wait()
|
||||
}
|
||||
|
||||
// InstallLocal installs a local extension from the given directory.
|
||||
func (m *Manager) InstallLocal(dir string) error {
|
||||
name := filepath.Base(dir)
|
||||
if err := m.cleanExtensionUpdateDir(name); err != nil {
|
||||
|
|
@ -451,6 +461,7 @@ var localExtensionUpgradeError = errors.New("local extensions can not be upgrade
|
|||
var upToDateError = errors.New("already up to date")
|
||||
var noExtensionsInstalledError = errors.New("no extensions installed")
|
||||
|
||||
// Upgrade upgrades the named extension or all extensions if name is empty.
|
||||
func (m *Manager) Upgrade(name string, force bool) error {
|
||||
// Fetch metadata during list only when upgrading all extensions.
|
||||
// This is a performance improvement so that we don't make a
|
||||
|
|
@ -570,6 +581,7 @@ func (m *Manager) upgradeBinExtension(ext *Extension) error {
|
|||
return m.installBin(repo, "")
|
||||
}
|
||||
|
||||
// Remove uninstalls the named extension and cleans up its files.
|
||||
func (m *Manager) Remove(name string) error {
|
||||
name = normalizeExtension(name)
|
||||
targetDir := filepath.Join(m.installDir(), name)
|
||||
|
|
@ -609,6 +621,7 @@ var scriptTmpl string
|
|||
//go:embed ext_tmpls/buildScript.sh
|
||||
var buildScript []byte
|
||||
|
||||
// Create scaffolds a new extension project with the given name and template type.
|
||||
func (m *Manager) Create(name string, tmplType extensions.ExtTemplateType) error {
|
||||
if _, err := m.gitClient.CommandOutput([]string{"init", "--quiet", name}); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -9,31 +9,37 @@ type mockGitClient struct {
|
|||
mock.Mock
|
||||
}
|
||||
|
||||
// CheckoutBranch mocks checking out a branch.
|
||||
func (g *mockGitClient) CheckoutBranch(branch string) error {
|
||||
args := g.Called(branch)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// Clone mocks cloning a repository.
|
||||
func (g *mockGitClient) Clone(cloneURL string, cloneArgs []string) (string, error) {
|
||||
args := g.Called(cloneURL, cloneArgs)
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
||||
// CommandOutput mocks running a git command and returning its output.
|
||||
func (g *mockGitClient) CommandOutput(commandArgs []string) ([]byte, error) {
|
||||
args := g.Called(commandArgs)
|
||||
return []byte(args.String(0)), args.Error(1)
|
||||
}
|
||||
|
||||
// Config mocks retrieving a git configuration value.
|
||||
func (g *mockGitClient) Config(name string) (string, error) {
|
||||
args := g.Called(name)
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
||||
// Fetch mocks fetching refs from a remote.
|
||||
func (g *mockGitClient) Fetch(remote string, refspec string) error {
|
||||
args := g.Called(remote, refspec)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// ForRepo mocks returning a gitClient scoped to a repository directory.
|
||||
func (g *mockGitClient) ForRepo(repoDir string) gitClient {
|
||||
args := g.Called(repoDir)
|
||||
if v, ok := args.Get(0).(*mockGitClient); ok {
|
||||
|
|
@ -42,11 +48,13 @@ func (g *mockGitClient) ForRepo(repoDir string) gitClient {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Pull mocks pulling changes from a remote branch.
|
||||
func (g *mockGitClient) Pull(remote, branch string) error {
|
||||
args := g.Called(remote, branch)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// Remotes mocks returning the set of configured git remotes.
|
||||
func (g *mockGitClient) Remotes() (git.RemoteSet, error) {
|
||||
args := g.Called()
|
||||
return nil, args.Error(1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue