At my last job I wrote the CLI for deploying AI/ML models. I had never written a CLI before. It's not particularly difficult to do, but they are deceivingly difficult to get "just right". Since my time at that company I've been writing and deploying a lot of sites. Part of my goal of these projects is to be as low complexity/resource demanding as necessary as I'll be footing the server bill each month.
So I started demoing different providers and their offerings. GCP was nice but not ideal for my use case. I had already used AWS before. Then I stumbled upon Cloudflare - they offer free static site hosting that is entirely easier to set up compared to GCP. So I migrated my blog again and came up with a few static sites. You can deploy projects via GUI, or from the command line via
Auth and prompting the user
The first thing that stuck out was: if the user forgot to pass their token, launch an OAuth workflow via browser. All I had to do was log in to Cloudflare in my browser, with my username and password saved in the browser. This lets the user's browser handle the username and password concern, which is desirable.
$ npx wrangler pages deploy html/ --project-name myproject --skip-caching Attempting to login via OAuth... Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=[redacted]&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=lcykA2LiHgli6Az1DFDjbquPHBXWRlnr&code_challenge=_9Jkt0gMzkwSfM-vy9xrSC-yy9y7fa0OYCf5zMYS1Pg&code_challenge_method=S256 Successfully logged in. 🌍 Uploading... (2/2) ✨ Success! Uploaded 2 files (0.74 sec) ✨ Deployment complete! Take a peek over at [redacted]
Seeing is knowing
Command line semantic consistency and visualizing the taxonomy2. I remember implementing a command that was not intuitive and having to refactor it. This could have been avoided by drawing out the planned command(s).
Looking for inspiration (and choosing the right one)
Whenever I faced a problem where I had no experience in, or had no intuition for, I would begin searching Github for code that solves a similar problem. I did consult Heroku's CLI, but I passed over it. Cloudflare took inspiration in the form of "colon-delimited command namespacing". I like how it makes painfully clear what the command is operating on. User proofing your interfaces is difficult.
Avoid logic that requires passing in raw ID strings
The third takeaway in the Cloudflare blog post I am guilty of: my delete command required the GUID of the deployment to delete. Being able to issue a command based on a name is better user experience than having to paste a long string into the command line. I myself am so used to doing this that it simply doesn't bother me anymore, and so I never thought of it. It's dangerous to assume that your experience will be the same as others!
Designing a CLI just right is more difficult than you would think: it needs to be intuitive for all users with a variety of computing backgrounds. It needs collaboration to do well. While I didn't write the perfect CLI, I did add some interesting features that most CLIs don't have:
- Typo resilience - a mistyped command will still work (
deploy) by comparing string similarity to the list of available commands.
- Aliased commands. Sometimes users can't remember the command and type something similar but different (
list). A few of the commands were aliased like that.
- Customized command groupings. Most CLIs when invoked with
--helpgive you a large list of alphabetically sorted commands/options. This is notoriously difficult to read, so we hooked into the CLI framework and modified how commands are printed.