Boost your productivity by creating your own CLI command with typescript and OCLIF (Part 2)

nodejs, typescript, npm, npm, oclif
Wednesday, January 20, 2021

Context

OCLIF is a wonderful framework that makes it easy to develop a professional CLI command. Let's see how we can create a CLI command that will delight your end-user in less than 3 minutes.

The final project is published on https://github.com/raphaelmansuy/matcha-stock

Add a 🌟 on the project if you have enjoyed this tutorial ❤️

1$ matcha-stock -symbol=MSFT

Lets go ! 🚀

Create a new command with OCLIF (30 seconds ⏰)

1npx oclif single matcha-stock
2cd matcha-stock
3./bin.run

Result:

OCLIF generates a starting project for my command.

1❯ npx oclif single matcha-stock
2
3 _-----_ ╭──────────────────────────╮
4 | | │ Time to build a │
5 |--(o)--| │ single-command CLI with │
6 `---------´ │ oclif! Version: 1.16.1 │
7 ( _´U`_ ) ╰──────────────────────────╯
8 /___A___\ /
9 | ~ |
10 __'.___.'__
11 ´ ` |° ´ Y `
12
13 npm package name matcha-stock
14? command bin name the CLI will export matcha-stock
15? description A command to get stocks information
16? author Raphael MANSUY @raphaelmansuy
17? version 0.0.1
18? license MIT
19? Who is the GitHub owner of repository (https://github.com/OWNER/repo) raphaelmansuy
20? What is the GitHub name of repository (https://github.com/owner/REPO) matcha-stock
21? Select a package manager yarn
22? TypeScript Yes
23? Use eslint (linter for JavaScript and Typescript) Yes
24? Use mocha (testing framework) Yes

Code created by OCLIF

1├── README.md
2├── bin
3│ ├── run
4│ └── run.cmd
5├── package.json
6├── src
7│ └── index.ts
8├── test
9│ ├── index.test.ts
10│ ├── mocha.opts
11│ └── tsconfig.json
12├── tsconfig.json
13└── yarn.lock

Content of src/index.ts

1import { Command, flags } from "@oclif/command"
2
3class MatchaStock extends Command {
4 static description = "describe the command here"
5
6 static flags = {
7 // add --version flag to show CLI version
8 version: flags.version({ char: "v" }),
9 help: flags.help({ char: "h" }),
10 // flag with a value (-n, --name=VALUE)
11 name: flags.string({ char: "n", description: "name to print" }),
12 // flag with no value (-f, --force)
13 force: flags.boolean({ char: "f" })
14 }
15
16 static args = [{ name: "file" }]
17
18 async run() {
19 const { args, flags } = this.parse(MatchaStock)
20
21 const name = flags.name ?? "world"
22 this.log(`hello ${name} from ./src/index.ts`)
23 if (args.file && flags.force) {
24 this.log(`you input --force and --file: ${args.file}`)
25 }
26 }
27}
28
29export = MatchaStock

✅ OCLIF has created a template class that represents the skeleton of my command.

Starting from the generated code I can:

  • 💪 add flags such as --symbol
  • 🏗 modify the implementation of the run() method

Add the support of the flag --symbol (40 seconds ⏰)

1import { Command, flags } from "@oclif/command"
2
3class MatchaStock extends Command {
4 static description =
5 "A simple command to retrieve stock information from Yahoo Finance"
6
7 static flags = {
8 // add --version flag to show CLI version
9 version: flags.version({ char: "v" }),
10 help: flags.help({ char: "h" }),
11 // Add Support of of -symbol flag
12 // flag with a value (-s, --symbol=VALUE)
13 symbol: flags.string({
14 char: "s", // Alias for my flag
15 description: "stock symbol to retrieve", // A description of the symbol flag
16 required: true, // The flag symbol is required 👉 The command will abort of the flag is not provide
17 helpValue: "MSFT" // An example of flag value (MSFT is the symbol for Microsoft)
18 })
19 }
20
21 async run() {
22 const { args, flags } = this.parse(MatchaStock)
23
24 this.log(`Get Symbol=${flags.symbol} from ./src/index.ts`)
25 }
26}
27
28export = MatchaStock

I can now test my command

✅ With no flag

1./bin

result:

1› Error: Missing required flag:
2 › -s, --symbol SYMBOL stock symbol to retrieve
3 › See more help with --help

✅ With flag -help

1./bin -help

result:

1❯ ./bin/run -help
2A simple command to retrieve stock information from Yahoo Finance
3
4USAGE
5\$ matcha-stock
6
7OPTIONS
8-h, --help show CLI help
9-s, --symbol=MSFT (required) stock symbol to retrieve
10-v, --version show CLI version

✅ With flag --symbol

1./bin --symbol GOOG

result:

1❯ ./bin/run -symbol=GOOG
2Get Symbol=ymbol=GOOG from ./src/index.ts

Add the business logic (60 seconds ⏰)

👉 Add axios as our http library.

1yarn add axios

👉 Add the file ./src/getStock.ts

1import axios from "axios"
2
3export const getSingleStockInfo = async (stock: string) => {
4 if (!stock) {
5 throw new Error("Stock symbol argument required")
6 }
7
8 if (typeof stock !== "string") {
9 throw new Error(
10 `Invalid argument type for stock argument. Required: string. Found: ${typeof stock}`
11 )
12 }
13
14 const url = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=${stock}`
15
16 const res = await axios.get(url)
17
18 const { data } = res
19 if (
20 !data ||
21 !data.quoteResponse ||
22 !data.quoteResponse.result ||
23 data.quoteResponse.result.length === 0
24 ) {
25 throw new Error(`Error retrieving info for symbol ${stock}`)
26 }
27
28 const quoteResponse = data.quoteResponse.result[0]
29
30 return quoteResponse
31}

👉 Modify the src/index.ts file such as:

1import { Command, flags } from "@oclif/command"
2import { getSingleStockInfo } from "./getStocks"
3class MatchaStock extends Command {
4 static description = `A simple command to retrieve stock information from Yahoo Finance.\nA simple command to retrieve stock information from Yahoo Finance.\n\n Created with ❤️ by Elitizon (https://www.elitizon.com)`
5
6 static flags = {
7 // add --version flag to show CLI version
8 version: flags.version({ char: "v" }),
9 help: flags.help({ char: "h" }),
10 // Add Support of of -symbol flag
11 // flag with a value (-s, --symbol=VALUE)
12 symbol: flags.string({
13 char: "s", // Alias for my flag
14 description: "stock symbol to retrieve", // A description of the symbol flag
15 required: true, // The flag symbol is required 👉 The command will abort of the flag is not provide
16 helpValue: "MSFT" // An example of flag value (MSFT is the symbol for Microsoft)
17 })
18 }
19
20 async run() {
21 const { flags } = this.parse(MatchaStock)
22 const res = await getSingleStockInfo(flags.symbol)
23 // Print the result as tabular
24 console.table(res)
25 }
26}
27
28export = MatchaStock

👉 Test the command

1❯ ./bin/run -s=MSFT
2┌───────────────────────────────────┬─────────────────────────┐
3(index) │ Values │
4├───────────────────────────────────┼─────────────────────────┤
5│ language │ 'en-US'
6│ region │ 'US'
7│ quoteType │ 'EQUITY'
8│ quoteSourceName │ 'Delayed Quote'
9│ triggerable │ true
10│ currency │ 'USD'
11│ firstTradeDateMilliseconds │ 511108200000
12│ priceHint │ 2
13│ marketState │ 'POSTPOST'
14│ postMarketChangePercent │ 0.31417143

Publish the command to NPM.org (30 seconds ⏰)

1npm publish

✅ The package is now published on npm.org at https://www.npmjs.com/package/matcha-stock

  • 👉 You have to change the name of the package if the package is already registered on NPM.
  • 👉 The package version must be updated each time you publish

Test your command (10 seconds ⏰)

1npm install -g matcha-stock
2matcha-stock -s=MSFT

Conclusion

OCLIF is an impressive framework. With OCLIF it's easy to create:

  • Single-Command CLI
  • Multi-command CLI

Main features:

  • 🕺 Flag/Argument parsing
  • 🚀 Super Speed
  • 🏗 Include a CLI generator to speed the development of commands
    • Testing Helpers
    • Auto-Documentation
    • Plugins
    • Hooks

OCLIF is available on Github and maintained by Matt Graham, Paul Elliott and Chris Castle and funded by Heroku 🎉

Subscribe to our Newsletter

We deliver high quality blog posts written by professionals monthly. And we promise no spam.

elitizon ltd.

© 2020 elitizon ltd. All Rights Reserved.