Scaffolding can help us quickly build content through simple commands. Currently, common scaffolds include vue-CLI, create-React-app, etc., which is an important means for teams to improve efficiency. We can develop our own scaffolding tools in line with our business. Next I will develop a scaffolding tool step by step, follow me!

1. Prepare

First, we need to make sure you have Node installed, and then we can initialize a project

mkdir mycli-demo & cd mycli-demo
npm init -y
Copy the code

2. Add a simple command

Add the bin configuration to package.json. Mycli is the name of the command provided by our CLI tool. The corresponding value is the file entry, and we point to the bin/cli.js file

{" name ":" mycli - demo ", "version" : "1.0.0", "description" : ""," main ":" index. Js ",+ "bin":{
+ "mycli": "./bin/cli.js"
+},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
Copy the code

Create bin/cli.js in the project and print a piece of text

#! /usr/bin/env node

console.log("i'm a cli")
Copy the code

The first line is important to indicate the runtime environment

At this point we can run mycli from the console

The console cannot find our CLI tool. This is because we have to install a tool to use it. For example, if you don’t have NPM installed, you will encounter the same error.

Since our myCLI is not released, we can use NPM Link or Yarn Link to select a local installation, run:

npm link

yarn link
Copy the code

If you find NPM Link and Yarn link cumbersome, you can also use yalc for local debugging

Then execute mycli again, and you can see that the console outputs what was printed in bin/cli.js

Now that we’ve seen how the CLI tool works, let’s make some improvements to it, allowing it to handle parameters, color printing, display loading, and so on

3. Process parameters

Many times we need to run the CLI tool with a parameter, so how to get this parameter?

In the Node program, process.argv retrives the arguments to the command and returns them as an array

As you can see, we have already got the parameters, but this is not very intuitive, so we introduced a third-party NPM package to help us deal with this part of the function: Commander. Refer to the Commander documentation

npm i commander -S
Copy the code

Let’s add the following code to bin/cli.js

#! /usr/bin/env node+ const program = require('commander')Console. log(" I'm a cli") // Print parameter console.log(process.argv)+ program
+ .command('create 
      
       ')
      
+ .description('create a new project')
+ .alias('c')
+ .option('-r, --react', 'react template')
+ .option('-v, --vue', 'vue template')
+ .option('-v2, --vue2', 'vue2 template')
+ .option('-v3, --vue3', 'vue3 template')
+ .action((projectName, options) => {
+ console.log(projectName, options)
+})
+ program version (' 1.0.0). The parse (process. Argv)
Copy the code

So far, we can get the parameters of the command intuitively, and you can try it out on the console

4. Interactive commands

There may be times when we need to incorporate some interaction into a command-line tool, generating something or doing something based on user input or selection. We can introduce an NPM package to help us implement this: Inquirer

npm i inquirer -S
Copy the code

Let’s add the following code to bin/cli.js

#! /usr/bin/env node const program = require('commander')+ const inquirer = require('inquirer')Console.log (" I'm a cli") // Print the parameter console.log(process.argv) program.command ('create <projectName>').description('create  a new project') .alias('c') .option('-r, --react', 'react template') .option('-v, --vue', 'vue template') .option('-v2, --vue2', 'vue2 template') .option('-v3, --vue3', 'vue3 template') .action((projectName, options) => { console.log(projectName, options)+ inquirer
+ .prompt([
+ {
+ type: 'list',
+ name: 'frameTemplate',
+ message: 'Please select frame type ',
+ choices: ['Vue3', 'Vue2', 'React']
+}
+])
+ .then((answer) => {
+ console.log(answer)
+})}). The program version (' 1.0.0). The parse (process. Argv)Copy the code

We’re running from the console

mycli create test -r
Copy the code

Get:

At this point, we are done with the interactive command, now I will complete a template download, let’s go!

5. Complete a template download

In the previous step, we found that our log printing was not very friendly, we can use the log-Symblos Chalk ORA to help us do some prompt optimization

NPM package name role website note
chalk Changes the style of strings in the console chalk Font style, color, background color
log-symbols Color symbols for various log levels log-symbols Use the ESM since version 5Release v5.0.0
ora Terminal loading effect ora

Since log-symbols has been using the ESM since version 5, we use version 4 here

npm i chalk log-symbols@4 ora -S
Copy the code

To download the code for a Github repository, we need to introduce download-git-repo, download-git-repo-npm

npm i download-git-repo -S
Copy the code

Let’s implement a simple download function

#! /usr/bin/env node const program = require('commander') const inquirer = require('inquirer')+ const ora = require('ora')
+ const download = require('download-git-repo')
+ const { errLog, successLog } = require('.. /src/utils/log.js')Console.log (" I'm a cli") // Print the parameter console.log(process.argv) program.command ('create <projectName>').description('create  a new project') .alias('c') .option('-r, --react', 'react template') .option('-v, --vue', 'vue template') .option('-v2, --vue2', 'vue2 template') .option('-v3, --vue3', 'vue3 template') .action((projectName, options) => { console.log(projectName, options) inquirer .prompt([ { type: 'list', name: 'frameTemplate', message: 'please select a frame type' choices: [' Vue3 ', 'Vue2', 'React']}]), then ((answer) = > {the console. The log (answer)+ const spinner = ora()
+ spinner. Text = 'downloading template... '
+ spinner.start()
+ download(
+ "',
+ projectName,
+ { clone: true },
+ function (err) {
+ if (err) {
+ spinner. Fail (' template download failed ')
+ errLog(err)
+ } else {
+ spinner. Succeed (' Template downloaded successfully ')
+ successLog(' Project initialization completed ')
+}
+}
+)})}). The program version (' 1.0.0). The parse (process. Argv)Copy the code

Type in the console

 mycli create test -r
Copy the code

You can see that the template is downloaded successfully

If the download fails, check the download-git-repo documentation: download-git-repo-npm

At this point, we have implemented a simple template repository download capability. More features can be tried by everyone.

If we want other people to use it, we need to publish the CLI to NPM, and how to do that is beyond the scope of this article.

6. Project optimization

If we have multiple commands, then we need to write multiple

program
  .command(' ')
  .description(' ')
  .alias(' ')
  .option(' ')
  .action(() = > {
    // Command processing
  })
Copy the code
  1. Since this part of the code is used repeatedly, we naturally want to iterate. First declare a list variable to maintain our command configuration, create a command-config.js file at SRC, and export the configuration
const COMMAND_LIST = [
  {
    command: 'create <projectName>'.description: 'create a new project'.alias: 'c'.options: [['-r, --react'.'react template'],
      ['-v, --vue'.'vue template'],
      ['-v2, --vue2'.'vue2 template'],
      ['-v3, --vue3'.'vue3 template']],action: require('./commandHandler/create'),
    examples: ['-r'.'--react'.'-v'.'--vue'.'-v2'.'--vue2'.'-v3'.'--vue3'].map((v) = > `create projectName ${v}`)}]module.exports = COMMAND_LIST

Copy the code

Modify the bin/cli. Js

/** * Register option *@param {Object} Commander Commander example *@param {Object} Option Configures the object * for each command@returns commander* /
const registerOption = (commander, option) = > {
  returnoption && option.length ? commander.option(... option) : commander }/** * Register for action *@param {Object} Commander Commander example *@param {Object} CommandEle Configures an object * for each command@returns commander* /
const registerAction = (commander, commandEle) = > {
  const { command, description, alias, options, action } = commandEle
  const c = commander
    .command(command) // The name of the command
    .description(description) // Description of the command
    .alias(alias)
  / / cycle options
  options && options.reduce(registerOption, c)
  c.action(action)
  return commander
}

// Loop create command
COMMAND_LIST.reduce(registerAction, program)
Copy the code
  1. Because the command processing part of the code is large, so we consider extracting the command processing functions in a folder, we create a new SRC commandHandler directory, and create a new create.js, put the create command processing code in the create.js

  2. For ease of use, let’s rewrite the mycli –help command


// the help command displays example
const help = () = > {
  console.log('\n')
  console.log(chalk.green('How to Use :'))
  COMMAND_LIST.forEach((command, index) = > {
    console.log(' ', chalk.keyword('orange')(index + 1), `${command.command}Command `)
    command.examples.forEach((example) = > {
      console.log(`     - mycli ${example}`)
    })
  })
}

program.on('-h', help)
program.on('--help', help)
Copy the code

At this point we type mycli –help

The circulation section can be optimized by itself

So far we have completed the development of a simple version of the CLI tool

Source: github.com/enbrands/st…

The main branch only git init the project. The other branches complete one step per branch