[{"data":1,"prerenderedAt":1515},["ShallowReactive",2],{"blog-node-cli-tutorial":3},{"id":4,"title":5,"body":6,"date":1500,"description":1501,"extension":1502,"featuredImage":1503,"featuredImageAlt":1504,"keywords":1505,"meta":1506,"navigation":158,"path":1507,"seo":1508,"stem":1509,"subtitle":1510,"tags":1511,"__hash__":1514},"blog\u002Fblog\u002Fnode-cli-tutorial.md","Creating a CLI tool using Node.js",{"type":7,"value":8,"toc":1492},"minimark",[9,19,24,29,44,50,54,57,346,353,356,359,581,588,592,599,934,948,955,1426,1465,1469,1481,1488],[10,11,12,13,14,18],"p",{},"In an attempt to learn something new about Node.js, and to improve my productivity, I began developing a CLI tool for generating React components. This would be very useful for the Next.js project that I talked about in my last post, as I could customize the file structure to the pattern I’ve already implemented (Model, View, ViewModel-ish).","  Through the use of the ",[15,16,17],"code",{},"oclif"," CLI framework, it was actually pretty easy to not only develop this tool, but also to publish it on NPM so that my team members could utilize it as well. I decided to create this tutorial to share my experience and hopefully help you write tools that can make you more productive.",[20,21,23],"h2",{"id":22},"getting-started","Getting Started",[25,26,28],"h3",{"id":27},"initialize-the-oclif-project","Initialize the Oclif project",[10,30,31,32,35,36,39,40,43],{},"The easiest way to get a new project started is by using npx:\n",[15,33,34],{},"npx oclif single [project name]","\nYou will then be prompted with several configuration options for your project. For example, in this tutorial I chose to use TypeScript. This will create a folder for your project with all of the required configuration.  Once you’re done, cd into the new directory and run\n",[15,37,38],{},"npm install","  or ",[15,41,42],{},"yarn install"," to install the required dependencies.",[10,45,46,47],{},"To make sure that everything is situated correctly, run your CLI tool:\n",[15,48,49],{},".\u002Fbin\u002Frun",[25,51,53],{"id":52},"define-the-required-arguments","Define the required arguments",[10,55,56],{},"Our freshly-generated project gives us predefined variables to store our arguments and flags related to our cli:",[58,59,65],"pre",{"className":60,"code":61,"filename":62,"language":63,"meta":64,"style":64},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F *** Add these imports to the beginning of the file! ***\nimport * as inquirer from \"inquirer\";\nimport * as fs from 'fs';\nimport cli from 'cli-ux';\nimport { createCipheriv, randomBytes, scryptSync } from 'crypto';\n\nclass Crypto extends Command {\n    static description = 'describe the command here';\n\n    static flags = {\n        \u002F\u002F add --version flag to show CLI version\n        version: flags.version({char: 'v'}),\n        help: flags.help({char: 'h'}),\n        \u002F\u002F flag with a value (-n, --name=VALUE)\n        name: flags.string({char: 'n', description: 'name to print'}),\n        \u002F\u002F flag with no value (-f, --force)\n        force: flags.boolean({char: 'f'}),\n    };\n\n    static args = [{name: 'file'}];\n    ...\n}\n","\u002Fsrc\u002Findex.ts","ts","",[15,66,67,76,104,123,138,153,160,179,197,202,214,220,238,254,260,282,288,304,310,315,334,340],{"__ignoreMap":64},[68,69,72],"span",{"class":70,"line":71},"line",1,[68,73,75],{"class":74},"sJ8bj","\u002F\u002F *** Add these imports to the beginning of the file! ***\n",[68,77,79,83,87,90,94,97,101],{"class":70,"line":78},2,[68,80,82],{"class":81},"szBVR","import",[68,84,86],{"class":85},"sj4cs"," *",[68,88,89],{"class":81}," as",[68,91,93],{"class":92},"sVt8B"," inquirer ",[68,95,96],{"class":81},"from",[68,98,100],{"class":99},"sZZnC"," \"inquirer\"",[68,102,103],{"class":92},";\n",[68,105,107,109,111,113,116,118,121],{"class":70,"line":106},3,[68,108,82],{"class":81},[68,110,86],{"class":85},[68,112,89],{"class":81},[68,114,115],{"class":92}," fs ",[68,117,96],{"class":81},[68,119,120],{"class":99}," 'fs'",[68,122,103],{"class":92},[68,124,126,128,131,133,136],{"class":70,"line":125},4,[68,127,82],{"class":81},[68,129,130],{"class":92}," cli ",[68,132,96],{"class":81},[68,134,135],{"class":99}," 'cli-ux'",[68,137,103],{"class":92},[68,139,141,143,146,148,151],{"class":70,"line":140},5,[68,142,82],{"class":81},[68,144,145],{"class":92}," { createCipheriv, randomBytes, scryptSync } ",[68,147,96],{"class":81},[68,149,150],{"class":99}," 'crypto'",[68,152,103],{"class":92},[68,154,156],{"class":70,"line":155},6,[68,157,159],{"emptyLinePlaceholder":158},true,"\n",[68,161,163,166,170,173,176],{"class":70,"line":162},7,[68,164,165],{"class":81},"class",[68,167,169],{"class":168},"sScJk"," Crypto",[68,171,172],{"class":81}," extends",[68,174,175],{"class":168}," Command",[68,177,178],{"class":92}," {\n",[68,180,182,185,189,192,195],{"class":70,"line":181},8,[68,183,184],{"class":81},"    static",[68,186,188],{"class":187},"s4XuR"," description",[68,190,191],{"class":81}," =",[68,193,194],{"class":99}," 'describe the command here'",[68,196,103],{"class":92},[68,198,200],{"class":70,"line":199},9,[68,201,159],{"emptyLinePlaceholder":158},[68,203,205,207,210,212],{"class":70,"line":204},10,[68,206,184],{"class":81},[68,208,209],{"class":187}," flags",[68,211,191],{"class":81},[68,213,178],{"class":92},[68,215,217],{"class":70,"line":216},11,[68,218,219],{"class":74},"        \u002F\u002F add --version flag to show CLI version\n",[68,221,223,226,229,232,235],{"class":70,"line":222},12,[68,224,225],{"class":92},"        version: flags.",[68,227,228],{"class":168},"version",[68,230,231],{"class":92},"({char: ",[68,233,234],{"class":99},"'v'",[68,236,237],{"class":92},"}),\n",[68,239,241,244,247,249,252],{"class":70,"line":240},13,[68,242,243],{"class":92},"        help: flags.",[68,245,246],{"class":168},"help",[68,248,231],{"class":92},[68,250,251],{"class":99},"'h'",[68,253,237],{"class":92},[68,255,257],{"class":70,"line":256},14,[68,258,259],{"class":74},"        \u002F\u002F flag with a value (-n, --name=VALUE)\n",[68,261,263,266,269,271,274,277,280],{"class":70,"line":262},15,[68,264,265],{"class":92},"        name: flags.",[68,267,268],{"class":168},"string",[68,270,231],{"class":92},[68,272,273],{"class":99},"'n'",[68,275,276],{"class":92},", description: ",[68,278,279],{"class":99},"'name to print'",[68,281,237],{"class":92},[68,283,285],{"class":70,"line":284},16,[68,286,287],{"class":74},"        \u002F\u002F flag with no value (-f, --force)\n",[68,289,291,294,297,299,302],{"class":70,"line":290},17,[68,292,293],{"class":92},"        force: flags.",[68,295,296],{"class":168},"boolean",[68,298,231],{"class":92},[68,300,301],{"class":99},"'f'",[68,303,237],{"class":92},[68,305,307],{"class":70,"line":306},18,[68,308,309],{"class":92},"    };\n",[68,311,313],{"class":70,"line":312},19,[68,314,159],{"emptyLinePlaceholder":158},[68,316,318,320,323,325,328,331],{"class":70,"line":317},20,[68,319,184],{"class":81},[68,321,322],{"class":187}," args",[68,324,191],{"class":81},[68,326,327],{"class":92}," [{name: ",[68,329,330],{"class":99},"'file'",[68,332,333],{"class":92},"}];\n",[68,335,337],{"class":70,"line":336},21,[68,338,339],{"class":81},"    ...\n",[68,341,343],{"class":70,"line":342},22,[68,344,345],{"class":92},"}\n",[10,347,348,349,352],{},"The difference between flags and arguments is that flags are provided when the command is executed, ie: ",[15,350,351],{},".\u002Fbin\u002Frun --force",", whereas an argument is a value that the CLI prompts the user for during runtime.",[10,354,355],{},"For this example project, I am going to be making a CLI tool that takes a user input, and return a hashed string of that input. We will also be giving the user choices on what encryption algorithm they want to use.",[10,357,358],{},"From this example, we can see that there are two user inputs that are going to be recorded: the input string, and the encryption algorithm. Let's add those fields to our flags and arguments:",[58,360,362],{"className":60,"code":361,"filename":62,"language":63,"meta":64,"style":64},"static flags = {\n  \u002F\u002F add --version flag to show CLI version\n  version: flags.version({char: 'v'}),\n  help: flags.help({char: 'h'}),\n  \u002F\u002F flag with a value (-n, --name=VALUE)\n  name: flags.string({char: 'n', description: 'name to print'}),\n  \u002F\u002F flag with no value (-f, --force)\n  force: flags.boolean({char: 'f'}),\n\n  \u002F\u002F Our new input flags\n  input: flags.string({ default: '' }),\n  algorithm: flags.string({\n    options: ['aes-192-cbc', 'aes-256-cbc', 'des3', 'rc2']\n  }),\n  password: flags.string({}),\n};\n\nstatic args = [\n  \u002F\u002F Our new input arguments\n  {name: 'input'},\n  {name: 'algorithm'},\n  {name: 'password'}\n];\n",[15,363,364,374,379,392,405,410,427,432,445,449,454,470,480,507,512,522,527,531,541,546,557,566,575],{"__ignoreMap":64},[68,365,366,369,372],{"class":70,"line":71},[68,367,368],{"class":92},"static flags ",[68,370,371],{"class":81},"=",[68,373,178],{"class":92},[68,375,376],{"class":70,"line":78},[68,377,378],{"class":74},"  \u002F\u002F add --version flag to show CLI version\n",[68,380,381,384,386,388,390],{"class":70,"line":106},[68,382,383],{"class":92},"  version: flags.",[68,385,228],{"class":168},[68,387,231],{"class":92},[68,389,234],{"class":99},[68,391,237],{"class":92},[68,393,394,397,399,401,403],{"class":70,"line":125},[68,395,396],{"class":92},"  help: flags.",[68,398,246],{"class":168},[68,400,231],{"class":92},[68,402,251],{"class":99},[68,404,237],{"class":92},[68,406,407],{"class":70,"line":140},[68,408,409],{"class":74},"  \u002F\u002F flag with a value (-n, --name=VALUE)\n",[68,411,412,415,417,419,421,423,425],{"class":70,"line":155},[68,413,414],{"class":92},"  name: flags.",[68,416,268],{"class":168},[68,418,231],{"class":92},[68,420,273],{"class":99},[68,422,276],{"class":92},[68,424,279],{"class":99},[68,426,237],{"class":92},[68,428,429],{"class":70,"line":162},[68,430,431],{"class":74},"  \u002F\u002F flag with no value (-f, --force)\n",[68,433,434,437,439,441,443],{"class":70,"line":181},[68,435,436],{"class":92},"  force: flags.",[68,438,296],{"class":168},[68,440,231],{"class":92},[68,442,301],{"class":99},[68,444,237],{"class":92},[68,446,447],{"class":70,"line":199},[68,448,159],{"emptyLinePlaceholder":158},[68,450,451],{"class":70,"line":204},[68,452,453],{"class":74},"  \u002F\u002F Our new input flags\n",[68,455,456,459,461,464,467],{"class":70,"line":216},[68,457,458],{"class":92},"  input: flags.",[68,460,268],{"class":168},[68,462,463],{"class":92},"({ default: ",[68,465,466],{"class":99},"''",[68,468,469],{"class":92}," }),\n",[68,471,472,475,477],{"class":70,"line":222},[68,473,474],{"class":92},"  algorithm: flags.",[68,476,268],{"class":168},[68,478,479],{"class":92},"({\n",[68,481,482,485,488,491,494,496,499,501,504],{"class":70,"line":240},[68,483,484],{"class":92},"    options: [",[68,486,487],{"class":99},"'aes-192-cbc'",[68,489,490],{"class":92},", ",[68,492,493],{"class":99},"'aes-256-cbc'",[68,495,490],{"class":92},[68,497,498],{"class":99},"'des3'",[68,500,490],{"class":92},[68,502,503],{"class":99},"'rc2'",[68,505,506],{"class":92},"]\n",[68,508,509],{"class":70,"line":256},[68,510,511],{"class":92},"  }),\n",[68,513,514,517,519],{"class":70,"line":262},[68,515,516],{"class":92},"  password: flags.",[68,518,268],{"class":168},[68,520,521],{"class":92},"({}),\n",[68,523,524],{"class":70,"line":284},[68,525,526],{"class":92},"};\n",[68,528,529],{"class":70,"line":290},[68,530,159],{"emptyLinePlaceholder":158},[68,532,533,536,538],{"class":70,"line":306},[68,534,535],{"class":92},"static args ",[68,537,371],{"class":81},[68,539,540],{"class":92}," [\n",[68,542,543],{"class":70,"line":312},[68,544,545],{"class":74},"  \u002F\u002F Our new input arguments\n",[68,547,548,551,554],{"class":70,"line":317},[68,549,550],{"class":92},"  {name: ",[68,552,553],{"class":99},"'input'",[68,555,556],{"class":92},"},\n",[68,558,559,561,564],{"class":70,"line":336},[68,560,550],{"class":92},[68,562,563],{"class":99},"'algorithm'",[68,565,556],{"class":92},[68,567,568,570,573],{"class":70,"line":342},[68,569,550],{"class":92},[68,571,572],{"class":99},"'password'",[68,574,345],{"class":92},[68,576,578],{"class":70,"line":577},23,[68,579,580],{"class":92},"];\n",[10,582,583,584,587],{},"The reason I am defining these variables in both the flags and args options is so that a user can provide values when running the command, and the CLI will prompt the user for any undefined arguments. For example, someone could run ",[15,585,586],{},"crypto --input=test",", because they know they want to encrypt \"test\" but aren't sure which algorithm they wish to use.",[25,589,591],{"id":590},"create-input-prompts","Create input prompts",[10,593,594,595,598],{},"Next, we need to define the logic that will both pull values from any provided flags, and prompt for any arguments not provided already. The \"main\" function of an Oclif project is ",[15,596,597],{},"run()",", which is where we will be doing the majority of our work.",[58,600,602],{"className":60,"code":601,"filename":62,"language":63,"meta":64,"style":64},"async run() {\n  \u002F\u002F Pulls args and flags variables from Crypto class\n  const {args, flags} = this.parse(Crypto)\n  \u002F\u002F Destructure flags object to get input and algorithm\n  let { input, algorithm, password } = flags;\n\n  \u002F\u002F If input variable is undefined...\n  if (!input) {\n    \u002F\u002F cli-ux and 'cli' object provide utilities to interact\n    \u002F\u002F  with the Oclif API.\n    input = await cli.prompt('String to be encrypted');\n  }\n  \u002F\u002F If algorithm variable is undefined...\n  if (!algorithm) {\n    \u002F\u002F inquirer is an add-on for Oclif that allows us to\n    \u002F\u002F  take different kinds of input - in this case a list\n    const select = await inquirer.prompt([{\n      name: 'algorithm',\n      message: 'Select encryption algorithm',\n      type: 'list',\n      choices: [\n        { name: 'aes-192-cbc' },\n        { name: 'aes-256-cbc' },\n        { name: 'des3' },\n        { name: 'rc2' },\n      ],\n    }]);\n    \u002F\u002F Assign user input to algorithm variable\n    algorithm = select.algorithm;\n  }\n  \u002F\u002F If password is undefined...\n  if (!password) {\n    password = await cli.prompt('Password for encryption key');\n  }\n}\n",[15,603,604,615,620,653,658,671,675,680,694,699,704,729,734,739,750,755,760,780,790,800,810,815,825,833,842,851,857,863,869,880,885,891,903,924,929],{"__ignoreMap":64},[68,605,606,609,612],{"class":70,"line":71},[68,607,608],{"class":92},"async ",[68,610,611],{"class":168},"run",[68,613,614],{"class":92},"() {\n",[68,616,617],{"class":70,"line":78},[68,618,619],{"class":74},"  \u002F\u002F Pulls args and flags variables from Crypto class\n",[68,621,622,625,628,631,633,636,639,641,644,647,650],{"class":70,"line":106},[68,623,624],{"class":81},"  const",[68,626,627],{"class":92}," {",[68,629,630],{"class":85},"args",[68,632,490],{"class":92},[68,634,635],{"class":85},"flags",[68,637,638],{"class":92},"} ",[68,640,371],{"class":81},[68,642,643],{"class":85}," this",[68,645,646],{"class":92},".",[68,648,649],{"class":168},"parse",[68,651,652],{"class":92},"(Crypto)\n",[68,654,655],{"class":70,"line":125},[68,656,657],{"class":74},"  \u002F\u002F Destructure flags object to get input and algorithm\n",[68,659,660,663,666,668],{"class":70,"line":140},[68,661,662],{"class":81},"  let",[68,664,665],{"class":92}," { input, algorithm, password } ",[68,667,371],{"class":81},[68,669,670],{"class":92}," flags;\n",[68,672,673],{"class":70,"line":155},[68,674,159],{"emptyLinePlaceholder":158},[68,676,677],{"class":70,"line":162},[68,678,679],{"class":74},"  \u002F\u002F If input variable is undefined...\n",[68,681,682,685,688,691],{"class":70,"line":181},[68,683,684],{"class":81},"  if",[68,686,687],{"class":92}," (",[68,689,690],{"class":81},"!",[68,692,693],{"class":92},"input) {\n",[68,695,696],{"class":70,"line":199},[68,697,698],{"class":74},"    \u002F\u002F cli-ux and 'cli' object provide utilities to interact\n",[68,700,701],{"class":70,"line":204},[68,702,703],{"class":74},"    \u002F\u002F  with the Oclif API.\n",[68,705,706,709,711,714,717,720,723,726],{"class":70,"line":216},[68,707,708],{"class":92},"    input ",[68,710,371],{"class":81},[68,712,713],{"class":81}," await",[68,715,716],{"class":92}," cli.",[68,718,719],{"class":168},"prompt",[68,721,722],{"class":92},"(",[68,724,725],{"class":99},"'String to be encrypted'",[68,727,728],{"class":92},");\n",[68,730,731],{"class":70,"line":222},[68,732,733],{"class":92},"  }\n",[68,735,736],{"class":70,"line":240},[68,737,738],{"class":74},"  \u002F\u002F If algorithm variable is undefined...\n",[68,740,741,743,745,747],{"class":70,"line":256},[68,742,684],{"class":81},[68,744,687],{"class":92},[68,746,690],{"class":81},[68,748,749],{"class":92},"algorithm) {\n",[68,751,752],{"class":70,"line":262},[68,753,754],{"class":74},"    \u002F\u002F inquirer is an add-on for Oclif that allows us to\n",[68,756,757],{"class":70,"line":284},[68,758,759],{"class":74},"    \u002F\u002F  take different kinds of input - in this case a list\n",[68,761,762,765,768,770,772,775,777],{"class":70,"line":290},[68,763,764],{"class":81},"    const",[68,766,767],{"class":85}," select",[68,769,191],{"class":81},[68,771,713],{"class":81},[68,773,774],{"class":92}," inquirer.",[68,776,719],{"class":168},[68,778,779],{"class":92},"([{\n",[68,781,782,785,787],{"class":70,"line":306},[68,783,784],{"class":92},"      name: ",[68,786,563],{"class":99},[68,788,789],{"class":92},",\n",[68,791,792,795,798],{"class":70,"line":312},[68,793,794],{"class":92},"      message: ",[68,796,797],{"class":99},"'Select encryption algorithm'",[68,799,789],{"class":92},[68,801,802,805,808],{"class":70,"line":317},[68,803,804],{"class":92},"      type: ",[68,806,807],{"class":99},"'list'",[68,809,789],{"class":92},[68,811,812],{"class":70,"line":336},[68,813,814],{"class":92},"      choices: [\n",[68,816,817,820,822],{"class":70,"line":342},[68,818,819],{"class":92},"        { name: ",[68,821,487],{"class":99},[68,823,824],{"class":92}," },\n",[68,826,827,829,831],{"class":70,"line":577},[68,828,819],{"class":92},[68,830,493],{"class":99},[68,832,824],{"class":92},[68,834,836,838,840],{"class":70,"line":835},24,[68,837,819],{"class":92},[68,839,498],{"class":99},[68,841,824],{"class":92},[68,843,845,847,849],{"class":70,"line":844},25,[68,846,819],{"class":92},[68,848,503],{"class":99},[68,850,824],{"class":92},[68,852,854],{"class":70,"line":853},26,[68,855,856],{"class":92},"      ],\n",[68,858,860],{"class":70,"line":859},27,[68,861,862],{"class":92},"    }]);\n",[68,864,866],{"class":70,"line":865},28,[68,867,868],{"class":74},"    \u002F\u002F Assign user input to algorithm variable\n",[68,870,872,875,877],{"class":70,"line":871},29,[68,873,874],{"class":92},"    algorithm ",[68,876,371],{"class":81},[68,878,879],{"class":92}," select.algorithm;\n",[68,881,883],{"class":70,"line":882},30,[68,884,733],{"class":92},[68,886,888],{"class":70,"line":887},31,[68,889,890],{"class":74},"  \u002F\u002F If password is undefined...\n",[68,892,894,896,898,900],{"class":70,"line":893},32,[68,895,684],{"class":81},[68,897,687],{"class":92},[68,899,690],{"class":81},[68,901,902],{"class":92},"password) {\n",[68,904,906,909,911,913,915,917,919,922],{"class":70,"line":905},33,[68,907,908],{"class":92},"    password ",[68,910,371],{"class":81},[68,912,713],{"class":81},[68,914,716],{"class":92},[68,916,719],{"class":168},[68,918,722],{"class":92},[68,920,921],{"class":99},"'Password for encryption key'",[68,923,728],{"class":92},[68,925,927],{"class":70,"line":926},34,[68,928,733],{"class":92},[68,930,932],{"class":70,"line":931},35,[68,933,345],{"class":92},[10,935,936,937,939,940,943,944,947],{},"What I've done here is pretty simple - after getting the user input variables by destructuring ",[15,938,635],{},", we simply check if each item is undefined, and if so we prompt the user with the appropriate method. In this case I am using ",[15,941,942],{},"cli.prompt()"," to get basic text inputs, and ",[15,945,946],{},"inquirer.prompt()"," to give the user a list of items to select from.",[10,949,950,951,954],{},"Now that we have collected the required variables, we can generate a hash from the input string and print the resulting hash to the terminal. We are going to use the imported function ",[15,952,953],{},"createHash"," to do this:",[58,956,958],{"className":60,"code":957,"filename":62,"language":63,"meta":64,"style":64},"async run() {\n  ...\n\n  \u002F\u002F Encryption key length is dependent on algorithm\n  let keyLength: number;\n  switch(algorithm) {\n    case \"aes-192-cbc\":\n      keyLength = 24;\n      break;\n    case \"aes-256-cbc\":\n      keyLength = 32;\n      break;\n    case \"des-ede3-cbc\":\n      keyLength = 7;\n      break;\n    default:\n      keyLength = 24;\n      break;\n  }\n  \u002F\u002F Generate encryption key from password and keyLength\n  const key = scryptSync(password ? password : 'password', 'salt', keyLength);\n  \u002F\u002F Define IV (Initialization vector)\n  const iv = Buffer.alloc(16, 0);\n  \u002F\u002F Create Cipher object\n  const cipher = createCipheriv(\n    algorithm ? algorithm : 'aes-192-cbc',\n    key,\n    \u002F\u002F des-ede3 algorithm does not use iv\n    algorithm === 'des-ede3' ? '' : iv\n  );\n\n  this.log(`\\n\u002F\u002F Input string: ${input}`);\n  this.log(`\u002F\u002F Algorithm: ${algorithm}`);\n  this.log(`\u002F\u002F Cipher password: ${password}\\n`);\n\n  \u002F\u002F Create the encrypted string by updating the cipher\n  let encrypted: string = cipher.update(input, 'utf8', 'hex');\n  encrypted += cipher.final('hex');\n  this.log(`OUTPUT: ${encrypted}`);\n}\n",[15,959,960,968,973,977,982,997,1005,1016,1028,1035,1044,1055,1061,1070,1081,1087,1094,1104,1110,1114,1119,1153,1158,1185,1190,1205,1221,1226,1231,1253,1258,1262,1291,1311,1336,1340,1346,1380,1400,1421],{"__ignoreMap":64},[68,961,962,964,966],{"class":70,"line":71},[68,963,608],{"class":92},[68,965,611],{"class":168},[68,967,614],{"class":92},[68,969,970],{"class":70,"line":78},[68,971,972],{"class":81},"  ...\n",[68,974,975],{"class":70,"line":106},[68,976,159],{"emptyLinePlaceholder":158},[68,978,979],{"class":70,"line":125},[68,980,981],{"class":74},"  \u002F\u002F Encryption key length is dependent on algorithm\n",[68,983,984,986,989,992,995],{"class":70,"line":140},[68,985,662],{"class":81},[68,987,988],{"class":92}," keyLength",[68,990,991],{"class":81},":",[68,993,994],{"class":85}," number",[68,996,103],{"class":92},[68,998,999,1002],{"class":70,"line":155},[68,1000,1001],{"class":81},"  switch",[68,1003,1004],{"class":92},"(algorithm) {\n",[68,1006,1007,1010,1013],{"class":70,"line":162},[68,1008,1009],{"class":81},"    case",[68,1011,1012],{"class":99}," \"aes-192-cbc\"",[68,1014,1015],{"class":92},":\n",[68,1017,1018,1021,1023,1026],{"class":70,"line":181},[68,1019,1020],{"class":92},"      keyLength ",[68,1022,371],{"class":81},[68,1024,1025],{"class":85}," 24",[68,1027,103],{"class":92},[68,1029,1030,1033],{"class":70,"line":199},[68,1031,1032],{"class":81},"      break",[68,1034,103],{"class":92},[68,1036,1037,1039,1042],{"class":70,"line":204},[68,1038,1009],{"class":81},[68,1040,1041],{"class":99}," \"aes-256-cbc\"",[68,1043,1015],{"class":92},[68,1045,1046,1048,1050,1053],{"class":70,"line":216},[68,1047,1020],{"class":92},[68,1049,371],{"class":81},[68,1051,1052],{"class":85}," 32",[68,1054,103],{"class":92},[68,1056,1057,1059],{"class":70,"line":222},[68,1058,1032],{"class":81},[68,1060,103],{"class":92},[68,1062,1063,1065,1068],{"class":70,"line":240},[68,1064,1009],{"class":81},[68,1066,1067],{"class":99}," \"des-ede3-cbc\"",[68,1069,1015],{"class":92},[68,1071,1072,1074,1076,1079],{"class":70,"line":256},[68,1073,1020],{"class":92},[68,1075,371],{"class":81},[68,1077,1078],{"class":85}," 7",[68,1080,103],{"class":92},[68,1082,1083,1085],{"class":70,"line":262},[68,1084,1032],{"class":81},[68,1086,103],{"class":92},[68,1088,1089,1092],{"class":70,"line":284},[68,1090,1091],{"class":81},"    default",[68,1093,1015],{"class":92},[68,1095,1096,1098,1100,1102],{"class":70,"line":290},[68,1097,1020],{"class":92},[68,1099,371],{"class":81},[68,1101,1025],{"class":85},[68,1103,103],{"class":92},[68,1105,1106,1108],{"class":70,"line":306},[68,1107,1032],{"class":81},[68,1109,103],{"class":92},[68,1111,1112],{"class":70,"line":312},[68,1113,733],{"class":92},[68,1115,1116],{"class":70,"line":317},[68,1117,1118],{"class":74},"  \u002F\u002F Generate encryption key from password and keyLength\n",[68,1120,1121,1123,1126,1128,1131,1134,1137,1140,1142,1145,1147,1150],{"class":70,"line":336},[68,1122,624],{"class":81},[68,1124,1125],{"class":85}," key",[68,1127,191],{"class":81},[68,1129,1130],{"class":168}," scryptSync",[68,1132,1133],{"class":92},"(password ",[68,1135,1136],{"class":81},"?",[68,1138,1139],{"class":92}," password ",[68,1141,991],{"class":81},[68,1143,1144],{"class":99}," 'password'",[68,1146,490],{"class":92},[68,1148,1149],{"class":99},"'salt'",[68,1151,1152],{"class":92},", keyLength);\n",[68,1154,1155],{"class":70,"line":342},[68,1156,1157],{"class":74},"  \u002F\u002F Define IV (Initialization vector)\n",[68,1159,1160,1162,1165,1167,1170,1173,1175,1178,1180,1183],{"class":70,"line":577},[68,1161,624],{"class":81},[68,1163,1164],{"class":85}," iv",[68,1166,191],{"class":81},[68,1168,1169],{"class":92}," Buffer.",[68,1171,1172],{"class":168},"alloc",[68,1174,722],{"class":92},[68,1176,1177],{"class":85},"16",[68,1179,490],{"class":92},[68,1181,1182],{"class":85},"0",[68,1184,728],{"class":92},[68,1186,1187],{"class":70,"line":835},[68,1188,1189],{"class":74},"  \u002F\u002F Create Cipher object\n",[68,1191,1192,1194,1197,1199,1202],{"class":70,"line":844},[68,1193,624],{"class":81},[68,1195,1196],{"class":85}," cipher",[68,1198,191],{"class":81},[68,1200,1201],{"class":168}," createCipheriv",[68,1203,1204],{"class":92},"(\n",[68,1206,1207,1209,1211,1214,1216,1219],{"class":70,"line":853},[68,1208,874],{"class":92},[68,1210,1136],{"class":81},[68,1212,1213],{"class":92}," algorithm ",[68,1215,991],{"class":81},[68,1217,1218],{"class":99}," 'aes-192-cbc'",[68,1220,789],{"class":92},[68,1222,1223],{"class":70,"line":859},[68,1224,1225],{"class":92},"    key,\n",[68,1227,1228],{"class":70,"line":865},[68,1229,1230],{"class":74},"    \u002F\u002F des-ede3 algorithm does not use iv\n",[68,1232,1233,1235,1238,1241,1244,1247,1250],{"class":70,"line":871},[68,1234,874],{"class":92},[68,1236,1237],{"class":81},"===",[68,1239,1240],{"class":99}," 'des-ede3'",[68,1242,1243],{"class":81}," ?",[68,1245,1246],{"class":99}," ''",[68,1248,1249],{"class":81}," :",[68,1251,1252],{"class":92}," iv\n",[68,1254,1255],{"class":70,"line":882},[68,1256,1257],{"class":92},"  );\n",[68,1259,1260],{"class":70,"line":887},[68,1261,159],{"emptyLinePlaceholder":158},[68,1263,1264,1267,1269,1272,1274,1277,1280,1283,1286,1289],{"class":70,"line":893},[68,1265,1266],{"class":85},"  this",[68,1268,646],{"class":92},[68,1270,1271],{"class":168},"log",[68,1273,722],{"class":92},[68,1275,1276],{"class":99},"`",[68,1278,1279],{"class":85},"\\n",[68,1281,1282],{"class":99},"\u002F\u002F Input string: ${",[68,1284,1285],{"class":92},"input",[68,1287,1288],{"class":99},"}`",[68,1290,728],{"class":92},[68,1292,1293,1295,1297,1299,1301,1304,1307,1309],{"class":70,"line":905},[68,1294,1266],{"class":85},[68,1296,646],{"class":92},[68,1298,1271],{"class":168},[68,1300,722],{"class":92},[68,1302,1303],{"class":99},"`\u002F\u002F Algorithm: ${",[68,1305,1306],{"class":92},"algorithm",[68,1308,1288],{"class":99},[68,1310,728],{"class":92},[68,1312,1313,1315,1317,1319,1321,1324,1327,1330,1332,1334],{"class":70,"line":926},[68,1314,1266],{"class":85},[68,1316,646],{"class":92},[68,1318,1271],{"class":168},[68,1320,722],{"class":92},[68,1322,1323],{"class":99},"`\u002F\u002F Cipher password: ${",[68,1325,1326],{"class":92},"password",[68,1328,1329],{"class":99},"}",[68,1331,1279],{"class":85},[68,1333,1276],{"class":99},[68,1335,728],{"class":92},[68,1337,1338],{"class":70,"line":931},[68,1339,159],{"emptyLinePlaceholder":158},[68,1341,1343],{"class":70,"line":1342},36,[68,1344,1345],{"class":74},"  \u002F\u002F Create the encrypted string by updating the cipher\n",[68,1347,1349,1351,1354,1356,1359,1361,1364,1367,1370,1373,1375,1378],{"class":70,"line":1348},37,[68,1350,662],{"class":81},[68,1352,1353],{"class":92}," encrypted",[68,1355,991],{"class":81},[68,1357,1358],{"class":85}," string",[68,1360,191],{"class":81},[68,1362,1363],{"class":92}," cipher.",[68,1365,1366],{"class":168},"update",[68,1368,1369],{"class":92},"(input, ",[68,1371,1372],{"class":99},"'utf8'",[68,1374,490],{"class":92},[68,1376,1377],{"class":99},"'hex'",[68,1379,728],{"class":92},[68,1381,1383,1386,1389,1391,1394,1396,1398],{"class":70,"line":1382},38,[68,1384,1385],{"class":92},"  encrypted ",[68,1387,1388],{"class":81},"+=",[68,1390,1363],{"class":92},[68,1392,1393],{"class":168},"final",[68,1395,722],{"class":92},[68,1397,1377],{"class":99},[68,1399,728],{"class":92},[68,1401,1403,1405,1407,1409,1411,1414,1417,1419],{"class":70,"line":1402},39,[68,1404,1266],{"class":85},[68,1406,646],{"class":92},[68,1408,1271],{"class":168},[68,1410,722],{"class":92},[68,1412,1413],{"class":99},"`OUTPUT: ${",[68,1415,1416],{"class":92},"encrypted",[68,1418,1288],{"class":99},[68,1420,728],{"class":92},[68,1422,1424],{"class":70,"line":1423},40,[68,1425,345],{"class":92},[10,1427,1428,1429,1432,1433,1436,1437,1440,1441,1448,1449,1452,1453,1456,1457,1460,1461,1464],{},"There's a little bit of extra work that had to go into this. Each encryption algorithm requires a different key length, so I used a switch\u002Fcase statement to properly assign the ",[15,1430,1431],{},"keyLength"," variable. Next, we create the ",[15,1434,1435],{},"key"," and ",[15,1438,1439],{},"iv"," variables (more info about Initialization Vectors ",[1442,1443,1447],"a",{"href":1444,"rel":1445},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FInitialization_vector",[1446],"nofollow","here","). We then define the ",[15,1450,1451],{},"cipher"," variable by using the ",[15,1454,1455],{},"createCipheriv"," method from the Node.js crypto library. To actually get an encrypted value, we use the ",[15,1458,1459],{},"cipher.update()"," method to run the encryption, and ",[15,1462,1463],{},"cipher.final()"," to pull the resulting encrypted string.",[25,1466,1468],{"id":1467},"running-the-program","Running the program",[10,1470,1471,1472,1474,1475,1480],{},"To test our program, we can run ",[15,1473,49],{}," in the terminal which will launch our CLI application. Congratulations! You have created your very own CLI tool to use as you please. CLI applications can be extremely powerful - and learning how to make them yourself is a great learning experience that can also improve your productivity. You can also publish your CLI tool to ",[1442,1476,1479],{"href":1477,"rel":1478},"https:\u002F\u002Fnpmjs.org\u002F",[1446],"npmjs.org"," so anyone can install and use your work!",[10,1482,1483,1484,646],{},"The full code for index.ts can be found ",[1442,1485,1447],{"href":1486,"rel":1487},"https:\u002F\u002Fgist.github.com\u002Fdan-valinotti\u002F6f7499d8e7a59d32f85740486ef4ba28",[1446],[1489,1490,1491],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":64,"searchDepth":78,"depth":78,"links":1493},[1494],{"id":22,"depth":78,"text":23,"children":1495},[1496,1497,1498,1499],{"id":27,"depth":106,"text":28},{"id":52,"depth":106,"text":53},{"id":590,"depth":106,"text":591},{"id":1467,"depth":106,"text":1468},"2021-03-11","In an attempt to learn something new about Node.js, and to improve my productivity, I began developing a CLI tool for generating React components. This would be very useful for the Next.js project that I talked about in my last post, as I could customize the file structure to the pattern I’ve already implemented (Model, View, ViewModel-ish).  Through the use of the oclif CLI framework, it was actually pretty easy to not only develop this tool, but also to publish it on NPM so that my team members could utilize it as well. I decided to create this tutorial to share my experience and hopefully help you write tools that can make you more productive.","md","cli-tool.jpg","A picture of a computer terminal window","web development,nodejs,node,tutorial,cli,command line interface,oclif,typescript,javascript,cli-ux,npm,github,git,npm package",{},"\u002Fblog\u002Fnode-cli-tutorial",{"title":5,"description":1501},"blog\u002Fnode-cli-tutorial","Spend time learning to save time working.",[1512,1513],"tutorial","nodejs","xP8LkkByJKdNc8g_qkeNLA9lMcv9GpLNQ8wpYY4Drkk",1776925020722]