nodejs 制作从远程仓库下载项目的简易脚手架

2,026 阅读1分钟

需要用到的包

1.commander:解析用户命令行输入和参数,简化简化命令行开发。

  • command -- 定义命令行指令,后面可跟上一个name,用空格隔开,如 .command( 'app [name] ')
  • alias -- 定义一个更短的命令行指令 ,如执行命令 $ app m 与之是等价的
  • description -- 描述,它会在help里面展示
  • option -- 定义参数。它接受四个参数。 第一个参数中,它可输入短名字 -a和长名字--app ,使用  |  或者 , 分隔,在命令行里使用时,这两个是等价的,区别是后者可以在程序里通过回调获取到;

第二个为描述, 会在 help 信息里展示出来;

第三个参数为回调函数,他接收的参数为一个string,有时候我们需要一个命令行创建多个模块,就需要一个回调来处理; 第四个参数为默认值

  • action -- 注册一个callback函数,这里需注意目前回调不支持let声明变量
  • parse -- 解析命令行 2.chalk:美化命令行的模块。

3.download-git-repo:来下载github库的代码

download(repository, destination, options, callback)

repository 是远程仓库地址;

GitHub - github:owner/name 或者简写为 owner/name github上面的项目只需要 用户名/仓库名

GitLab - gitlab:owner/name

Bitbucket - bitbucket:owner/name

destination 是存放下载的文件路径,也可以写文件名,默认当前目录; options 是选项,比如 { clone:boolean } 表示用 http download 还是 git clone

4.fs-extra:fs-extra是fs的一个扩展,提供了非常多的便利API,并且继承了fs所有方法和为fs方法添加了promise的支持。它应该是 fs 的替代品

5.inquirer:命令行进行交互,通用的命令行用户界面集合,用于和用户进行交互:blog.csdn.net/qq_26733915…

{
  type: "input|number|confirm|list|rawlist|expand|checkbox|password|editor".//默认值为输入:input
  name: String,//此问题的id,在后面查找此问题的结果时可以直接使用options.example取到此问题的值
  message:String | Function,//要打印的问题。如果定义为函数,则第一个参数将是当前查询者会话答案。缺省值为name(后跟冒号)
  default:String | Number | Boolean | Array | Function,//如果未输入任何内容,则使用默认值,或者返回默认值的函数。如果定义为函数,则第一个参数将是当前查询者会话答案,
  choices:Array|Function,//(Array | Function)Choices数组或返回choices数组的函数。如果定义为函数,则第一个参数将是当前查询者会话答案。数组值可以是simple numbers,strings或objects包含name(显示在列表中),value(保存在上面讲的name中和short(选择后显示)属性
  validate:Function,//校验输入的值是否符合要求
  filter:Function,//接收用户输入并返回要在程序内部使用的过滤值,过滤的值将会返回到`Answers `中,询问用户是否正确
  when:Boolean|Function,//接收当前用户的答案哈希,并应返回true或false取决于是否应询问此问题
  pageSize:Number,//当使用list,rawList,expand,checkbox的时候可能存在分页
  prefix:String,//更改默认的前缀消息
  suffix:String//更改默认的后缀消息

}

6.node-emoji:输入 表情符

7.ora:node.js 命令行环境的 loading效果, 和显示各种状态的图标(美观)下载的时候会有转圈特效

8.read-pkg:读取项目的package.json文件

9.validate-npm-package-name:验证包名是否合法

开始搭建(练手)

1、创建lp-cli文件夹

2、在该目录下执行npm init -y,生成package.json文件

3、安装所需要的包

npm i commander chalk download-git-repo fs-extra inquirer ora read-pkg validate-npm-package-name --save

4、在package.json文件中新建一个bin文件夹,在bin文件夹下新建一个文件lp.js,这个文件夹就是我们脚手架的入口文件,然后写几句代码:

#!/usr/bin/env node
console.log('hello')

注意:#!/usr/bin/env node 表明这是一个可执行的应用,它帮助脚本找到node的脚本解释器,可以理解为调用系统中的node来解析我们的脚本,当 系统看到这行时,能够沿着该路径查找node并执行。所以需要执行的脚本文件第一行都必须写上这句,否者会有错误提示。

在终端执行lp.js: node ./bin/lp.js

5、输入node ./bin/lp执行太过麻烦,我们可以在package.json里定义一个bin命令,bin用来指定每个命令所对应的可执行文件地位置:

然后执行npm link。它将会把lp这个字段复制到npm的全局模块安装文件夹node_modules内,并创建符号链接(symbolic link,软链接),也就是将 lp 的路径加入环境变量 PATH

最后执行 lp和执行 node ./bin/lp命令结果一样:

6、完善入口文件lp.js:在这里面定义command命令

#!/usr/bin/env node
const  program = require('commander')
// 定义当前版本
// 定义使用方法
// 定义四个指令
program.version(require('../package.json').version)
.usage('<command> [options]')
.command('add', 'add a new template')
.command('init', 'generate a new project from a template')
// 解析命令行参数
program.parse(process.argv)

定义了两个命令分别是lp add和lp init,但是现在系统只认识lp,所以也要在package.json加入add和init命令以及他们的入口文件:

"bin": {
    "lp": "./bin/lp.js",
    "lp-add":"./bin/lp-add.js",
    "lp-init":"./bin/lp-init.js"
  },

最后npm unlink解绑一下,再执行npm link重新绑定命令。

7、在根目录下新建一个template.json文件,内容为{},作为我们存放模版的仓库。

8、lp-add.js

// 交互式命令行
const inquirer = require("inquirer");
// 修改控制台字符串的样式
const chalk = require("chalk");
// node 内置文件模块
const fs = require("fs");
// 读取根目录下的 template.json
const tplObj = require(`${__dirname}/../template`);

// 自定义交互式命令行的问题及简单的校验
let question = [
  {
    name: "name",
    type: "input",
    message: "请输入模板名称",
    // 验证输入的模板名称
    validate(val) {
      if (val === "") {
        return "Name is required!";
      } else if (tplObj[val]) {
        return "Template has already existed!";
      } else {
        return true;
      }
    },
  },
  {
    name: "url",
    type: "input",
    message: "请输入模板地址",
    validate(val) {
      if (val === "") return "The url is required!";
      return true;
    },
  },
  {
    type: "list",
    name: "testchoice",
    message: "What do you want to do?",
    choices: [
      {
        name: "PC",
        value: "pc",
        short: "PC",
      },
      {
        name: "mobile",
        value: "mobile",
        short: "mobile",
      },
    ],
  },
  {
    type: "list",
    name: "pcchoice",
    message: "what type do you  want to use pc?",
    //when据前面问题的回答,判断当前问题是否需要被回答;// 当testchoice为pc的时候才会提问当前问题
    when(answer) {
      return answer.testchoice === "pc";
    },
    choices: [
      {
        name: "PC",
        value: "pc",
        short: "PC",
      },
      {
        name: "Responsive",
        value: "responsive",
        short: "Responsive",
      },
    ],
  },
];

inquirer.prompt(question).then((answers) => {
  console.log("answers", answers);
  // answers 就是用户输入的内容,是个对象
  let { name, url } = answers;
  // 过滤 unicode 字符
  tplObj[name] = url.replace(/[\u0000-\u0019]/g, "");
  // 把模板信息写入 template.json 文件中
  fs.writeFile(
    `${__dirname}/../template.json`,
    JSON.stringify(tplObj),
    "utf-8",
    (err) => {
      if (err) console.log(err);
      console.log("\n");
      console.log(chalk.green("Added successfully!\n"));
      console.log(chalk.grey("The latest template list is: \n"));
      console.log(tplObj);
      console.log("\n");
    }
  );
});

// 其实当我们执行moe add时,commander会尝试在入口文件的目录内寻找可执行文件,找到形如program-command的命令来执行(moe-add)

注意:这里的模版地址,不需要写全部链接,等会根据模板地址去下载项目时,download-git-repo默认去github上下载 如果项目地址是github.com/CavinHuang/… 模板地址只需要:CavinHuang/node-cli-demo

执行:lp add:入口文件定义program.command('add').action(() => {})的时候没有写action这个回调函数,为什么能够执行lp add? 其实当我们执行lp add时,commander会尝试在入口文件的目录内寻找可执行文件,找到形如program-command的命令来执行(lp-add),即找入口文件的可执行文件作为执行这个命令的回调函数

这个时候template.json中也会加上:node-cli-demo :CavinHuang/node-cli-demo

lp-init.js

#!/usr/bin/env node
const program = require("commander");
const chalk = require("chalk");
const ora = require("ora");
const download = require("download-git-repo");
const tplObj = require(`${__dirname}/../template`);
program.usage("<template-name> [project-name]");
program.parse(process.argv);
if (program.args.length < 1) return program.help();

// 好比 vue init webpack project-name 的命令一样,第一个参数是 webpack,第二个参数是 project-name
let templateName = program.args[0];
let projectName = program.args[1];
if (!tplObj[templateName]) {
  console.log(chalk.red("\n Template does not exit! \n "));
  return;
}
if (!projectName) {
    console.log(chalk.red('\n Project should not be empty! \n '))
    return
}
url = tplObj[templateName]
console.log("url", url)
console.log(chalk.white('\n Start generating... \n'))
// 出现加载图标
const spinner = ora("Downloading...");
spinner.start();
// 执行下载方法并传入参数
// download(repository, destination, options, callback)
// download第一个参数是github:
// repository 是远程仓库地址;destination 是存放下载的文件路径,也可以写文件名,默认当前目录;options 是选项,比如 { clone:boolean } 表示用 http download 还是 git clone 。
//  比如远程地址是https://github.com/yangzicheng/command-line   repository只要是yangzicheng/command-line 
download(
    url,
    projectName,
    err=>{
        if(err){
            spinner.fail();
            console.log(chalk.red(`Generation failed. ${err}`))
            return  
        }
        // 结束加载图标
        spinner.succeed();
        console.log(chalk.cyan('\n Generation completed!'))
        console.log(chalk.cyan('\n To get started'))
        console.log(chalk.cyan(`\n    cd ${projectName} \n`))
    }
)

执行lp init node-cli-demo demo:

这个时候在当前目录增加了demo文件夹,里面内容是github.com/CavinHuang/…

发布

1、npm login

2、npm pubilsh (去npm上查询是否存在此包名)

使用

npm i -g (包名) 使用命令:lp、lp add、lp init

参考:blog.csdn.net/qq_40766509…

比较全面的,完整的项目:github.com/lppwlwzj/pr…