• 从零开始搭建自己的cli脚手架


    创建 cli

    创建文件

    创建新的项目文件夹

    $ mkdir cli # 创建项目文件夹
    $ cd cli # 进入文件夹
    $ npm init # 初始化项目生成package.json文件
    
    • 1
    • 2
    • 3

    初始化项目

    生成的package.json文件如下

    {
      "name": "cli-demo",
      "version": "1.0.0",
      "description": "开发个人cli工具脚手架",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": ["cli", "脚手架"],
      "author": "绝对零度",
      "license": "ISC"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    创建执行文件

    $ mkdir bin # 创建bin文件夹
    $ cd bin # 进入文件夹
    $ type null > index.js # 穿件执行文件
    $ cd ./../
    $ mkdir template # 创建模版文件目录
    $ cd template
    $ cd .> index.html # 穿建模板文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    修改package.json文件,增加以下代码:

    "main": "./bin/index.js",
    "bin": "./bin/index.js",
    
    • 1
    • 2

    测试 index.js 文件

    index.js文件增加

    #! /usr/bin/env node
    console.log("test link is successed");
    
    • 1
    • 2

    关于这句话的意思:

    在写 npm 包的时候需要在脚本的第一行写上#!/usr/bin/env node ,用于指明该脚本文件要使用 node 来执行。
    /usr/bin/env 用来告诉用户到path目录下去寻找node#!/usr/bin/env node可以让系统动态的去查找node,已解决不同机器不同用户设置不一致问题。

    PS: 该命令必须放在第一行, 否者不会生效

    链接到全局

    在 cmd 中执行npm link命令

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oKcUWl2f-1654848419552)(./imgs/link.png)]

    执行成功后,然后测试命令是否生效

    # 执行
    $ cli-demo
    
    • 1
    • 2

    结果输出

    index.js
    
    • 1

    说明link成功!!

    常用工具

    说明

    一个脚手架的搭建,需要很多额外的工具库,比如命令行指令的commander,命令问答的 inquirer等等,本次重点介绍一些常用的 node 工具库:

    序号库名主要作用
    1commandernode.js命令行工具
    2inquirer命令行交互工具

    commander使用

    安装commander工具,相关 Api 中文文档请移至>>>这里

    或者请移步>>>这里

    npm install commander
    
    • 1

    安装成功 package.json 增加以下信息

    "dependencies": {
        "commander": "^9.3.0"
    }
    
    • 1
    • 2
    • 3

    测试commander命令

    现在在bin/index.js文件中增加如下代码:

    const { program } = require("commander");
    
    program
      .command("create <my-app>")
      .description("create a new project")
      .option("-f, --force", "overwrite target directory if it exist")
      .action((name, options) => {
        // 打印执行结果
        console.log("name", name, "options", options);
        // require('../bin/create.js')(name, options)
      });
    program.parse();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行测试一下,看是否能正常使用,可以直接使用node命令执行index.js文件,或者使用前面配置的全局命令cli-demo来执行。

    createCommand的命令,后面跟随的cli-demo<my-app>实际参数,-foption指令

    $ node index.js  create cli-demo -f
    # or
    $ cli-demo create cli-demo -f
    # 打印出结果
    # name cli-demo options { force: true }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当使用-h时,则会显示帮助信息

    Usage: index [options] [command]
    
    Options:
      -h, --help                 display help for command
    
    Commands:
      create [options] <my-app>  create a new project
      help [command]             display help for command
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    命令行交互工具inquirer使用

    命令行交互工具在脚手架开发中占有很重要的地位,主要使用在对用户的询问与选择,执行一些命令

    安装inquirer工具,相关 Api 文档请移至>>>这里

    本例中inquirer主要是用于列表选择等问答。

    为了代码更好封装,我们将创建文件夹的相关逻辑单独抽离出来,创建 create.js 文件,并增加以下代码

    #! /usr/bin/env node
    const inquirer = require("inquirer");
    
    inquirer
      .prompt([
        {
          name: "action",
          type: "list",
          message:
            "The target folder already exists, Please select the action below",
          choices: [
            {
              name: "Overwrite",
              value: "overwrite",
            },
            {
              name: "Cancel",
              value: false,
            },
          ],
        },
      ])
      .then((answers) => {
        // 交互输出的结果值
        console.log(answers);
        if (answers.action === "overwrite") {
          console.log("重写目标文件夹");
        } else {
          console.log("取消,结束操作");
        }
      })
      .catch((error) => {
        if (error.isTtyError) {
          // Prompt couldn't be rendered in the current environment
        } else {
          // Something else went wrong
        }
      });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    运行一下试试,看看能否正常进行命令交互

    $ node .\index.js
    ? The target folder already exists, Please select the action below (Use arrow keys)
    > Overwrite
      Cancel
    
    • 1
    • 2
    • 3
    • 4

    键盘上下键作为选择键,enter 作为选择确认键,现在直接按下 enter

    ? The target folder already exists, Please select the action below Overwrite
    # { action: 'overwrite' }
    # 重写目标文件夹
    
    • 1
    • 2
    • 3

    inquirer返回的是一个promise,所以,我们可以使用async await 代替

    创建本地文件夹

    在创建本地文件夹之前,我们需要先判断本地是否已经存在此文件夹,如果存在则需要告知用户是否删除或覆盖文件夹。另外可以根据用户的创建命令,直接强制创建文件夹(-f --force);

    所以我们需要用到nodefs.existsSync:

    以同步的方法检测目录是否存在。如果目录存在 返回 true ,如果目录不存在 返回 false

    删除文件夹则需要使用 fs 的拓展工具fs-extra中的fs.remove(targetPath)方法,需要安装一下npm install fs-extra;

    完善一下代码:

    const path = require("path");
    const fs = require("fs-extra");
    const inquirer = require("inquirer");
    module.exports = async function (name, options) {
      const cwd = process.cwd();
      const targetPath = path.join(cwd, name);
      console.log(targetPath);
      // 判断目标是否存在
      if (fs.existsSync(targetPath)) {
        // 是否强制创建目录
        if (options.force) {
          // 移除旧目录
          console.log(`\r\nRemoving....`);
          // await fs.remove(targetPath)
        } else {
          // 是否要覆盖目录
          let { action } = await inquirer.prompt([
            {
              name: "action",
              type: "list",
              message:
                "The target folder already exists, Please select the action belowcls",
              choices: [
                {
                  name: "Overwrite",
                  value: "overwrite",
                },
                {
                  name: "Cancel",
                  value: false,
                },
              ],
            },
          ]);
          if (!action) {
            return;
          } else if (action === "overwrite") {
            // 如果选择重写目录
            console.log(`\r\nRemoving....`);
            // await fs.remove(targetPath)
          }
        }
      }
      // 创建目录
      console.log("创建目录文件夹成功");
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    例如我们需要创建一个名为template的模版文件夹,且本地存在template文件夹

    $ ****\cli>cli-demo create template
    index.js
    name template options {}
    $ ****\cli\template
    ? The target folder already exists, Please select the action belowcls (Use arrow keys)
    > Overwrite
      Cancel
    # 按下enter键则输出
    
    Removing....
    创建目录文件夹成功
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    此时,逻辑代码完全按照预想的预案执行

    (未完待续!!!!)

  • 相关阅读:
    2.2 发布与订阅python
    信创就用国产的 Solon Java Framework,v2.6.0 发布
    HTML5 新增的input 表单属性
    112. 求每次滑动窗口中的最大值(考察队列)
    坚挺市场下,ICT企业如何赢盈并重持续增长–2022年B2B企业新增长趋势之ICT篇
    nonaDlA 逻辑分析仪 使用记录
    VS Code 自动选择Python3 venv
    万邦抖音获取douyin分享口令url API
    ML.NET 更新
    【算法-哈希表4】 三数之和(去重版)
  • 原文地址:https://blog.csdn.net/cheng521521/article/details/125223462