1. What is CLI

CLI is short for Command Line Interface. The user can type executable instructions at the prompt, and the computer, which usually does not support a mouse, executes them. A lot of CLI is used in front-end development. The most typical ones are create-react-app and vuE-CLI for react and vue respectively. Taking vue-CLI as an example, we create a VUE project like this

Vue create my-app then answers some project-related questions from the command line, and finally generates the corresponding project files based on the answers to the questions. So, we’re going to build our own CLI to customize these operations to help with things like project generation and code checking.

2. Related concepts

2.1 command

The command is the name of the operation. For example, create is the command that creates the project. We can run vue –help to see which commands are available:

Usage: vue <command> [options]

Options:
  -V, --version                              output the version number
  -h, --help                                 output usage information

Commands:
  create [options] <app-name>                create a new project powered by vue-cli-service
  add [options] <plugin> [pluginOptions]     install a plugin and invoke its generator in an already created project
  invoke [options] <plugin> [pluginOptions]  invoke the generator of a plugin inan already created project inspect [options] [paths...]  inspect the webpack configin a project with vue-cli-service
  serve [options] [entry]                    serve a .js or .vue file in development mode with zero config
  build [options] [entry]                    build a .js or .vue file in production mode with zero config
  ui [options]                               start and open the vue-cli ui
  init [options] <template> <app-name>       generate a project from a remote template (legacy API, requires @vue/cli-init)
  config [options] [value]                   inspect and modify the config
  outdated [options]                         (experimental) check for outdated vue cli service / plugins
  upgrade [options] [plugin-name]            (experimental) upgrade vue cli service / plugins
  migrate [options] [plugin-name]            (experimental) run migrator for an already-installed cli plugin
  info                                       print debugging information about your environment

  Run vue <command> --help for detailed usage of given command.
Copy the code

2.1 Options Options are commands to configure, such as vue create –help, you can see:

Usage: create [options] <app-name>

create a new project powered by vue-cli-service

Options:
  -p, --preset <presetName>       Skip prompts and use saved or remote preset
  -d, --default                   Skip prompts and use default preset
  -i, --inlinePreset <json>       Skip prompts and use inline JSON string as preset
  -m, --packageManager <command>  Use specified npm client when installing dependencies
  -r, --registry <url>            Use specified npm registry when installing dependencies (only for npm)
  -g, --git [message]             Force git initialization with initial commit message
  -n, --no-git                    Skip git initialization
  -f, --force                     Overwrite target directory if it exists
  --merge                         Merge target directory if it exists
  -c, --clone                     Use git clone when fetching remote preset
  -x, --proxy <proxyUrl>          Use specified proxy when creating project
  -b, --bare                      Scaffold project without beginner instructions
  --skipGetStarted                Skip displaying "Get started" instructions
  -h, --help                      output usage information
Copy the code
  • The name of the configuration option starts with – or —
  • parameter
    • A value passed to a command as if the command were a function and the value is an argument to the function when it is executed.
    • Vue create my-app My-app is the parameter, indicating the project name

3. Start

3.1 Initializing the Project

First, create a new empty folder and initialize the project

$ npm init
Copy the code

3.2 Writing JavaScript Scripts

Write a JavaScript script called index.js:

#! /usr/bin/env node console.log('hello cli');Copy the code

#! /usr/bin/env /usr/bin/env /usr/bin/env /usr/bin/env /usr/bin/env

3.3 modify the package. The json

{
  "bin": {
    "youzan": "./index.js"
  }
}
Copy the code

3.4 npm link

The NPM link is used to establish a connection between a local project and a local NPM module so that module tests can be performed locallynpm linkAnd then you can executeyouzanThe command

4. Configure cli options

The use of commander.js is recommended

$ yarn add commander --save
Copy the code

program.option('-ig,--initgit', 'init git'); console.log('Options: ', program.opts()); // Get the option valueCopy the code

4.1 First Command

Just like vue create, we need to complete a command to create the project, and we can configure the template and whether or not git is initialized

const { program } = require('commander');
const handleCreate = (params, options) = > {
  console.log(params, options);
};

program
  .command('create <name> [destination]')
  .description('create a project')
  .action((name, destination) = > {
    handleCreate({ name, destination }, program.opts());
  });

program.option('-ig,--initgit'.'init git');

program.parse(process.argv);
Copy the code
  • .command() is used to configure commands and parameters, where <> indicates that parameters are required and [] indicates that parameters are optional.

  • .description() Adds a command description

  • .action() is used to add action functions, and the input arguments are used to configure commands

  • program.parse(process.argv); Process command line arguments

5. User interaction problems

To use Inquirer. Js, click on the documentation github.com/SBoudrias/I… Use this article at blog.csdn.net/qq_26733915…

$ yarn add inquirer --save
Copy the code
const handleCreate = (params, options) = > {
  console.log(params, options);
  inquirer
    // User interaction
    .prompt([
      {
        type: 'input'.name: 'author'.message: 'author name? '
      },
      {
        type: 'list'.name: 'template'.message: 'choose a template'.choices: ['tpl-1'.'tpl-2']
      }
    ])
    .then((answers) = > {
      // Generate project files based on the answers and options and parametersgenFiles({ ... answers, ... params, ... options }); }) .catch((error) = > {
      console.error(error);
    });
};
Copy the code

6. Generate project files on demand

Create the Templates directory in your project to hold the template files

+-- templates
|   +-- tpl-1
    |   +-- package.json
|   +-- tpl-2
    |   +-- package.json
Copy the code

Then, copy the file to the specified directory.

Using Metalsmith, you can easily copy files to a specified directory or create a new directory if the specified directory does not exist

// Get the path where the command is run
const getCwd = () = > process.cwd();

const genFiles = (options) = > {
  // Template directory
  const templateSrc = path.resolve(__dirname, `./templates/${options.template}`);
  // The project specifies the build directory. If there is no configuration directory in the command, a new directory with the name of the project will be generated in the directory where the current command is run
  const destination = options.destination
    ? path.resolve(options.destination)
    : path.resolve(getCwd(), options.name);

  Metalsmith(__dirname)
    .source(templateSrc)
    .destination(destination)
    .build((err) = > {
      if (err) {
        console.error(err); }}); }; .source() and.destination() configure the source and destination directories for replication respectively. Absolute paths are preferredCopy the code

7. Dynamically render the target file

The name and author equivalents in the generated package.json are fixed and should vary with the project name. So package.json must be a template file that is rendered as an object file according to the actual situation while being generated;

7.1 Template uses EJS

yarn add ejs --save
Copy the code

7.2 Modifying template Files

Add handlers between.destination() and.build()

const ejs = require('ejs'); Const renderPathList = ['package.json', // 'SRC /main.js', ] Metalsmith(__dirname) .source(templateSrc) .destination(destination) .use((files) => { If (renderPathList.includes(key)) {const file = files[key]; // const STR = file.contents. ToString (); // const newContents = ejs.render(STR, options); File.contents = buffer.from (newContents); }}); }) .build((err) => { if (err) { console.error(err); }});Copy the code

This way, with a simple CLI, more commands can be added themselves.

8. Command line wait optimization

If the command execution is time-consuming, a friendly prompt to wait is also necessary. Ora can help you

Const genFiles = ()=>{// todo generates files on top... } const ora = require('ora') const processGenFiles = ora('Create project... ') processGenfiles.start () // The progress bar starts to await genFiles(answers); processGenFiles.succeed(`Create project complete: i18n-b-${name}`)Copy the code

9. Git template download

Most CLI tool templates are not locally available, but are downloaded from the Internet. You can use the download-git-repo library as well as the Github API

const { promisify } = require('util')

const clone = async function (repo, desc) {
    const download = promisify(require('download-git-repo')) // download-git-repo: Download and extract a git repository (GitHub, GitLab, Bitbucket)
    const ora = require('ora')
    const process = ora(` download...${repo}`)
    process.start() // Progress bar starts
    await download(repo, desc)
    // download-git-repo Specifies the download method for the export. The first parameter repo is the repository address.
    // GitHub - github:owner/name or simply owner/name
    // GitLab - gitlab:owner/name
    // Bitbucket - bitbucket:owner/name
    process.succeed()
}

await clone('[email protected]:sz-web/i18n-b-dashboard.git', name)
Copy the code

10. Execute shell in Node.js (NPM install as an example)

Shell execution in Node.js typically uses the spawn of child_process to stream output from the main process to the child process

const spawn = async(... args) => {const { spawn } = require('child_process')
    return new Promise(resolve= > {
        constproc = spawn(... args)// Shell spawn is used in node.js to spawn the output stream from the main process to the child process
        proc.stdout.pipe(process.stdout) // The normal flow of the child overlaps the normal flow of the main process
        proc.stderr.pipe(process.stderr) // The child error stream is inserted into the main process error stream
        proc.on('close'.() = > {
            resolve()
        })
    })
}
 await spawn('npm'['install'] and {cwd: `. / ` }) // CWD Specifies the directory where the command is executed
Copy the code

11. Package distribution

11.1 npm init

I did it above and I don’t have to do it again

11.2 npm login

  1. www.npmjs.com Set up an account
  2. Go to your project root directory and run NPM login
    • Enter your username, password and email address

11.2 npm publish

After login, execute NPM publish and publish successfully

12. Use your own NPM package

For example,

  • Global installation
NPM install -g/NPM install -g/NPM installCopy the code
  • The command
  youzan --version
  youzan --help
  youzan init <project-name>
Copy the code

Further reading

  • Build your own CLI zhuanlan.zhihu.com/p/242656395

The last

  • A lot of likes will make you look good
  • Leave a lot of messages and you’ll get rich