Skip to content

Check for new version of extension when invoked, print message notifying user of upgrade #8183

Closed

Description

@andyfeller

Describe the feature or problem you’d like to solve

GitHub CLI extension authors want to ensure users are running the newest version of extensions.
Like the GitHub CLI checks for updates every 24 hours when invoked, the GitHub CLI could perform the same logic when an extension command is invoked.

This would ensure GitHub CLI extension users knew about potentially necessary or valuable updates while minimizing the amount of effort on extension authors from reimplementing the logic already within GitHub CLI.

Proposed solution

  1. Refactor the update logic within cmd/gh/main.go to be reusable

    cli/cmd/gh/main.go

    Lines 59 to 68 in 64f4660

    updateCtx, updateCancel := context.WithCancel(ctx)
    defer updateCancel()
    updateMessageChan := make(chan *update.ReleaseInfo)
    go func() {
    rel, err := checkForUpdate(updateCtx, cmdFactory, buildVersion)
    if err != nil && hasDebug {
    fmt.Fprintf(stderr, "warning: checking for update failed: %v", err)
    }
    updateMessageChan <- rel
    }()

    cli/cmd/gh/main.go

    Lines 165 to 182 in 64f4660

    updateCancel() // if the update checker hasn't completed by now, abort it
    newRelease := <-updateMessageChan
    if newRelease != nil {
    isHomebrew := isUnderHomebrew(cmdFactory.Executable())
    if isHomebrew && isRecentRelease(newRelease.PublishedAt) {
    // do not notify Homebrew users before the version bump had a chance to get merged into homebrew-core
    return exitOK
    }
    fmt.Fprintf(stderr, "\n\n%s %s → %s\n",
    ansi.Color("A new release of gh is available:", "yellow"),
    ansi.Color(strings.TrimPrefix(buildVersion, "v"), "cyan"),
    ansi.Color(strings.TrimPrefix(newRelease.Version, "v"), "cyan"))
    if isHomebrew {
    fmt.Fprintf(stderr, "To upgrade, run: %s\n", "brew upgrade gh")
    }
    fmt.Fprintf(stderr, "%s\n\n",
    ansi.Color(newRelease.URL, "yellow"))
    }

  2. Enhance dynamic extension command generation to ensure extension release is checked independently of one another or GitHub CLI

    cli/pkg/cmd/root/root.go

    Lines 170 to 175 in 64f4660

    // Extensions
    em := f.ExtensionManager
    for _, e := range em.List() {
    extensionCmd := NewCmdExtension(io, em, e)
    cmd.AddCommand(extensionCmd)
    }

  3. Ensure latest release information logic supports GitHub Enterprise users

    func getLatestReleaseInfo(ctx context.Context, client *http.Client, repo string) (*ReleaseInfo, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repo), nil)
    if err != nil {
    return nil, err
    }
    res, err := client.Do(req)
    if err != nil {
    return nil, err
    }
    defer func() {
    _, _ = io.Copy(io.Discard, res.Body)
    res.Body.Close()
    }()
    if res.StatusCode != 200 {
    return nil, fmt.Errorf("unexpected HTTP %d", res.StatusCode)
    }
    dec := json.NewDecoder(res.Body)
    var latestRelease ReleaseInfo
    if err := dec.Decode(&latestRelease); err != nil {
    return nil, err
    }
    return &latestRelease, nil
    }

cc: @samcoe @nickfyson

Additional context

I was originally attempting to move the internal/update package to cli/go-gh, requiring the standardization on its api clients for the function. After talking with @samcoe about the process going from cli/cli api clients to cli/go-gh api clients, Sam's suggestion of having GitHub CLI do the checking would benefit all extensions without effort.

Activity

  1. added
    coreThis issue is not accepting PRs from outside contributors
    and removed on Oct 20, 2023
  2. andyfeller commented on Oct 23, 2023

    @andyfeller
    ContributorAuthor

    Realizing there is duplication between this and #5964. I believe the thoughts here echo those made by @vilmibm about not wanting to impact the experience, primarily focusing on notifying the user the extension has a newer release available.

  3. dylan-smith commented on Oct 23, 2023

    @dylan-smith

    would this approach work for non-Go based extensions?

  4. andyfeller commented on Oct 24, 2023

    @andyfeller
    ContributorAuthor

    would this approach work for non-Go based extensions?

    @dylan-smith : personally, I would like some way that we could detect this for either binary and script based extensions. I'm not sure how this would work for extensions that are locally installed; likely nothing happens to them. The main challenge I can see is that script based extensions are cloned repositories even when installing via a pinned tag.

    func (m *Manager) Install(repo ghrepo.Interface, target string) error {
    isBin, err := isBinExtension(m.client, repo)
    if err != nil {
    if errors.Is(err, releaseNotFoundErr) {
    if ok, err := repoExists(m.client, repo); err != nil {
    return err
    } else if !ok {
    return repositoryNotFoundErr
    }
    } else {
    return fmt.Errorf("could not check for binary extension: %w", err)
    }
    }
    if isBin {
    return m.installBin(repo, target)
    }
    hs, err := hasScript(m.client, repo)
    if err != nil {
    return err
    }
    if !hs {
    return errors.New("extension is not installable: missing executable")
    }
    return m.installGit(repo, target)
    }

    $ gh extension install andyfeller/gh-repo-export --pin v1.2.0 
    Cloning into '/Users/andyfeller/.local/share/gh/extensions/gh-repo-export'...
    remote: Enumerating objects: 82, done.
    remote: Counting objects: 100% (82/82), done.
    remote: Compressing objects: 100% (63/63), done.
    remote: Total 82 (delta 34), reused 47 (delta 17), pack-reused 0
    Receiving objects: 100% (82/82), 23.46 KiB | 5.86 MiB/s, done.
    Resolving deltas: 100% (34/34), done.
    ✓ Installed extension andyfeller/gh-repo-export
    ✓ Pinned extension at v1.2.0
    
    
    $ gh extension list                                           
    gh repo-export          andyfeller/gh-repo-export          614376c5
  5. nickfyson commented on Nov 1, 2023

    @nickfyson
    Contributor

    Just wanted to officially 'plus 1' this issue, as having this functionality provided by the default CLI would enable us to take the duplicated functionality out of github/gh-autofix. 🙂

    Also wanted to comment as a reminder to myself of the need for some coordination to avoid users getting double-notified (at least not too many times 😆).

  6. andyfeller commented on Nov 13, 2023

    @andyfeller
    ContributorAuthor

    While discussing this with @samcoe today, we largely reaffirmed initial points above in that it made sense to add this logic specifically to the commands for extensions and that we would need to maintain state information for the extension.

    The only unanswered question is: do we manage cached release information for extensions differently than GitHub CLI?

    1. The GitHub CLI checks at the least every 24 hours whether there is a new version available and caches that information under state:

      $ cat ~/.local/state/gh/state.yml 
      checked_for_update_at: 2023-11-13T08:07:36.475011-05:00
      latest_release:
          version: v2.38.0
          url: https://github.com/cli/cli/releases/tag/v2.38.0
          publishedat: 2023-11-01T10:52:41Z
    2. The GitHub CLI manages extensions under data directory:

      $ cat ~/.local/share/gh/extensions/gh-copilot/manifest.yml 
      owner: github
      name: gh-copilot
      host: github.com
      tag: v0.5.3-beta
      ispinned: false
      path: /Users/andyfeller/.local/share/gh/extensions/gh-copilot/gh-copilot

    This division is based upon XDG Base Directory Specification, which causes us to question which trade off we're willing to make:

    1. Treat the extension release information like state and maintain in $XDG_STATE_HOME directory, separate from the extensions themselves and their manifests

      • Pro: provides a single space on disk for all state information
      • Con: spreads information about extension all over the place
    2. Couple the extension release information like extension artifacts and maintain in $XDG_DATA_HOME directory, spreading release information in multiple places

      • Pro: makes cleaning up an extension straightforward, delete the directory
      • Con: spreads release information all over the place

    Between these 2 approaches, it is arguable that the latter is more important the former.

  7. 0xdevalias commented on Nov 14, 2023

    @0xdevalias

    Not to de-rail the discussion here too much, but I just opened a new semi-related issue that proposes adding a gh extensions outdated command, and it would seem that the update check+cached versions discussion here would work well for that feature too:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

coreThis issue is not accepting PRs from outside contributorsenhancementa request to improve CLIgh-extensionrelating to the gh extension command

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions