github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/docs/project-layout.md (about) 1 # GitHub CLI project layout 2 3 At a high level, these areas make up the `github.com/ungtb10d/cli` project: 4 - [`cmd/`](../cmd) - `main` packages for building binaries such as the `gh` executable 5 - [`pkg/`](../pkg) - most other packages, including the implementation for individual gh commands 6 - [`docs/`](../docs) - documentation for maintainers and contributors 7 - [`script/`](../script) - build and release scripts 8 - [`internal/`](../internal) - Go packages highly specific to our needs and thus internal 9 - [`go.mod`](../go.mod) - external Go dependencies for this project, automatically fetched by Go at build time 10 11 Some auxiliary Go packages are at the top level of the project for historical reasons: 12 - [`api/`](../api) - main utilities for making requests to the GitHub API 13 - [`context/`](../context) - DEPRECATED: use only for referencing git remotes 14 - [`git/`](../git) - utilities to gather information from a local git repository 15 - [`test/`](../test) - DEPRECATED: do not use 16 - [`utils/`](../utils) - DEPRECATED: use only for printing table output 17 18 ## Command-line help text 19 20 Running `gh help issue list` displays help text for a topic. In this case, the topic is a specific command, 21 and help text for every command is embedded in that command's source code. The naming convention for gh 22 commands is: 23 ``` 24 pkg/cmd/<command>/<subcommand>/<subcommand>.go 25 ``` 26 Following the above example, the main implementation for the `gh issue list` command, including its help 27 text, is in [pkg/cmd/issue/list/list.go](../pkg/cmd/issue/list/list.go) 28 29 Other help topics not specific to any command, for example `gh help environment`, are found in 30 [pkg/cmd/root/help_topic.go](../pkg/cmd/root/help_topic.go). 31 32 During our release process, these help topics are [automatically converted](../cmd/gen-docs/main.go) to 33 manual pages and published under https://cli.github.com/manual/. 34 35 ## How GitHub CLI works 36 37 To illustrate how GitHub CLI works in its typical mode of operation, let's build the project, run a command, 38 and talk through which code gets run in order. 39 40 1. `go run script/build.go` - Makes sure all external Go dependencies are fetched, then compiles the 41 `cmd/gh/main.go` file into a `bin/gh` binary. 42 2. `bin/gh issue list --limit 5` - Runs the newly built `bin/gh` binary (note: on Windows you must use 43 backslashes like `bin\gh`) and passes the following arguments to the process: `["issue", "list", "--limit", "5"]`. 44 3. `func main()` inside `cmd/gh/main.go` is the first Go function that runs. The arguments passed to the 45 process are available through `os.Args`. 46 4. The `main` package initializes the "root" command with `root.NewCmdRoot()` and dispatches execution to it 47 with `rootCmd.ExecuteC()`. 48 5. The [root command](../pkg/cmd/root/root.go) represents the top-level `gh` command and knows how to 49 dispatch execution to any other gh command nested under it. 50 6. Based on `["issue", "list"]` arguments, the execution reaches the `RunE` block of the `cobra.Command` 51 within [pkg/cmd/issue/list/list.go](../pkg/cmd/issue/list/list.go). 52 7. The `--limit 5` flag originally passed as arguments be automatically parsed and its value stored as 53 `opts.LimitResults`. 54 8. `func listRun()` is called, which is responsible for implementing the logic of the `gh issue list` command. 55 9. The command collects information from sources like the GitHub API then writes the final output to 56 standard output and standard error [streams](../pkg/iostreams/iostreams.go) available at `opts.IO`. 57 10. The program execution is now back at `func main()` of `cmd/gh/main.go`. If there were any Go errors as a 58 result of processing the command, the function will abort the process with a non-zero exit status. 59 Otherwise, the process ends with status 0 indicating success. 60 61 ## How to add a new command 62 63 1. First, check on our issue tracker to verify that our team had approved the plans for a new command. 64 2. Create a package for the new command, e.g. for a new command `gh boom` create the following directory 65 structure: `pkg/cmd/boom/` 66 3. The new package should expose a method, e.g. `NewCmdBoom()`, that accepts a `*cmdutil.Factory` type and 67 returns a `*cobra.Command`. 68 * Any logic specific to this command should be kept within the command's package and not added to any 69 "global" packages like `api` or `utils`. 70 4. Use the method from the previous step to generate the command and add it to the command tree, typically 71 somewhere in the `NewCmdRoot()` method. 72 73 ## How to write tests 74 75 This task might be tricky. Typically, gh commands do things like look up information from the git repository 76 in the current directory, query the GitHub API, scan the user's `~/.ssh/config` file, clone or fetch git 77 repositories, etc. Naturally, none of these things should ever happen for real when running tests, unless 78 you are sure that any filesystem operations are strictly scoped to a location made for and maintained by the 79 test itself. To avoid actually running things like making real API requests or shelling out to `git` 80 commands, we stub them. You should look at how that's done within some existing tests. 81 82 To make your code testable, write small, isolated pieces of functionality that are designed to be composed 83 together. Prefer table-driven tests for maintaining variations of different test inputs and expectations 84 when exercising a single piece of functionality.