cli/pkg/cmd/root/official_extension.go
William Martin 7ad1d7c0a1 Suggest and install official extensions via stub commands
Register hidden stub commands for official GitHub extensions (gh-aw,
gh-stack) that offer to install the extension when invoked. This
replaces the error-string-matching approach from the original PR with
proper cobra commands that:

- Avoid false-positive matches on flag values or post-'--' args
- Eliminate conflicting cobra 'Did you mean?' suggestions
- Properly propagate prompt/install errors for correct exit codes
- Are hidden from help output and shell completions
- Use GroupID "extension" so checkValidExtension allows installing over them
- Are registered after extensions and aliases so both take priority

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 17:58:37 +02:00

76 lines
2.4 KiB
Go

package root
import (
"fmt"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/extensions"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/spf13/cobra"
)
// NewCmdOfficialExtension creates a hidden stub command for an official
// extension that has not yet been installed. When invoked, it suggests
// installing the extension and, in interactive sessions, offers to do so
// immediately. After a successful install, the extension is dispatched with
// the original arguments.
func NewCmdOfficialExtension(io *iostreams.IOStreams, p prompter.Prompter, em extensions.ExtensionManager, ext *extensions.OfficialExtension) *cobra.Command {
return &cobra.Command{
Use: ext.Name,
Short: fmt.Sprintf("Install the official %s extension", ext.Name),
Hidden: true,
GroupID: "extension",
Annotations: map[string]string{
"skipAuthCheck": "true",
},
// Accept any args/flags the user may have passed so we don't get
// cobra validation errors before reaching RunE.
DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
return officialExtensionRun(io, p, em, ext, args)
},
}
}
func officialExtensionRun(io *iostreams.IOStreams, p prompter.Prompter, em extensions.ExtensionManager, ext *extensions.OfficialExtension, args []string) error {
stderr := io.ErrOut
if !io.CanPrompt() {
fmt.Fprint(stderr, heredoc.Docf(`
%[1]s is available as an official extension.
To install it, run:
gh extension install %[2]s/%[3]s
`, fmt.Sprintf("gh %s", ext.Name), ext.Owner, ext.Repo))
return nil
}
prompt := heredoc.Docf(`
%[1]s is available as an official extension.
Would you like to install it now?
`, fmt.Sprintf("gh %s", ext.Name))
confirmed, err := p.Confirm(prompt, true)
if err != nil {
return err
}
if !confirmed {
return nil
}
repo := ext.Repository()
io.StartProgressIndicatorWithLabel(fmt.Sprintf("Installing %s/%s...", ext.Owner, ext.Repo))
installErr := em.Install(repo, "")
io.StopProgressIndicator()
if installErr != nil {
return fmt.Errorf("failed to install extension: %w", installErr)
}
fmt.Fprintf(stderr, "Successfully installed %s/%s\n", ext.Owner, ext.Repo)
// Dispatch the newly installed extension with the original arguments.
dispatchArgs := append([]string{ext.Name}, args...)
if _, dispatchErr := em.Dispatch(dispatchArgs, io.In, io.Out, stderr); dispatchErr != nil {
return dispatchErr
}
return nil
}