zhihao-cli v1.0.2
准备工作
必备的知识库
在完善构建脚手架前,需要引入一些脚手架构建中必须用到的工具库。
commander
可以自定义一些命令行指令,在输入自定义的命令行的时候,会去执行相应的操作inquirer
可以在命令行询问用户问题,并且可以记录用户回答选择的结果fs-extra
是fs的一个扩展,提供了非常多的便利API,并且继承了fs所有方法和为fs方法添加了promise的支持。 就是操作本地目录chalk
可以美化终端的输出figlet
可以在终端输出logoora
控制台的loading动画git-clone
下载远程模板
简单三步做好准备工作~
第一步:注册一个npm的账号,用于后面发包
第二步:mkdir zhihao-cli && cd zhihao-cli && npm init -y // 创建文件夹 切到文件夹目录 初始化项目
第三步:之后在 `package.json` 文件中填入一些基本的信息即可
{
"name": "zhihao-cli",
"version": "1.0.0",
"description": "A tool for cli creation",
"main": "index.js",
"scripts": {
"pub:beta": "npm publish --tag=beta",
"pub": "npm publish"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"keywords": [
"create",
"cli"
],
"author": "your name",
"license": "ISC"
开始造轮子
第一步:定义命令
相信大家都用过脚手架,脚手架工具一般都会提供相应的脚本命令以完成一些操作。比如查看版本号的 --version
, 查看帮助信息的 --help
等等。在这一步骤中,我们也先来定义下属于我们自己脚手架的命令
创建脚本文件
首先,我们在项目的根目录下创建 bin
目录,然后在该文件夹下创建定义脚本的文件 index.js
。创建完成后,在 package.json
文件中定义命令对应的文件:
{
// ...
"name":"my-cli",
"bin": "./bin/index.js"
}
其中,bin
指定了我们日后在使用脚手架时所需要输入的命令,bin/index.js
指定输入命令后所执行的脚本文件路径。
注意:**npm link
**会链接这个包,指定到node_module进行软链接,如果**package.json
**有**bin
**,那链接完,输入**package.json
**文件的**name
**,即**my-cli
**后会执行**bin
**下的文件 **./bin/index.js
**
编写脚本
命令行界面
需要用到的相关插件:
Commander.js node.js命令行界面的完整解决方案,
npm install commander
我们在脚本中定义:
#!/usr/bin/env node
// 这里的注释 #!/usr/bin/env node 是一个 shebang,它告诉操作系统:运行这个脚本时,
//应该使用哪个解释器来执行这个脚本。在这个例子中,解释器是 "node"。 这个 shebang 是必需的,
//因为如果没有指定解释器,操作系统就不知道如何执行这个脚本。
// CommonJS (.cjs)
const { program } = require('commander');
// ----------commander的使用----------------
// 通过这个选项可以修改帮助信息的首行提示 例如:Usage: my-command [options] [command]
program
.name("zhihao-cli")
.usage("<command> [options]")
//选项:
//Commander 使用.option()方法来定义选项,同时可以附加选项的简介。
//每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(--后面接一个或多个单词),使用逗号、空格或|分隔。
// 另一类选项则可以设置参数(使用尖括号声明在该选项后,如--expect <value>)。如果在命令行中不指定具体的选项及参数,
//则会被定义为undefined。 <>表示必填参数,[]表示选填参数
program
.option('-d, --debug', 'output extra debugging')
.option('-s, --small', 'small pizza size')
.option('-p, --pizza-type <type>', 'flavour of pizza');
// 命令行:
//Commander 使用.command()方法来定义命令行,同时可以附加命令行的简介。
// .command()的第一个参数为命令名称,参数可为必选的(尖括号表示)、
//可选的(方括号表示)或变长参数(点号表示,如果使用,只能是最后一个参数)。第二个参数为命令简介
program
.command('clone <source> [destination]')
.description('clone a repository into a newly created directory')
.action((source, destination) => {
console.log('clone command called');
});
// 解析命令行参数。解析这些参数并将它们关联到程序中的对应操作 process.argv 是 Node.js 运行时环境中的全局变量
// 打印process.argv有2个参数,第一个是node的执行路径,第二个是当前执行脚本的路径,
program.parse(process.argv);
//它用于获取命令行中传递的所有参数和选项信息,并以一个对象的形式返回。
// 这个对象中的键名就是用户传递的选项名或参数名,而键值则是用户传递的选项值或参数值
// const options = program.opts();
// console.log(options);
注意:这里的注释 #!/usr/bin/env node 是一个 shebang,它告诉操作系统:运行这个脚本时,应该使用哪个解释器来执行这个脚本。在这个例子中,解释器是 "node"。 这个 shebang 是必需的,因为如果没有指定解释器,操作系统就不知道如何执行这个脚本。
编写完成后,如果想要在命令行中进行验证,此时我们可以在命令行中输入以下命令:
npm link
然后在命令行中输入
zhihaoa-cli
有我们配置好的选项以及命令行
注意:zhihao-cli —help
是默认配置有的
美化终端输出
chalk 终端美化工具
npm install chalk@4.0.0
注意: Chalk 5 is ESM.模块,如果想要用cjs模块,安装4版本的
脚本文件:
const chalk = require('chalk');
//-----------------chalk的使用-----------------
console.log(chalk.blue('Hello world!'));
console.log(chalk.blue.bgRed.bold('Hello world!'));
console.log(chalk.blue('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz'));
console.log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!'));
命令行交互模块
inquirer 命令行交互
常见交互式命令行用户界面的集合。
npm install --save inquirer@8.0.0
Inquirer v9 及更高版本是本机 esm 模块,这意味着您不能再使用 commonjs 语法require('inquirer')
。所以需要到8.0.0
const inquirer = require('inquirer'); //命令行交互
//-----------------inquirer的使用-----------------
// inquirer.prompt(questions, answers) -> promise 启动提示界面(查询会话)
// questions: array 包含Question 对象
// Question 对象包含以下属性:
// type: 提示的类型,包括:input, number, confirm, list, rawlist, expand, checkbox, password, editor
// name: 键名,用于获取用户输入的值
// message: 提示信息
// default: 默认值
// choices: 选项,包含:type为list, rawlist, expand, checkbox时的选项
// answers: 默认值(对象){}
inquirer.prompt([
{
type: 'input',
name: 'name',
message: '请输入你的姓名'
},
{
type: 'input',
name: 'age',
message: '请输入你的年龄'
}]).then(answers => {
console.log(answers);
}).catch(err => {
console.log(err);
})
控制台加载效果
ora 控制台loading效果
npm install ora@5.0.0
const ora = require('ora'); //loading效果
//-----------------ora的使用----------------
//启动加载效果
const spinner = ora('Loading unicorns').start();
//1秒后加载效果变为Loading rainbows
setTimeout(() => {
spinner.color = 'yellow';
spinner.text = 'Loading rainbows';
}, 1000);
//3秒后显示为加载成功或者失败
setTimeout(() => {
// spinner.succeed('Loading rainbows succeed');
spinner.fail('Loading rainbows failed');
}, 3000);
终端艺术字
figlet
:生成终端艺术字
npm install figlet
const figlet = require("figlet"); //打印大字体
//-----------------figlet的使用----------------
figlet("Hello World!!", function (err, data) {
if (err) {
console.log("Something went wrong...");
console.dir(err);
return;
}
console.log(data);
});
模仿vue
#!/usr/bin/env node
// 这里的注释 #!/usr/bin/env node 是一个 shebang,它告诉操作系统:运行这个脚本时,
//应该使用哪个解释器来执行这个脚本。在这个例子中,解释器是 "node"。 这个 shebang 是必需的,
//因为如果没有指定解释器,操作系统就不知道如何执行这个脚本。
// CommonJS (.cjs)
const { program } = require('commander');
const chalk = require('chalk');
const inquirer = require('inquirer'); //命令行交互
const ora = require('ora'); //loading效果
const figlet = require("figlet"); //打印大字体
const fs = require('fs-extra'); //文件操作
const path = require('path'); //路径操作
const gitClone = require('git-clone'); //克隆项目
//首行显示提示
program.name('zhihao-cli').usage('<command> [options]');
//显示版本号
program.version(`v${require('../package.json').version}`, '-v, --version');
//增加创建命令
program
.command('create <app-name>')
.description('创建一个项目')
.action(async(name) => {
// 创建一个名字为name的项目,把我们模板项目代码放到这个文件下的目录下
// 1.判断当前目录是否为空 process.cwd() 获取当前目录(node命令) fs.existsSync() 判断文件是否存在
const targetDir = path.join(process.cwd(), name);
if(fs.existsSync(targetDir)){
//存在文件夹 先询问是否清空
const answers = await inquirer.prompt([
{
type: 'confirm',
name: 'isRemove',
message: '当前目录不为空,是否清空当前目录?',
default: true
}
]);
if(answers.isRemove){
//清空当前目录
// 使用 fs.rmdir() 时需要添加 { recursive: true } 参数来指示要删除整个目录。
fs.rmdir(targetDir, { recursive: true })
console.log('targetDir: ', targetDir);
console.log('清空成功');
}else{
return;
}
}
// 2.创建目录
//定义一个对象,存放需要创建的克隆项目模板地址
const projectList = {
'vue': "https://github.com/kfc-vme50/vue-template.git",
'react': 'https://github.com/kfc-vme50/react-template.git',
'react&ts': 'https://github.com/kfc-vme50/react-template-ts.git',
'vue&ts': 'https://github.com/kfc-vme50/vue-template-ts.git',
}
const res = await inquirer.prompt([
{
type: 'list',
name: 'type',
message: '请选择项目框架',
choices: [
{
name: 'vue',
value: 'vue'
},
{
name: 'react',
value: 'react'
}
]
},
{
type: 'list',
name: 'ts',
message: '是否需要ts',
choices: [
{
name: '是',
value: true
},
{
name: '否',
value: false
}
]
}
])
const key = res.type + (res.ts ? '&ts' : '');
const spinner = ora('正在下载项目模板...').start();
// 3.克隆项目 gitClone 参数1:项目地址 参数2:目标地址 参数3:配置项(有选择克隆的分支) 参数4:回调函数
gitClone(projectList[key], targetDir, { checkout: 'main'},(err) => {
if(err){
spinner.fail(chalk.red('下载失败'));
}else {
spinner.succeed(chalk.green('下载成功'));
// 删除.git文件
// fs.removeSync(path.join(targetDir, '.git'));
// 成功后提示操作
console.log(chalk.green('Download success'));
console.log(chalk.green('cd ' + name));
console.log(chalk.green('npm install'));
console.log(chalk.green('npm run dev'));
}
})
});
// 给help信息增加提示
program.on('--help', function(){
console.log(figlet.textSync(
"zhihao-cli",
{
font: "Ghost",
horizontalLayout: "default",
verticalLayout: "default",
width: 80,
whitespaceBreak: true,
}))
});
//解析命令
program.parse(process.argv);
发包
npm login
登陆自己的login ,记得是npm源
npm publish
在目录下执行,则可以发包
步骤文件
#!/usr/bin/env node
// 这里的注释 #!/usr/bin/env node 是一个 shebang,它告诉操作系统:运行这个脚本时,
//应该使用哪个解释器来执行这个脚本。在这个例子中,解释器是 "node"。 这个 shebang 是必需的,
//因为如果没有指定解释器,操作系统就不知道如何执行这个脚本。
// CommonJS (.cjs)
const { program } = require('commander');
const chalk = require('chalk');
const inquirer = require('inquirer'); //命令行交互
const ora = require('ora'); //loading效果
const figlet = require("figlet"); //打印大字体
//-----------------figlet的使用----------------
// figlet("Hello World!!", function (err, data) {
// if (err) {
// console.log("Something went wrong...");
// console.dir(err);
// }
// console.log(data);
// });
//-----------------ora的使用----------------
// //启动加载效果
// const spinner = ora('Loading unicorns').start();
// //1秒后加载效果变为Loading rainbows
// setTimeout(() => {
// spinner.color = 'yellow';
// spinner.text = 'Loading rainbows';
// }, 1000);
// //3秒后显示为加载成功或者失败
// setTimeout(() => {
// // spinner.succeed('Loading rainbows succeed');
// spinner.fail('Loading rainbows failed');
// }, 3000);
//-----------------inquirer的使用-----------------
// inquirer.prompt(questions, answers) -> promise 启动提示界面(查询会话)
// questions: array 包含Question 对象
// Question 对象包含以下属性:
// type: 提示的类型,包括:input, number, confirm, list, rawlist, expand, checkbox, password, editor
// name: 键名,用于获取用户输入的值
// message: 提示信息
// default: 默认值
// choices: 选项,包含:type为list, rawlist, expand, checkbox时的选项
// answers: 默认值(对象){}
// inquirer.prompt([
// {
// type: 'input',
// name: 'name',
// message: '请输入你的姓名'
// },
// {
// type: 'input',
// name: 'age',
// message: '请输入你的年龄'
// }]).then(answers => {
// console.log(answers);
// }).catch(err => {
// console.log(err);
// })
// //-----------------chalk的使用-----------------
// console.log(chalk.blue('Hello world!'));
// console.log(chalk.blue.bgRed.bold('Hello world!'));
// console.log(chalk.blue('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz'));
// console.log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!'));
// // ----------commander的使用----------------
// // 通过这个选项可以修改帮助信息的首行提示 例如:Usage: my-command [options] [command]
// program
// .name("zhihao-cli")
// .usage("<command> [options]")
// //选项:
// //Commander 使用.option()方法来定义选项,同时可以附加选项的简介。
// //每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(--后面接一个或多个单词),使用逗号、空格或|分隔。
// // 另一类选项则可以设置参数(使用尖括号声明在该选项后,如--expect <value>)。如果在命令行中不指定具体的选项及参数,
// //则会被定义为undefined。 <>表示必填参数,[]表示选填参数
// program
// .option('-d, --debug', 'output extra debugging')
// .option('-s, --small', 'small pizza size')
// .option('-p, --pizza-type <type>', 'flavour of pizza');
// // 命令行:
// //Commander 使用.command()方法来定义命令行,同时可以附加命令行的简介。
// // .command()的第一个参数为命令名称,参数可为必选的(尖括号表示)、
// //可选的(方括号表示)或变长参数(点号表示,如果使用,只能是最后一个参数)。第二个参数为命令简介
// program
// .command('clone <source> [destination]')
// .description('clone a repository into a newly created directory')
// .action((source, destination) => {
// console.log('clone command called');
// });
// // 解析命令行参数。解析这些参数并将它们关联到程序中的对应操作 process.argv 是 Node.js 运行时环境中的全局变量
// // 打印process.argv有2个参数,第一个是node的执行路径,第二个是当前执行脚本的路径,
// program.parse(process.argv);
// //它用于获取命令行中传递的所有参数和选项信息,并以一个对象的形式返回。
// // 这个对象中的键名就是用户传递的选项名或参数名,而键值则是用户传递的选项值或参数值
// // const options = program.opts();
// // console.log(options);