【npm】搞个自己的CLI拉取基础工程

一、CLI原理

CLIcommand-line interface — 命令行界面):commander 这个node库,有很多cli使用了它,它可以帮助开发者简化实现命令流程。不过我们的第一步还是应该先搞清楚,如何通过npm如何实现命令行。(其实就和普通的bat命令一样)

一个简单的cli
package.json 中有一个 bin 字段,指定各个内部命令对应的可执行文件的位置。
②在包安装时,如果是全局安装,npm 将会把 package.json 里定义的 bin 文件软连接到全局 node_modules/bin
③如果是非全局安装,会软链接到项目文件夹./node_modules/.bin/
④根据下面代码的配置,当我们全局安装此包后,在任意位置运行 cli-test,都会执行全局 node_modules 中的 cli-test 文件。

/*cli-test 的 package.json*/
"bin": {
    "cli-test": "./bin/cli-test"
}

cli文件:
下面的第一行必写,是告诉操作系统这个文件中的代码用node可执行程序去运行它。 后面就做我们要做的事情就行了 。

#!/usr/bin/env node
//do something

一个简单的cli就完成了
其实有很多三方cli也并没有用类似 commandernode 库,如果我们的 cli 足够简单,以上这样就可以了。


二、commandernode.js命令行界面的完整解决方案

1. Commander.js中文文档链接一
2. Commander.js中文文档链接二
3. 仿vue的前端自定义cmd命令拉取项目脚手架

那下面开练吧! 我们先来看到脚手架工程中可能会用到的js~

A. commander.js

//安装
npm install commander --save
//调用
const program = require("commander");

B. chalk.js:修改控制台中字符串的样式【打印日志的时候,根据日志级别的不同,显示不同的颜色】,包括:①字体样式(加粗、隐藏等);②字体颜色;③背景颜色。 创建log.js如下:


//安装
npm install chalk --save-dev

//调用
const chalk = require('chalk');
const log = console.log;

const success = function (msg) {
  log(chalk.bgGreen(' SUCCESS ') + chalk.green(' ' + msg));
}

const warn = function (msg) {
  log(chalk.bgYellow(' WARN ') + chalk.yellow(' ' + msg));
}

const error= function (msg) {
  log(chalk.bgRed(' ERROR') + chalk.red(' ' + msg));
}

const info = function (msg) {
  log(chalk.bgBlackBright(' INFO ') + chalk.gray(' ' + msg));
}

const tip = function (msg) {
  log(chalk.bgBlue(' TIP') + chalk.blue(' ' + msg));
}

module.exports = { success, warn, error, info, tip }

C. fs-extra.js:文件操作相关工具库,该模块是系统fs模块的扩展,提供了更多便利的 API,并继承了fs模块的 API

//安装
npm install --save-dev fs-extra
//调用
const fs = require("fs-extra");

相关API了解下:


a. 文件拷贝:copy(src, dest, [option],callback);【copySync()】
/**
option对应:
  ①clobber (boolean): 覆盖现有的文件或目录,默认true
  ②dereference (boolean): dereference symlinks, default is false
  ③preserveTimestamps (boolean): 最后修改和访问时间和原始的源文件一致,默认为false
  ④filter: 函数或正则表达式过滤复制文件,返回true包含,否则排除
*/
fs.copy('/tmp/myfile', '/tmp/mynewfile', function (err) {
   if (err) return console.error(err); 
   console.log("success!")
}) //拷贝文件
fs.copy('/tmp/mydir', '/tmp/mynewdir', function (err) {
   if (err) return console.error(err) 
   console.log('success!')
}) //拷贝目录

============================================

b. 清空目录:emptydir() 【emptyDirSync(), emptydirSync()】
/**
确保一个目录是空的。如果目录非空删除目录内容。
如果目录不存在,就创建一个。目录本身并不是删除。
*/
fs.emptyDir('/tmp/some/dir', function (err) {
  if (!err) console.log('success!')
})

============================================

c. 创建文件:ensureFile() 【createFileSync(),ensureFileSync()】
/**
确保文件存在。如果被请求的文件的目录不存在,创建这些目录。
如果文件已经存在,它不修改。
*/
var file = '/tmp/this/path/does/not/exist/file.txt';
fs.ensureFile(file, function (err) { 
   console.log(err) ;
})

=============================================

d.创建目录:ensureDir()  【ensureDirSync()】
/**
确保目录的存在。如果目录结构不存在,就创建一个
*/
var dir = '/tmp/this/path/does/not/exist';
fs.ensureDir(dir, function (err) {
   console.log(err);
})

D. inquirer.js如果想自己做一个脚手架或者在某些时候要与用户进行交互,这个时候就不得不提到了这个库了。

由于交互的问题种类不同,inquirer为每个问题提供很多参数:
type:表示提问的类型,包括:input, confirm, list, rawlist, 
                      expand, checkbox, password, editor
name: 存储当前问题回答的变量;
message:问题的描述;
default:默认值;
choices:列表选项,在某些type下可用,并且包含一个分隔符(separator);
validate:对用户的回答进行校验;
filter:对用户的回答进行过滤处理,返回处理后的值;
transformer:对用户回答的显示效果进行处理
    (如:修改回答的字体或背景颜色),但不会影响最终的答案的内容;
when:根据前面问题的回答,判断当前问题是否需要被回答;
pageSize:修改某些type类型下的渲染行数;
prefix:修改message默认前缀;
suffix:修改message默认后缀。

案例如下:

const promptList = [{
    type: 'input',
    message: '设置一个用户名:',
    name: 'name',
    default: "test_user" // 默认值
},{
    type: 'input',
    message: '请输入手机号:',
    name: 'phone',
    validate: function(val) {
        if(val.match(/\d{11}/g)) { // 校验位数
            return val;
        }
        return "请输入11位数字";
    }
},{
    type: "confirm",
    message: "是否使用监听?",
    name: "watch",
    prefix: "前缀"
},{
    type: "confirm",
    message: "是否进行文件过滤?",
    name: "filter",
    suffix: "后缀",
    when: function(answers) { // 当watch为true的时候才会提问当前问题
        return answers.watch
    }
},{
    type: 'list',
    message: '请选择一种水果:',
    name: 'fruit',
    choices: [
        "Apple",
        "Pear",
        "Banana"
    ],
    filter: function (val) { // 使用filter将回答变为小写
        return val.toLowerCase();
    }
},{
    type: "expand",
    message: "请选择一种水果:",
    name: "fruit",
    choices: [
        {
            key: "a",
            name: "Apple",
            value: "apple"
        },
        {
            key: "O",
            name: "Orange",
            value: "orange"
        },
        {
            key: "p",
            name: "Pear",
            value: "pear"
        }
    ]
},{
    type: "checkbox",
    message: "选择颜色:",
    name: "color",
    choices: [
        "red",
        "blur",
        "green",
        "yellow"
    ],
    pageSize: 2 // 设置行数
},{
    type: "password", // 密码为密文输入
    message: "请输入密码:",
    name: "pwd"
},{
    type: "editor",
    message: "请输入备注:",
    name: "editor"
}]

E. execa:据称是更好的子进程管理工具。执行后续的示例,先执行npm install --save execa

const execa = require("execa");
execa("ls").then(result => console.log(result.stdout));

F.get-stream:Get a stream as a string, buffer, or array先执行npm install get-stream

//检查NodeJs版本
const execa = require('execa');
const getStream = require('get-stream');
const stream = execa('node', ['--version']).stdout;
return getStream(stream).then(data => {

 })

G. download-git-repo:Download and extract a git repository (GitHub, GitLab, Bitbucket) from node. 若我们CLI中的具体基础工程是从git上下载的,那就需要用到该库了。
Vue-CLIvue-init中就有调用了该函数

image.png

若我们自己的脚手架中包含了基础工程,则执行命令时拷贝基础工程到我们创建的初始工程中即可。

image.png

H. vue-cli 中用到的其他一些库
user-home:【Get the path to the user home directory — 获取用户主目录的路径】

image.png

tildifyConvert an absolute path to a tilde path: /Users/sindresorhus/dev → ~/dev — 将绝对路径转换为波形路径】

image.png

ora:【Elegant terminal spinner — 在终端里有显示载入动画】

image.png

vue-cli其他js中还引用了其他一些库,这里就先不介绍了,后面再将vue-cli源码 Vue-cli 原理分析好好解读下。下图是vue-cli的一个基本结构。

image.png

小结
搭建一个简易的cli需要:
① 检查node 等的版本号,一般校验不能低于某个版本
② 日志的打印:chalk
③ 搭建过程中的一些提问: inquirer
④ 工程包的拷贝fs-extra 或下载 download-git-repo
pack.json的配置

来看下这个最简易cli的结构吧:

image.png

具体的创建过程,上面已提到过,即基础工程的拷贝过程。这里再一次贴一下相关代码吧

image.png

相关package.json也看下吧:

image.png

将该cli发布到npm中,具体发布过程见将自己的vue组件发布为npm包 & npm私有仓库搭建

image.png

发布成功后,安装cli

npm install eui2-cli -g

按装成功后,执行命令

//projectName: 你的工程名
eui2 create <projectName>

创建工程

image.png

打开工程目录查看

image.png

没错,就是它了

最简易的cli 在git上的地址


三、yeoman自行了解下

大前端的自动化工厂(1)——Yeoman

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355