NPM and Node.js are the perfect match for building CLIs quickly. Learn best practices for getting going quickly with your next CLI
Presented @ Boston Node Meetup #12
Bhubaneswar🌹Call Girls Bhubaneswar ❤Komal 9777949614 💟 Full Trusted CALL GIRL...
Making CLIs with Node.js
1. Making CLIs with Node.js
🏆 Terminals FTW 🏆
Joe Lust mabl engineer @lustcoder
2. About Me: Joseph Lust
Engineer
■ Building the web for two decades
▲ PHP, Node, Scala, Java, Typescript
■ Currently building mabl testing cloud on GCP
■ Co-organizer GDG Cloud Boston Meetup
@lustcoder
4. Why do you need a CLI? Do you?
■ Easily automate processes
▲ Common developer actions
▲ Helpful SDK wrapper
■ Harness client side power
▲ File system access
▲ Complex non-browser use cases
4
5. CLI tie breakers
■ Very simple UIs
▲ No pixels to polish (e.g. React + Redux)
▲ Allows rapid feature delivery
■ Easily combine with workflows
▲ Chain bash/CLI tools together (stdin → stdout, exit codes)
▲ Users can create novel combinations
■ No Hosting, No Deployments
▲ Runs on customer hardware, no-ops!
5
6. Your technology options
■ Node.js + NPM
▲ Ubiquitous NPM install base
▲ Pervasive CLI tooling
▲ One line installs
■ Alternatives
▲ Python + PIP
▲ Go binaries
▲ Ruby
6
9. You only need one file
■ NPM is a CLI, afterall!
■ Just tell NPM what to run
▲ the CLI name (e.g. “boston-node”)
▲ the JS file to launch (e.g. cli.js)
9
# package.json
...
"bin": {
"bode": "./cli.js"
},
...
# package.json
...
"bin": {
"boston-node": "./cli.js"
},
...
11. You can alias too
■ Everything in bin winds up on the PATH
■ Add helpful aliases as well
■ Shorter is better (life is short)
▲ boston-node → bode
11
# package.json
...
"bin": {
"bode": "./cli.js",
"boston-node": "./cli.js"
},
...
ProTip™ developers hate typing. Keep it short.
12. Just link it
■ You can try out your CLI by linking
▲ npm link
■ Don’t forget to cleanup
▲ npm unlink
12
13. Inspiration: Beg, Barrow, & Attribute
■ Pick your favorite CLI
▲ Investigate it’s NPM listing: dependencies
▲ Read the source code
▲ Look in the package
● How’d they do that? 🤔
13
$ URL=$(npm view jest-cli@latest dist.tarball)
$ wget $URL -O - | tar xvz
14. Putting the “I” in CLI: the Interface
■ Design your interface
▲ Ergonomic layout
▲ Easily guessed/remember
▲ Plain English flags
14
$ <cli-name> <noun> <verb> <subject> --flag <value>
$ aws s3 ls s3://bucket --recursive
Flag valuePositional ArgNested Commands
15. Putting the “I” in CLI: the Interface
■ Don’t reinvent the wheel
■ Use argument parser libs
▲ commander (39k)
▲ yargs (17k)
● nested command directories
● autocomplete generator
15
16. Putting the “I” in CLI: the Interface
■ Don’t reinvent the wheel
■ Use argument parser libs
▲ commander (39k)
▲ yargs (17k)
● nested command directories
● autocomplete generator
16
17. Hang on to that State
■ Don’t reinvent the wheel
■ There are libs for that!
▲ conf
▲ persist rich configs in user HOME
▲ update your config schema (migrations)
▲ supports secret encryption/keyrings
17
18. CLIs can be Sexy
■ Spinners - ora
■ Rich forms - inquirer
■ Pretty text - chalk
■ Banners - figlet
■ Tables - cli-table3
■ Progress bar- progress
18
19. Say NO to Banal CLIs
19
Sorry PDF viewers, these are animated!
20. Be bashful
■ What would Stephen (Bourne) Do? (WWBD)
▲ Be recombinable
▲ Use Standard In (stdin), Out (stdout), and Error (stderr)
▲ Return error codes when trouble happens
● process.exit(0); // all is well
20
$ curl http://no-such-place.no-way
$ echo “Error code: [$?]”
> Error Code [6]
21. Log All the Things!
■ Enable a --verbose mode for debugging/power users
■ Logging libs to the rescue
▲ winston, bunyun, pino, morgan (http)
▲ control log.debug() output
■ Save detailed logs to a file for debugging/support
▲ ~/.npm/node_modules/my-cli/logs/aw-snap.log
■ Simplifies testing
▲ Don’t use console.log()
21
22. Keep Everyone Up to Date
■ Don’t reinvent updates/notifications
■ Use what npm uses
▲ update-notifier
▲ automatically checks NPM registry for updates
▲ just set the nagging interval
22
23. Don’t forget the tests!
■ Easy to test, just call your yargs parser!
■ Bash script for sanity checks
■ Users will install your CLI on multiple OSes
▲ Use GitHub Workflows to test Win/OSX/Linux
23
24. Be bashful
24
// Simple Parser Test
describe('application commands', () => {
it('applications describe requires app id', async () => {
const cmd = 'describe';
const parser: Argv<CrudCommand> = yargs.command(myCommand);
const [,out]= await parseArguments<CrudCommand>(parser, cmd);
expect(out).to.contain(
'Not enough arguments: got 0, need at least 1',
);
});
});