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

raphaelmansuy

Raphael MANSUY

Posted on January 20, 2021

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

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 โค๏ธ

$ matcha-stock -symbol=MSFT
Enter fullscreen mode Exit fullscreen mode

Lets go ! ๐Ÿš€

Create a new command with OCLIF (30 seconds โฐ)

npx oclif single matcha-stock
cd matcha-stock
./bin.run
Enter fullscreen mode Exit fullscreen mode

Result:

OCLIF generates a starting project for my command.

โฏ npx oclif single matcha-stock

     _-----_     โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
    |       |    โ”‚      Time to build a     โ”‚
    |--(o)--|    โ”‚  single-command CLI with โ”‚
   `---------ยด   โ”‚  oclif! Version: 1.16.1  โ”‚
    ( _ยดU`_ )    โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
    /___A___\   /
     |  ~  |
   __'.___.'__
 ยด   `  |ยฐ ยด Y `

 npm package name matcha-stock
? command bin name the CLI will export matcha-stock
? description A command to get stocks information
? author Raphael MANSUY @raphaelmansuy
? version 0.0.1
? license MIT
? Who is the GitHub owner of repository (https://github.com/OWNER/repo) raphaelmansuy
? What is the GitHub name of repository (https://github.com/owner/REPO) matcha-stock
? Select a package manager yarn
? TypeScript Yes
? Use eslint (linter for JavaScript and Typescript) Yes
? Use mocha (testing framework) Yes
Enter fullscreen mode Exit fullscreen mode

Code created by OCLIF

โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ bin
โ”‚   โ”œโ”€โ”€ run
โ”‚   โ””โ”€โ”€ run.cmd
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ src
โ”‚   โ””โ”€โ”€ index.ts
โ”œโ”€โ”€ test
โ”‚   โ”œโ”€โ”€ index.test.ts
โ”‚   โ”œโ”€โ”€ mocha.opts
โ”‚   โ””โ”€โ”€ tsconfig.json
โ”œโ”€โ”€ tsconfig.json
โ””โ”€โ”€ yarn.lock
Enter fullscreen mode Exit fullscreen mode

Content of src/index.ts

import { Command, flags } from "@oclif/command"

class MatchaStock extends Command {
  static description = "describe the command here"

  static flags = {
    // add --version flag to show CLI version
    version: flags.version({ char: "v" }),
    help: flags.help({ char: "h" }),
    // flag with a value (-n, --name=VALUE)
    name: flags.string({ char: "n", description: "name to print" }),
    // flag with no value (-f, --force)
    force: flags.boolean({ char: "f" })
  }

  static args = [{ name: "file" }]

  async run() {
    const { args, flags } = this.parse(MatchaStock)

    const name = flags.name ?? "world"
    this.log(`hello ${name} from ./src/index.ts`)
    if (args.file && flags.force) {
      this.log(`you input --force and --file: ${args.file}`)
    }
  }
}

export = MatchaStock
Enter fullscreen mode Exit fullscreen mode

โœ… 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 โฐ)

import { Command, flags } from "@oclif/command"

class MatchaStock extends Command {
  static description =
    "A simple command to retrieve stock information from Yahoo Finance"

  static flags = {
    // add --version flag to show CLI version
    version: flags.version({ char: "v" }),
    help: flags.help({ char: "h" }),
    // Add Support of of -symbol flag
    // flag with a value (-s, --symbol=VALUE)
    symbol: flags.string({
      char: "s", // Alias for my flag
      description: "stock symbol to retrieve", // A description of the symbol flag
      required: true, // The flag symbol is required ๐Ÿ‘‰ The command will abort of the flag is not provide
      helpValue: "MSFT" // An example of flag value (MSFT is the symbol for Microsoft)
    })
  }

  async run() {
    const { args, flags } = this.parse(MatchaStock)

    this.log(`Get Symbol=${flags.symbol} from ./src/index.ts`)
  }
}

export = MatchaStock
Enter fullscreen mode Exit fullscreen mode

I can now test my command

โœ… With no flag

./bin
Enter fullscreen mode Exit fullscreen mode

result:

 โ€บ   Error: Missing required flag:
 โ€บ    -s, --symbol SYMBOL  stock symbol to retrieve
 โ€บ   See more help with --help
Enter fullscreen mode Exit fullscreen mode

โœ… With flag -help

./bin -help
Enter fullscreen mode Exit fullscreen mode

result:

โฏ ./bin/run -help
A simple command to retrieve stock information from Yahoo Finance

USAGE
\$ matcha-stock

OPTIONS
-h, --help show CLI help
-s, --symbol=MSFT (required) stock symbol to retrieve
-v, --version show CLI version
Enter fullscreen mode Exit fullscreen mode

โœ… With flag --symbol

./bin --symbol GOOG
Enter fullscreen mode Exit fullscreen mode

result:

โฏ ./bin/run  -symbol=GOOG
Get Symbol=ymbol=GOOG from ./src/index.ts
Enter fullscreen mode Exit fullscreen mode

Add the business logic (60 seconds โฐ)

๐Ÿ‘‰ Add axios as our http library.

yarn add axios
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Add the file ./src/getStock.ts

import axios from "axios"

export const getSingleStockInfo = async (stock: string) => {
  if (!stock) {
    throw new Error("Stock symbol argument required")
  }

  if (typeof stock !== "string") {
    throw new Error(
      `Invalid argument type for stock argument. Required: string. Found: ${typeof stock}`
    )
  }

  const url = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=${stock}`

  const res = await axios.get(url)

  const { data } = res
  if (
    !data ||
    !data.quoteResponse ||
    !data.quoteResponse.result ||
    data.quoteResponse.result.length === 0
  ) {
    throw new Error(`Error retrieving info for symbol ${stock}`)
  }

  const quoteResponse = data.quoteResponse.result[0]

  return quoteResponse
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Modify the src/index.ts file such as:

import { Command, flags } from "@oclif/command"
import { getSingleStockInfo } from "./getStocks"
class MatchaStock extends Command {
  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)`

  static flags = {
    // add --version flag to show CLI version
    version: flags.version({ char: "v" }),
    help: flags.help({ char: "h" }),
    // Add Support of of -symbol flag
    // flag with a value (-s, --symbol=VALUE)
    symbol: flags.string({
      char: "s", // Alias for my flag
      description: "stock symbol to retrieve", // A description of the symbol flag
      required: true, // The flag symbol is required ๐Ÿ‘‰ The command will abort of the flag is not provide
      helpValue: "MSFT" // An example of flag value (MSFT is the symbol for Microsoft)
    })
  }

  async run() {
    const { flags } = this.parse(MatchaStock)
    const res = await getSingleStockInfo(flags.symbol)
    // Print the result as tabular
    console.table(res)
  }
}

export = MatchaStock
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Test the command

โฏ ./bin/run  -s=MSFT
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              (index)              โ”‚         Values          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚             language              โ”‚         'en-US'         โ”‚
โ”‚              region               โ”‚          'US'           โ”‚
โ”‚             quoteType             โ”‚        'EQUITY'         โ”‚
โ”‚          quoteSourceName          โ”‚     'Delayed Quote'     โ”‚
โ”‚            triggerable            โ”‚          true           โ”‚
โ”‚             currency              โ”‚          'USD'          โ”‚
โ”‚    firstTradeDateMilliseconds     โ”‚      511108200000       โ”‚
โ”‚             priceHint             โ”‚            2            โ”‚
โ”‚            marketState            โ”‚       'POSTPOST'        โ”‚
โ”‚      postMarketChangePercent      โ”‚       0.31417143        โ”‚

Enter fullscreen mode Exit fullscreen mode

Publish the command to NPM.org (30 seconds โฐ)

npm publish

Enter fullscreen mode Exit fullscreen mode

โœ… 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 โฐ)

npm install -g matcha-stock
matcha-stock -s=MSFT
Enter fullscreen mode Exit fullscreen mode

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 ๐ŸŽ‰

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
raphaelmansuy
Raphael MANSUY

Posted on January 20, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About