New GitHub CLI extension tools
source link: https://github.blog/2023-01-13-new-github-cli-extension-tools/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
New GitHub CLI extension tools
Support for GitHub CLI extensions has been expanded with new authorship tools and more ways to discover and install custom commands. Learn how to write powerful extensions in Go and find new commands to install.
Since the GitHub CLI 2.0 release, developers and organizations have customized their CLI experience by developing and installing extensions. Since then, the CLI team has been busy shipping several new features to further enhance the experience for both extension consumers and authors. Additionally, we’ve shipped go-gh 1.0 release, a Go library giving extension authors access to the same code that powers the GitHub CLI itself. Finally, the CLI team released the gh/pre-extension-precompile action, which automates the compilation and release of Go, Rust, or C++ extensions.
This blog post provides a tour of what’s new, including an in-depth look at writing a CLI extension with Go.
Introducing extension discovery
In the 2.20.0 release of the GitHub CLI, we shipped two new commands, including gh extension browse
and gh extension search
, to make discovery of extensions easier (all extension commands are aliased under gh ext
, so the rest of this post will use that shortened version).
gh ext browse
gh ext browse
is a new kind of command for the GitHub CLI: a fully interactive Terminal User Interface (TUI). It allows users to explore published extensions interactively right in the terminal.
Once gh ext browse
has launched and loads extension data, you can browse through all of the GitHub CLI extensions available for installation sorted by star count by pressing the up and down arrows (or k and j).
Pressing / focuses the filter box, allowing you to trim the list down to a search term.
You can select any extension by highlighting it. The selected extension can be installed by pressing i or uninstalled by pressing r. Pressing w will open the currently highlighted extension’s repository page on GitHub in your web browser.
Our hope is that this is a more enjoyable and easy way to discover new extensions and we’d love to hear feedback on the approach we took with this command.
gh ext search
In tandem with gh ext browse
we’ve shipped another new command intended for scripting and automation: gh ext search
. This is a classic CLI command which, with no arguments, prints out the first 30 extensions available to install sorted by star count.
A green check mark on the left indicates that an extension is installed locally.
Any arguments provided narrow the search results:
Results can be further refined and processed with flags, like:
--limit
, for fetching more results--owner
, for only returning extensions by a certain author--sort
, for example for sorting by updated--license
, to filter extensions by software license--web
, for opening search results in your web browser--json
, for returning results as JSON
This command is intended to be scripted and will produce composable output if piped. For example, you could install all of the extensions I have written with:
gh ext search --owner vilmibm | cut -f2 | while read -r extension; do gh ext install $extension; done
For more information about gh ext search
and example usage, see gh help ext search
.
Writing an extension with go-gh
The CLI team wanted to accelerate extension development by putting some of the GitHub CLI’s own code into an external library called go-gh for use by extension authors. The GitHub CLI itself is powered by go-gh, ensuring that it is held to a high standard of quality. This library is written in Go just like the CLI itself.
To demonstrate how to make use of this library, I’m going to walk through building an extension from the ground up. I’ll be developing a command called ask
for quickly searching the threads in GitHub Discussions. The end result of this exercise lives on GitHub if you want to see the full example.
Getting startedThe initial code
package main import ( "fmt" "github.com/cli/go-gh" ) func main() { fmt.Println("hi world, this is the gh-ask extension!") client, err := gh.RESTClient(nil) if err != nil { fmt.Println(err) return } response := struct {Login string}{} err = client.Get("user", &response) if err != nil { fmt.Println(err) return } fmt.Printf("running as %s\n", response.Login) }
Selecting a repository
…a list of relevant threads from whatever repository you're in locally…
…a list of relevant threads from the cli/cli repository…
main
(
)
{
err := cli(); err != {
fmt.Fprintf(os.Stderr, , err.Error())
os.Exit()
}
}
{
repoOverride := flag.String(
, , )
flag.Parse()
repo repository.Repository
err
*repoOverride == {
repo, err = gh.CurrentRepository()
} {
repo, err = repository.Parse(*repoOverride)
}
err != {
fmt.Errorf(, err.Error())
}
fmt.Printf(
, repo.Owner(), repo.Name())
}
$ go run .
Going to search discussions vilmibm/gh-ask
$ go run . --repo cli/cli
Going to search discussions cli/cli
Accepting an argument
(flag.Args()) < {
errors.New()
}
search := strings.Join(flag.Args(), )
fmt.Printf(
,
repo.Owner(), repo.Name(), search)
}
$ run .
Please specify a search term
exit status
$ run . cats
Going to search discussions in
$ run . fluffy cats
Going to search discussions in
Talking to the API
client, err := gh.GQLClient()
err != {
fmt.Errorf(, err)
}
query := fmt.Sprintf(, repo.Owner(), repo.Name())
Discussion {
Title
URL
Body
}
response := {
Repository {
Discussions {
Edges [] {
Node Discussion
}
}
HasDiscussionsEnabled
}
}{}
err = client.Do(query, , &response)
err != {
fmt.Errorf(, err)
}
!response.Repository.HasDiscussionsEnabled {
fmt.Errorf(, repo.Owner(), repo.Name())
}
matches := []Discussion{}
_, edge := response.Repository.Discussions.Edges {
strings.Contains(edge.Node.Body+edge.Node.Title, search) {
matches = (matches, edge.Node)
}
}
(matches) == {
fmt.Fprintln(os.Stderr, )
}
_, d := matches {
fmt.Printf(, d.Title, d.URL)
}
$ go run . --repo cli/cli actions
gh pr create don trigger `pullrequest:` actions https:
GitHub CLI . https:
What permissions are needed to OOTB GITHUB_TOKEN with gh pr merge --squash --auto https:
gh actions feedback https:
Pushing changes to an inbound pull request https:
getting workflow id and artifact id to reuse github actions https:
Formatting output
(matches) == {
fmt.Println()
}
isTerminal := term.IsTerminal(os.Stdout)
tp := tableprinter.New(os.Stdout, isTerminal, )
isTerminal {
fmt.Printf(
,
repo.Owner(), repo.Name(), search)
}
fmt.Println()
_, d := matches {
tp.AddField(d.Title)
tp.AddField(d.URL)
tp.EndRow()
}
err = tp.Render()
err != {
fmt.Errorf(, err)
}
Opening browsers
{
lucky := flag.Bool(, , )
(matches) == {
fmt.Println()
}
*lucky {
b := browser.New(, os.Stdout, os.Stderr)
b.Browse(matches[].URL)
}
JSON output
{
jsonFlag := flag.Bool(, , )
jqFlag := flag.String(, , )
isTerminal := term.IsTerminal(os.Stdout)
*jsonFlag {
output, err := json.Marshal(matches)
err != {
fmt.Errorf(, err)
}
*jqFlag != {
jq.Evaluate(bytes.NewBuffer(output), os.Stdout, *jqFlag)
}
jsonpretty.Format(os.Stdout, bytes.NewBuffer(output), , isTerminal)
}
Keep going
I’ll stop here with gh ask
but this isn’t all go-gh
can do. To browse its other features, check out this list of packages in the reference documentation. You can see my full code on GitHub at vilmibm/gh-ask.
Releasing your extension with cli/gh-extension-precompile
Now that I have a feature-filled extension, I’d like to make sure it’s easy to create releases for it so others can install it. At this point it’s uninstallable since I have not precompiled any of the Go code.
Before I worry about making a release, I have to make sure that my extension repository has the gh-extension tag. I can add that by running gh repo edit --add-tag gh-extension
. Without this topic added to the repository, it won’t show up in commands like gh ext browse
or gh ext search
.
Since I started this extension by running gh ext create
, I already have a GitHub Actions workflow defined for releasing. All that’s left before others can use my extension is pushing a tag to trigger a release. The workflow file contains:
name: release
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: cli/gh-extension-precompile@v1
Before tagging a release, make sure:
- The repository is public (assuming you want people to install this for themselves! You can keep it private and make releases just for yourself, if you prefer.).
- You’ve pushed all the local work you want to see in the release.
To release:
Note that the workflow ran automatically. It looks for tags of the form vX.Y.Z
and kicks off a build of your Go code. Once the release is done, I can check it to see that all my code compiled as expected:
Now, anyone can run gh ext install vilmibm/gh-ask
and try out my extension! This is all the work of the gh-extension-precompile action. This action can be used to compile any language, but by default it only knows how to handle Go code.
By default, the action will compile executables for:
- Linux (amd64, 386, arm, arm64)
- Windows (amd64, 386, arm64)
- MacOS (amd64, arm64)
- FreeBSD (amd64, 386, arm64)
- Android (amd64, arm64)
To build for a language other than Go, edit .github/workflows/release.yml
to add a build_script_override
configuration. For example, if my repository had a script at scripts/build.sh
, my release.yml
would look like:
name: release
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: cli/gh-extension-precompile@v1
with:
build_script_override: "script/build.sh"
The script specified as build_script_override
must produce executables in a dist directory at the root of the extension repository with file names ending with: {os}-{arch}{ext}, where the extension is .exe on Windows and blank on other platforms. For example:
dist/gh-my-ext_v1.0.0_darwin-amd64
dist/gh-my-ext_v1.0.0_windows-386.exe
Executables in this directory will be uploaded as release assets on GitHub. For OS and architecture nomenclature, please refer to this list. We use this nomenclature when looking for executables from the GitHub CLI, so it needs to be respected even for non-Go extensions.
Future directions
The CLI team has some improvements on the horizon for the extensions system in the GitHub CLI. We’re planning a more accessible version of the extension browse command that renders a single column style interface suitable for screen readers. We intend to add support for nested extensions–in other words, an extension called as a subcommand of an existing gh command like gh pr my-extension
–making third-party extensions fit more naturally into our command hierarchy. Finally, we’d like to improve the documentation and flexibility of the gh-extension-precompile action.
Are there features you’d like to see? We’d love to hear about it in a discussion or an issue in the cli/cli repository.
Wrap-up
It is our hope that the extensions system in the GitHub CLI inspire you to create features beyond our wildest imagination. Please go forth and make something you’re excited about, even if it’s just to make gh
do fun things like run screensavers.
The GitHub Insider Newsletter
Get the best of GitHub. Once a month. Directly to your inbox.
SubscribeMore on GitHub CLI
Contributing to open source at GitHub
A software engineer’s personal journey to becoming an open source contributor.
Release Radar · July 2022 Edition
While some of us have been wrapping up the financial year, and enjoying vacation time, others have been hard at work shipping open source projects and releases. These projects include…
GitHub Availability Report: March 2022
In March, we experienced several incidents resulting in significant impact to multiple GitHub services.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK