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.