CLI version: a collection of handy tips
Tags: cli, programming, productivity, tutorial
Published: 2022-09-27 12:21:33

Edited by: [Maja Szostok](https://www.linkedin.com/in/maja-szostok/) 

## Table Of Contents
  * [Flag or Command?](#flag-or-command)
    * [Flags](#flags)
    * [Command](#command)
    * [Decision](#decision)
  * [What to collect](#what-to-collect)
  * [Output options](#output-options)
  * [Bells and whistles](#bells-and-whistles)
    * [Upgrade notice](#upgrade-notice)
    * [Version skew warning](#version-skew-warning)
  * [Summary](#summary)
  * [Bonus](#bonus)

---

Including version information in CLIs is as obvious as a shower after a 3-day music festival. But have you ever given it a second thought?

I researched 16 popular CLIs to find out how they approach this, and ultimately to come up with a solution that is both elegant and useful.

In this article, I present my findings and outline both the good and the bad of each approach.

![Pretty version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofw5gps9ew7esixgc0l0.gif) <center>source: <a href="https://szostok.io/projects/version/">version.szostok.io</a></center>

----

## Flag or Command?

Before we discuss what we should display, let's talk about how our user can get the CLI version.

![flags-or-commands](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tm00vihl75hf2uz8rvnl.gif)

### Flags

Let's take a look at the most popular options:

| Name        | Description                                                                                                                                                   |
|-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-v`        | Might be confusing, as `-v` is often used to enable the verbose mode. <br> If you decide to use `-v` anyway, consider `-d, --debug` for more verbose output.  |
| `-V`        | Being a capital letter, it's a better alternative for `-v` as it's visually distinct.                                                                         |
| `--version` | Is a self-descriptive alternative, mentioned by [CLI guidelines](https://clig.dev/#arguments-and-flags) as a common approach found in CLIs.                   |


However, flags come with one big problem. It's hard to combine them with additional functionality. The most popular ones are:
- Printing a help message with more information about the version itself.
- Printing only the version number.
- Printing only the client version when your CLI works with a server, like `kubectl` does.

There are some options to deal with that, but they have their own flaws:
- `<cli> -V --short` — it's hard to tell which flags work together
- A combination of function and `-version` suffix. For example:
    ```bash
    <cli> --short-version
    ```
    Unfortunately, this doesn't scale well. See:
    ```bash
    <cli> --short-client-version
    <cli> --short-server-version
    <cli> --json-version
    <cli> --json-client-version
    <cli> --json-server-version
    ```

### Command

An alternative solution is to have a dedicated `<cli> version` command, which gives you an easy way to:
- Print a help message with `<cli> version -h`.
- Combine it with other functionality. For example, `--short`, `--json`, `--client-only`, etc.
- Introduce related sub-commands. For example, `<cli> version check-update` to check if there is a new release.

It requires more typing than `<cli> -v`, but you can add aliases, such as `<cli> ver` or even `<cli> v`. However, this is not something I saw often 🤔.

### Decision

I guess all of us want to have our cake and eat it too 😎. In this one case, it's possible. Personally, I would suggest having the `--version` flag to print the short version, and the `<cli> version` command to support more complex use cases.

---

## ![bubbles](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/whc7h93donokq75aiuw6.png)


## What to collect

Version information is your CLI's DNA. The data that you collect and display is later useful for the CLI authors and its users. Users want to easily check if they're using the right version. Authors mostly want to find out what revision of the source code was used for a given CLI version, e.g. in case of a bug report.

Users are the most important part of our projects. Let's start with their needs first!
- Get the CLI version — the best option is to use [semantic versioning](https://semver.org/) here.
    -  Get both the client and the server version, if available.

      > **Note:** Make sure that you have an option to print the client version only.

- Be informed about using an outdated version or [version skew](https://www.linkedin.com/pulse/managing-version-skew-len-bass/).
- Get the URL to the GitHub release and/or documentation.

For you as the author, displaying these details can come in handy:
- Git commit — the SHA for the commit that a given version was built from.
- Git state — for example, `clean` if there are no local code changes when this binary was built; `dirty` if the binary was built from locally modified code.
- Build date — the date when the binary was built.
- Programming language version — the version of the language that was used to compile your binary.
- Build user — the creator of the binary. It can be also the name of your automation, e.g. `goreleaser-1.11.2`.
- Related components' versions — the versions of the binaries that are used under the hood, e.g. Git, Docker. A good example, of collecting such information is Minikube ([example](https://github.com/mszostok/cli-analysis#system)).

---

## ![chat](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/usxw0ig5u9igzcl64dvt.png)

## Output options

The final aspect is how to present the data to the user. The most popular option is to print a human-readable message if no flags are specified.
However, for doing version check, options such as short, YAML, and JSON are useful.

| Name                                     | Description                                          |
|------------------------------------------|------------------------------------------------------|
| --output=  json &#124; yaml &#124; short | Displays the version information in the JSON format. |
| --client-only                            | Displays only the client version.                    |

As a result, on a CI pipeline, we can easily detect a mismatched version:
```bash
EXPECTED_VERSION="1.42.0"
GOT_VER=$(golangci-lint version --format=short 2>&1)

if [[ "${GOT_VER}" != "${GOLANGCI_LINT_VERSION}" ]]; then
  echo "✗ golangci-lint version mismatch, expected ${EXPECTED_VERSION}, available ${GOT_VER}"
  exit 1
fi
```

An unusual finding for me was that Helm CLI supports Go templates. For example, `--template='Version: {{.Version}}'` outputs 'Version: v3.2.1'. I personally don't see how it adds value if you already support the JSON and/or YAML output format. Do you find it useful?

---

## ![diamond](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/372jr1b5g87xudhi5g69.png)



## Bells and whistles

The below practices stood out as a nice touch for me as a user.

### Upgrade notice

Inform your user when a newer version was released. Additionally, you can detect what option was used for installing your CLI, e.g. `brew`, `apt`, etc., and suggest a command ready to copy-paste.

Let me show you a few examples:

1. GitHub CLI. The upgrade notice is enabled by default. To disable it, you need to export the `GH_NO_UPDATE_NOTIFIER` environment variable. It checks for new releases once every 24 hours, and displays an upgrade notice on stderr error if a newer version was found. Here is an example output:

    ```text
    A new release of gh is available: 2.5.1 → v2.5.2
    To upgrade, run: brew update && brew upgrade gh
    https://github.com/cli/cli/releases/tag/v2.5.2
    ```


2. OpenFaaS CLI. The upgrade notice is enabled by default. To disable it, you need to specify the `--warn-update=false` flag. Here is an example output:

    ```text
    Your faas-cli version (0.14.5) may be out of date. Version: 0.14.6 is now available on GitHub.
    ```


3. Terraform CLI. The upgrade notice is always enabled, however, when the JSON format is used, it only shows the `terraform_outdated` property set to `true`.

    ```text
    Terraform v1.2.8
    on darwin_amd64

    Your version of Terraform is out of date! The latest version
    is 1.2.9. You can update by downloading from https://www.terraform.io/downloads.html
    ```

### Version skew warning

Print a warning message if the difference between the CLI and related components is greater than the supported version skew.

The related components include but are not limited to the server that your CLI connects to, and/or other binaries that are used under the hood, such as Git, Docker, etc.

---

## Summary

You may think: printing CLI version… isn't that obvious? Don't you have better things to do instead of thinking how to implement it? And yet, we can find a lot of different approaches. It bothered me sooooo much that I decided to do this research and structure my knowledge in this area. I hope, that you found it useful, too. **Thanks for reading!**

## <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fnyg0x82jexb7ivr9ird.png" />

<center>Follow [@m_szostok](https://twitter.com/m_szostok) on Twitter to get the latest news.</center>

---

## Bonus

If you use [Go](https://go.dev/) to create your CLI, you don't need to reinvent the wheel - I got your back! You can use the [`version`](https://szostok.io/projects/version) package, which was made to remove repetitiveness in implementing the version command.

The details about the CLIs that I analyzed can be found at [mszostok/cli-analysis](https://github.com/mszostok/cli-analysis).

![preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2gms6trx0lquko79fnn3.gif)