DEV Community

loading...
Cover image for Boost your productivity by creating your own CLI command with typescript (Part 1) ๐Ÿ”ฅ

Boost your productivity by creating your own CLI command with typescript (Part 1) ๐Ÿ”ฅ

Raphael MANSUY
CTO | ELITIZON ๐Ÿš€ Opinions are my own ๐Ÿต Green Tea addict ๐Ÿ‘ท๐Ÿปโ€โ™‚๏ธ Maker ๐Ÿ“š Lifelong learner
Originally published at elitizon.com ใƒปUpdated on ใƒป6 min read

Context

Your daily productivity can be greatly improved ๐Ÿš€ if you can automate all the tasks you used to do.

Thanks to node, npm, npx and typescript: creating a CLI command and making it available on your system or globally has never been easier.

As an example, we will create a CLI command to get a stock value for a symbol. This command will be called pique-sous (as a reference to the Disney character "Uncle $crooge" in french ๐Ÿ˜„ ๐Ÿฆ†)

$ pique-sous MSFT SFIX GOOG
Enter fullscreen mode Exit fullscreen mode

Result:

Retrieving stock information for MSFT at date 2021-01-10T01:37:57.574Z
{
  language: 'en-US',
  region: 'US',
  quoteType: 'EQUITY',
  quoteSourceName: 'Delayed Quote',
  regularMarketOpen: 218.68,
  exchange: 'NMS',
  shortName: 'Microsoft Corporation',
  longName: 'Microsoft Corporation',
  messageBoardId: 'finmb_21835',
  exchangeTimezoneName: 'America/New_York',
  exchangeTimezoneShortName: 'EST',
  gmtOffSetMilliseconds: -18000000,
  market: 'us_market',
  esgPopulated: false,
  displayName: 'Microsoft',
  symbol: 'MSFT'
}

Enter fullscreen mode Exit fullscreen mode

The final results in available at https://github.com/raphaelmansuy/pique-sous and published at https://www.npmjs.com/package/pique-sous.

This article was originally published at https://www.elitizon.com/

๐Ÿ— 6 Easy steps to make it happen !

Step1 : creating a basic typescript project

โœ… Create a directory called pique-sous

$ mkdir ./pique-sous
Enter fullscreen mode Exit fullscreen mode

โœ… create a file index.ts under pique-sous

$ cd ./pique-sous
$ touch ./index.ts
Enter fullscreen mode Exit fullscreen mode

As a result, you should have:

pique-sous
โ””โ”€โ”€ index.ts
Enter fullscreen mode Exit fullscreen mode

โœ… Edit the index.ts and add a simple command for testing such as:

const currentDateAndTime = new Date().toIsoString()

console.log(currentDateTime)
Enter fullscreen mode Exit fullscreen mode

โœ… Execute and test the file with ts-node


npx ts-node index.ts

Enter fullscreen mode Exit fullscreen mode

npx is a tool from the NPM registry allowing executing commands without installation
ts-node is a node version supporting typescript directly

As a result you should have something like this:

2021-01-10T02:37:49.683Z
Enter fullscreen mode Exit fullscreen mode

Step2 : make the file executable

โœ… Modify the index.ts file such as

#!/usr/bin/env npx ts-node

const currentDateAndTime = new Date().toIsoString()

console.log(currentDateTime)
Enter fullscreen mode Exit fullscreen mode

The first line #!/usr/bin/env npx ts-node specifies that the file must be executed by npx and ts-node

โœ… Add the executable permission to the index.ts file

$ chmod u+x ./index.ts
Enter fullscreen mode Exit fullscreen mode

โœ… Test the file

$ ./index.ts
Enter fullscreen mode Exit fullscreen mode

Results:

$ ./index.ts
$ 2021-01-10T03:24:43.190Z
Enter fullscreen mode Exit fullscreen mode

Step 3: package the project

โœ… Add package.json file

Inside the directory use the npm command to create a package.json file

$ npm init
Enter fullscreen mode Exit fullscreen mode

Answer the questions:

package name: (pique-sous) 
version: (1.0.0) 
description: A simple package
entry point: (index.js) bin/index.js
test command: 
git repository: 
keywords: 
author: raphael mansuy
license: (ISC) MIT
About to write to /Users/raphaelmansuy/Projects/Github/raphaelmansuy/ElitizonWeb/data/blog/2021/01-09-how-to-create-a-cli-command-with-typescript/steps/step01/pique-sous/package.json:

{
  "name": "pique-sous",
  "version": "1.0.0",
  "description": "A simple package",
  "main": "bin/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "raphael mansuy",
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode

โœ… Configure compilation from typescript to javascript

Create a file called tsconfig.json as follow:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2017",
    "lib": ["es2015"],
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "bin",
    "baseUrl": ".",
    "paths": {
      "*": ["node_modules/*", "src/types/*"]
    },
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  },
  "include": ["src/**/*"]
}
Enter fullscreen mode Exit fullscreen mode

โœ… Create a src directory and move the index.ts in the ./src directory

$ mkdir ./src
$ mv ./index.ts ./src
Enter fullscreen mode Exit fullscreen mode

Results:

.
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ src
โ”‚ย ย  โ””โ”€โ”€ index.ts
โ””โ”€โ”€ tsconfig.json

1 directory, 3 files

Enter fullscreen mode Exit fullscreen mode

โœ… Add typescript support for the compilation

$ yarn add typescript @types/node -D
Enter fullscreen mode Exit fullscreen mode

Result:

yarn add v1.22.10
info No lockfile found.
[1/4] ๐Ÿ”  Resolving packages...
[2/4] ๐Ÿšš  Fetching packages...
[3/4] ๐Ÿ”—  Linking dependencies...
[4/4] ๐Ÿ”จ  Building fresh packages...
success Saved lockfile.
success Saved 2 new dependencies.
info Direct dependencies
โ”œโ”€ @types/node@14.14.20
โ””โ”€ typescript@4.1.3
info All dependencies
โ”œโ”€ @types/node@14.14.20
โ””โ”€ typescript@4.1.3
โœจ  Done in 1.44s.
Enter fullscreen mode Exit fullscreen mode

The package.json should look like this:

{
  "name": "pique-sous",
  "version": "1.0.0",
  "description": "A simple package",
  "main": "bin/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "raphael mansuy",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^14.14.20",
    "typescript": "^4.1.3"
  }
}
Enter fullscreen mode Exit fullscreen mode

โœ… Edit the package.json as follow

๐Ÿ‘‰ add "bin" entry with value "bin/index.js"
๐Ÿ‘‰ add "build" command in "scripts"

{
  "name": "pique-sous",
  "version": "1.0.0",
  "description": "A simple package",
  "main": "bin/index.js",
  "bin": "bin/index.js",
  "scripts": {
    "build": "tsc",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "raphael mansuy",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^14.14.20",
    "typescript": "^4.1.3"
  }
}
Enter fullscreen mode Exit fullscreen mode

โœ… Edit the index.ts as follow

๐Ÿ‘‰ replace npx ts-node by node because the result of the compilation by typescript compiler will be a javascript file

#!/usr/bin/env node

const currentDateTime = new Date().toISOString()

console.log(currentDateTime)
Enter fullscreen mode Exit fullscreen mode

โœ… Build

yarn build
Enter fullscreen mode Exit fullscreen mode

Results:

yarn run v1.22.10
$ tsc
โœจ  Done in 1.66s.
Enter fullscreen mode Exit fullscreen mode

The bin directory contains now the outcome of the compilation process:

$ tree ./bin
./bin
โ”œโ”€โ”€ index.js
โ””โ”€โ”€ index.js.map

0 directories, 2 files
Enter fullscreen mode Exit fullscreen mode

โœ… Make ./bin/index.js executable

chmod u+x ./bin/index.js
Enter fullscreen mode Exit fullscreen mode

โœ… Test the result

./bin/index.js
Enter fullscreen mode Exit fullscreen mode

Result:

โฏ pique-sous
2021-01-10T04:33:08.303Z
Enter fullscreen mode Exit fullscreen mode

Step 4: publish the command locally

๐Ÿ”ฅ The command can now be made available for use locally:

$ yarn link --global
Enter fullscreen mode Exit fullscreen mode

Result:

yarn link v1.22.10
success Registered "pique-sous".
info You can now run `yarn link "pique-sous"` in the projects where you want to use this package and it will be used instead.
โœจ  Done in 0.04s.
Enter fullscreen mode Exit fullscreen mode

๐ŸŽ‰ ๐Ÿ’ช We can now use the command from everywhere

โฏ pique-sous
2021-01-10T05:45:10.586Z
Enter fullscreen mode Exit fullscreen mode

๐ŸŒˆ ๐Ÿ™ˆ We can unregister the command with:

$ yarn unlink --global
Enter fullscreen mode Exit fullscreen mode

Step 5: publish the cli command on www.npmjs.org

๐Ÿ‘‰ First, you need to signup and create an account on https://www.npmjs.com/
๐Ÿ‘‰ ๐Ÿงจ You need to be sure that the name of your package is not taken on npmjs.com, the name of the package in the package.json must be modified it the name already exists on npm.

Type the following command in the base directory:

$ npm publish
Enter fullscreen mode Exit fullscreen mode

Enter your npm credentials

Result:

npm notice 
npm notice ๐Ÿ“ฆ  pique-sous@1.0.0
npm notice === Tarball Contents === 
npm notice 133B bin/index.js    
npm notice 198B bin/index.js.map
npm notice 372B package.json    
npm notice 100B src/index.ts    
npm notice 364B tsconfig.json   
npm notice === Tarball Details === 
npm notice name:          pique-sous                              
npm notice version:       1.0.0                                   
npm notice filename:      pique-sous-1.0.0.tgz                    
npm notice package size:  810 B                                   
npm notice unpacked size: 1.2 kB                                  
npm notice shasum:        6c8aea7b85c125a2d9dbbeec81d15ef94b07240a
npm notice integrity:     sha512-ozbnViT18DSUI[...]FquBcXBSV8f2g==
npm notice total files:   5                                       
npm notice 
Enter fullscreen mode Exit fullscreen mode

Your command is now published on npm and be installed or executed from everywhere.

Example:

Execution without formal installation:

npx pique-sous
Enter fullscreen mode Exit fullscreen mode

Or global installation:

npm install -g pique-sous
Enter fullscreen mode Exit fullscreen mode

Step 6: Add Yahoo finance get stocks information

โœ… Install axios library

yarn add axios
Enter fullscreen mode Exit fullscreen mode

โœ… Add 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

โœ… Add file "./src/getVersion.ts"

import * as fs from "fs"
import * as Path from "path"

export const getVersion = () => {
  const packageJSONPath = Path.resolve(__dirname, "../package.json")
  const content = fs.readFileSync(packageJSONPath, { encoding: "utf8" })
  const config = JSON.parse(content)
  return config.version
}
Enter fullscreen mode Exit fullscreen mode

โœ… Modify ./src/index.ts

#!/usr/bin/env node

import { getSingleStockInfo } from "./getStock"
import { getVersion } from "./getVersion"

/**
 *  return the arguments of the command except node and index.ts
 */
const getArgs = () => {
  // We retrieve all the command argumnts except the first 2
  const args = process.argv.slice(2)
  return args
}

/**
 * Command Help
 */
const printCommandHelp = () => {
  const version = getVersion()
  const help = `
pique-sous (version: ${version})

A simple command to retrieve stock information.

Example:

$ pique-sous MSFT SFIX GOOG

`
  console.log(help)
}

const symbols = getArgs()

// Print help if no arguments
if (symbols.length === 0) {
  printCommandHelp()
  getVersion()
  process.exit(0)
}

const now = new Date().toISOString()

// Call the yahoo API for each symbol and display the result on the console
symbols.forEach((symbol) => {
  console.log(`Retrieving stock information for ${symbol} at date ${now}`)
  getSingleStockInfo(symbol).then(console.log)
})
Enter fullscreen mode Exit fullscreen mode

โœ… Increment the version number in package.json to 1.1.0 ("version")

{
  "devDependencies": {
    "@types/axios": "^0.14.0",
    "@types/node": "^14.14.20"
  },
  "name": "pique-sous",
  "version": "1.1.0",
  "description": "A simple command to retrieve stock information",
  "main": "./bin/index.js",
  "dependencies": {
    "axios": "^0.21.1",
    "typescript": "^4.1.3"
  },
  "bin": "bin/index.js",
  "scripts": {
    "build": "tsc",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "cli",
    "node",
    "typescript",
    "npm",
    "stock",
    "yahoo finance"
  ],
  "contributors": [
    "raphaelmansuy"
  ],
  "repository": {
    "url": "https://github.com/raphaelmansuy/pique-sous.git",
    "type": ""
  },
  "author": {
    "email": "raphael.mansuy@gmail.com",
    "name": "raphaelmansuy"
  },
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode

โœ… Build a new version

$ yarn build
Enter fullscreen mode Exit fullscreen mode

โœ… Test locally

Publish the component:

$ yarn link --global
Enter fullscreen mode Exit fullscreen mode

โœ… Execute

$ pique-sous MSFT
Enter fullscreen mode Exit fullscreen mode

Result:

Retrieving stock information for MSFT at date 2021-01-10T06:11:41.305Z
{
  language: 'en-US',
  region: 'US',
  quoteType: 'EQUITY',
  quoteSourceName: 'Delayed Quote',
  triggerable: true,
  currency: 'USD',
  exchange: 'NMS',
  shortName: 'Microsoft Corporation',
  longName: 'Microsoft Corporation',
  messageBoardId: 'finmb_21835',
  exchangeTimezoneName: 'America/New_York',
  exchangeTimezoneShortName: 'EST',
  gmtOffSetMilliseconds: -18000000,

  ...
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅ The package can now be republished on npm ๐Ÿ’ช.

Discussion (2)

Collapse
raphaelmansuy profile image
Collapse
raphaelmansuy profile image
Forem Open with the Forem app