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 😄 🦆)
1$ pique-sous MSFT SFIX GOOG
Result:
1Retrieving stock information for MSFT at date 2021-01-10T01:37:57.574Z2{3 language: 'en-US',4 region: 'US',5 quoteType: 'EQUITY',6 quoteSourceName: 'Delayed Quote',7 regularMarketOpen: 218.68,8 exchange: 'NMS',9 shortName: 'Microsoft Corporation',10 longName: 'Microsoft Corporation',11 messageBoardId: 'finmb_21835',12 exchangeTimezoneName: 'America/New_York',13 exchangeTimezoneShortName: 'EST',14 gmtOffSetMilliseconds: -18000000,15 market: 'us_market',16 esgPopulated: false,17 displayName: 'Microsoft',18 symbol: 'MSFT'19}
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/
✅ Create a directory called pique-sous
1$ mkdir ./pique-sous
✅ create a file index.ts under pique-sous
1$ cd ./pique-sous2$ touch ./index.ts
As a result, you should have:
1pique-sous2└── index.ts
✅ Edit the index.ts and add a simple command for testing such as:
1const currentDateAndTime = new Date().toIsoString()23console.log(currentDateTime)
✅ Execute and test the file with ts-node
1npx ts-node index.ts
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:
12021-01-10T02:37:49.683Z
✅ Modify the index.ts file such as
1#!/usr/bin/env npx ts-node23const currentDateAndTime = new Date().toIsoString()45console.log(currentDateTime)
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
1$ chmod u+x ./index.ts
✅ Test the file
1$ ./index.ts
Results:
1$ ./index.ts2$ 2021-01-10T03:24:43.190Z
✅ Add package.json file
Inside the directory use the npm command to create a package.json file
1$ npm init
Answer the questions:
1package name: (pique-sous)2version: (1.0.0)3description: A simple package4entry point: (index.js) bin/index.js5test command:6git repository:7keywords:8author: raphael mansuy9license: (ISC) MIT10About 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:1112{13 "name": "pique-sous",14 "version": "1.0.0",15 "description": "A simple package",16 "main": "bin/index.js",17 "scripts": {18 "test": "echo \"Error: no test specified\" && exit 1"19 },20 "author": "raphael mansuy",21 "license": "MIT"22}
✅ Configure compilation from typescript to javascript
Create a file called tsconfig.json as follow:
1{2 "compilerOptions": {3 "module": "commonjs",4 "target": "es2017",5 "lib": ["es2015"],6 "moduleResolution": "node",7 "sourceMap": true,8 "outDir": "bin",9 "baseUrl": ".",10 "paths": {11 "*": ["node_modules/*", "src/types/*"]12 },13 "emitDecoratorMetadata": true,14 "experimentalDecorators": true15 },16 "include": ["src/**/*"]17}
✅ Create a src directory and move the index.ts in the ./src directory
1$ mkdir ./src2$ mv ./index.ts ./src
Results:
1.2├── package.json3├── src4│ └── index.ts5└── tsconfig.json671 directory, 3 files
✅ Add typescript support for the compilation
1$ yarn add typescript @types/node -D
Result:
1yarn add v1.22.102info No lockfile found.3[1/4] 🔍 Resolving packages...4[2/4] 🚚 Fetching packages...5[3/4] 🔗 Linking dependencies...6[4/4] 🔨 Building fresh packages...7success Saved lockfile.8success Saved 2 new dependencies.9info Direct dependencies10├─ @types/node@14.14.2011└─ typescript@4.1.312info All dependencies13├─ @types/node@14.14.2014└─ typescript@4.1.315✨ Done in 1.44s.
The package.json should look like this:
1{2 "name": "pique-sous",3 "version": "1.0.0",4 "description": "A simple package",5 "main": "bin/index.js",6 "scripts": {7 "test": "echo \"Error: no test specified\" && exit 1"8 },9 "author": "raphael mansuy",10 "license": "MIT",11 "devDependencies": {12 "@types/node": "^14.14.20",13 "typescript": "^4.1.3"14 }15}
✅ Edit the package.json as follow
👉 add "bin" entry with value "bin/index.js" 👉 add "build" command in "scripts"
1{2 "name": "pique-sous",3 "version": "1.0.0",4 "description": "A simple package",5 "main": "bin/index.js",6 "bin": "bin/index.js",7 "scripts": {8 "build": "tsc",9 "test": "echo \"Error: no test specified\" && exit 1"10 },11 "author": "raphael mansuy",12 "license": "MIT",13 "devDependencies": {14 "@types/node": "^14.14.20",15 "typescript": "^4.1.3"16 }17}
✅ 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
1#!/usr/bin/env node23const currentDateTime = new Date().toISOString()45console.log(currentDateTime)
✅ Build
1yarn build
Results:
1yarn run v1.22.102$ tsc3✨ Done in 1.66s.
The bin directory contains now the outcome of the compilation process:
1$ tree ./bin2./bin3├── index.js4└── index.js.map560 directories, 2 files
✅ Make ./bin/index.js executable
1chmod u+x ./bin/index.js
✅ Test the result
1./bin/index.js
Result:
1❯ pique-sous22021-01-10T04:33:08.303Z
🔥 The command can now be made available for use locally:
1$ yarn link --global
Result:
1yarn link v1.22.102success Registered "pique-sous".3info You can now run `yarn link "pique-sous"` in the projects where you want to use this package and it will be used instead.4✨ Done in 0.04s.
🎉 💪 We can now use the command from everywhere
1❯ pique-sous22021-01-10T05:45:10.586Z
🌈 🙈 We can unregister the command with:
1$ yarn unlink --global
👉 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:
1$ npm publish
Enter your npm credentials
Result:
1npm notice2npm notice 📦 pique-sous@1.0.03npm notice === Tarball Contents ===4npm notice 133B bin/index.js5npm notice 198B bin/index.js.map6npm notice 372B package.json7npm notice 100B src/index.ts8npm notice 364B tsconfig.json9npm notice === Tarball Details ===10npm notice name: pique-sous11npm notice version: 1.0.012npm notice filename: pique-sous-1.0.0.tgz13npm notice package size: 810 B14npm notice unpacked size: 1.2 kB15npm notice shasum: 6c8aea7b85c125a2d9dbbeec81d15ef94b07240a16npm notice integrity: sha512-ozbnViT18DSUI[...]FquBcXBSV8f2g==17npm notice total files: 518npm notice
Your command is now published on npm and be installed or executed from everywhere.
Example:
Execution without formal installation:
1npx pique-sous
Or global installation:
1npm install -g pique-sous
✅ Install axios library
1yarn add axios
✅ Add file ./src/getStock.ts
1import axios from "axios"23export const getSingleStockInfo = async (stock: string) => {4 if (!stock) {5 throw new Error("Stock symbol argument required")6 }78 if (typeof stock !== "string") {9 throw new Error(10 `Invalid argument type for stock argument. Required: string. Found: ${typeof stock}`11 )12 }1314 const url = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=${stock}`1516 const res = await axios.get(url)1718 const { data } = res19 if (20 !data ||21 !data.quoteResponse ||22 !data.quoteResponse.result ||23 data.quoteResponse.result.length === 024 ) {25 throw new Error(`Error retrieving info for symbol ${stock}`)26 }2728 const quoteResponse = data.quoteResponse.result[0]2930 return quoteResponse31}
✅ Add file "./src/getVersion.ts"
1import * as fs from "fs"2import * as Path from "path"34export const getVersion = () => {5 const packageJSONPath = Path.resolve(__dirname, "../package.json")6 const content = fs.readFileSync(packageJSONPath, { encoding: "utf8" })7 const config = JSON.parse(content)8 return config.version9}
✅ Modify ./src/index.ts
1#!/usr/bin/env node23import { getSingleStockInfo } from "./getStock"4import { getVersion } from "./getVersion"56/**7 * return the arguments of the command except node and index.ts8 */9const getArgs = () => {10 // We retrieve all the command argumnts except the first 211 const args = process.argv.slice(2)12 return args13}1415/**16 * Command Help17 */18const printCommandHelp = () => {19 const version = getVersion()20 const help = `21pique-sous (version: ${version})2223A simple command to retrieve stock information.2425Example:2627$ pique-sous MSFT SFIX GOOG2829`30 console.log(help)31}3233const symbols = getArgs()3435// Print help if no arguments36if (symbols.length === 0) {37 printCommandHelp()38 getVersion()39 process.exit(0)40}4142const now = new Date().toISOString()4344// Call the yahoo API for each symbol and display the result on the console45symbols.forEach((symbol) => {46 console.log(`Retrieving stock information for ${symbol} at date ${now}`)47 getSingleStockInfo(symbol).then(console.log)48})
✅ Increment the version number in package.json to 1.1.0 ("version")
1{2 "devDependencies": {3 "@types/axios": "^0.14.0",4 "@types/node": "^14.14.20"5 },6 "name": "pique-sous",7 "version": "1.1.0",8 "description": "A simple command to retrieve stock information",9 "main": "./bin/index.js",10 "dependencies": {11 "axios": "^0.21.1",12 "typescript": "^4.1.3"13 },14 "bin": "bin/index.js",15 "scripts": {16 "build": "tsc",17 "test": "echo \"Error: no test specified\" && exit 1"18 },19 "keywords": ["cli", "node", "typescript", "npm", "stock", "yahoo finance"],20 "contributors": ["raphaelmansuy"],21 "repository": {22 "url": "https://github.com/raphaelmansuy/pique-sous.git",23 "type": ""24 },25 "author": {26 "email": "raphael.mansuy@gmail.com",27 "name": "raphaelmansuy"28 },29 "license": "MIT"30}
✅ Build a new version
1$ yarn build
✅ Test locally
Publish the component:
1$ yarn link --global
✅ Execute
1$ pique-sous MSFT
Result:
1Retrieving stock information for MSFT at date 2021-01-10T06:11:41.305Z2{3 language: 'en-US',4 region: 'US',5 quoteType: 'EQUITY',6 quoteSourceName: 'Delayed Quote',7 triggerable: true,8 currency: 'USD',9 exchange: 'NMS',10 shortName: 'Microsoft Corporation',11 longName: 'Microsoft Corporation',12 messageBoardId: 'finmb_21835',13 exchangeTimezoneName: 'America/New_York',14 exchangeTimezoneShortName: 'EST',15 gmtOffSetMilliseconds: -18000000,1617 ...
🔥🔥🔥 The package can now be republished on npm 💪.
We deliver high quality blog posts written by professionals monthly. And we promise no spam.