docs/project-layout.md
At a high level, these areas make up the github.com/cli/cli project:
cmd/ - main packages for building binaries such as the gh executablepkg/ - most other packages, including the implementation for individual gh commandsdocs/ - documentation for maintainers and contributorsscript/ - build and release scriptsinternal/ - Go packages highly specific to our needs and thus internalgo.mod - external Go dependencies for this project, automatically fetched by Go at build timeSome auxiliary Go packages are at the top level of the project for historical reasons:
api/ - main utilities for making requests to the GitHub APIcontext/ - DEPRECATED: use only for referencing git remotesgit/ - utilities to gather information from a local git repositorytest/ - DEPRECATED: do not useutils/ - DEPRECATED: use only for printing table outputRunning gh help issue list displays help text for a topic. In this case, the topic is a specific command,
and help text for every command is embedded in that command's source code. The naming convention for gh
commands is:
pkg/cmd/<command>/<subcommand>/<subcommand>.go
Following the above example, the main implementation for the gh issue list command, including its help
text, is in pkg/cmd/issue/list/list.go
Other help topics not specific to any command, for example gh help environment, are found in
pkg/cmd/root/help_topic.go.
During our release process, these help topics are automatically converted to manual pages and published under https://cli.github.com/manual/.
To illustrate how GitHub CLI works in its typical mode of operation, let's build the project, run a command, and talk through which code gets run in order.
go run script/build.go - Makes sure all external Go dependencies are fetched, then compiles the
cmd/gh/main.go file into a bin/gh binary.bin/gh issue list --limit 5 - Runs the newly built bin/gh binary (note: on Windows you must use
backslashes like bin\gh) and passes the following arguments to the process: ["issue", "list", "--limit", "5"].func main() inside cmd/gh/main.go is the first Go function that runs. The arguments passed to the
process are available through os.Args.main package initializes the "root" command with root.NewCmdRoot() and dispatches execution to it
with rootCmd.ExecuteC().gh command and knows how to
dispatch execution to any other gh command nested under it.["issue", "list"] arguments, the execution reaches the RunE block of the cobra.Command
within pkg/cmd/issue/list/list.go.--limit 5 flag originally passed as arguments be automatically parsed and its value stored as
opts.LimitResults.func listRun() is called, which is responsible for implementing the logic of the gh issue list command.opts.IO.func main() of cmd/gh/main.go. If there were any Go errors as a
result of processing the command, the function will abort the process with a non-zero exit status.
Otherwise, the process ends with status 0 indicating success.gh boom create the following directory
structure: pkg/cmd/boom/NewCmdBoom(), that accepts a *cmdutil.Factory type and
returns a *cobra.Command.
api or utils.NewCmdRoot() method.This task might be tricky. Typically, gh commands do things like look up information from the git repository
in the current directory, query the GitHub API, scan the user's ~/.ssh/config file, clone or fetch git
repositories, etc. Naturally, none of these things should ever happen for real when running tests, unless
you are sure that any filesystem operations are strictly scoped to a location made for and maintained by the
test itself. To avoid actually running things like making real API requests or shelling out to git
commands, we stub them. You should look at how that's done within some existing tests.
To make your code testable, write small, isolated pieces of functionality that are designed to be composed together. Prefer table-driven tests for maintaining variations of different test inputs and expectations when exercising a single piece of functionality.