以下均在node版本为14.18.1的环境下运行的
cli.js
#! /usr/bin/env node
// #! 符号的名称叫 Shebang,用于指定脚本的解释程序
// Node CLI 应用入口文件必须要有这样的文件头
// 如果是Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改
// 用于检查入口文件是否正常执行
console.log('my-node-cli working~/项目运行成功')
package.json
{
"name": "my-node-cli",
"version": "1.0.0",
"description": "",
"main": "cli.js",
"bin": "cli.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
npm link详情解释
执行软链
npm link
执行具体如下图,会自动创建
一个package-lock.json
,并且会把my-node-cli
通过软链接的方式放入到node
的安装目录
下的node_modules
文件夹中(其实就是一个快捷方式映射),这也是后面为什么我们能够直接
使用my-node-cli指令
的原因。
此时的目录图如下:
我们再看下本地的node文件夹和node_modules文件夹
点击上面的my-node-cli,发现直接显示了my-node-cli项目所有文件。
命令行中输入 my-node-cli 执行一下(直接执行了bin指令对应的文件)
my-node-cli
为了更好的理解项目的配置,以下对一个通用的package.json文件的配置项进行逐一解释
一些包的作者希望包
可以作为命令行
工具使用,配置好 bin 字段之后,通过 npm install package_name -g
(package_name对应这里的是my-node-cli) 命令可以将脚本
添加到执行路径
中,之后可以在命令行中直接执行。
这样就可以直接
在命令行
中使用 bin
字段里注册
的命令
,而不需要加任何其余字段。
指定的是项目的入口文件
引入 inquirer.js,实现与询问用户信息的功能
$ npm install inquirer --dev
接着设置我们在 cli.js
需要询问的问题
#! /usr/bin/env node
const inquirer = require('inquirer')
inquirer.prompt([
{
type: 'input', //type: input, number, confirm, list, checkbox等操作类型
name: 'name', // key 名
message: '请在后面输入内容,设置你的项目名称', // 提示信息
default: 'my-node-cli' // 默认值,如未输入则采用默认值
}
]).then(answers => {
// 打印互用输入结果
console.log(answers)
})
!!!注意这里再次运行my-node-cli
,可能会报错:internal/modules/cjs/loader.js:1102 throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
这是因为在,
inquirer
在9.0.0
版本开始,模块化
方式改为native esm modules
,如果你的项目中使用的是CMD
模块化方式,则需要限制inquirer
的版本低于9.0.0
,否则将会抛出以上错误。
ok,那我们就删除node_module文件夹,重新安装指定版本
npm install inquirer@8.0.0 --dev
再次执行my-node-cli
,输入项目名称my-app
:
mkdir templates # 创建模版文件夹
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
<!-- ejs 语法 -->
<%= name %>
</title>
</head>
<body>
<h1><%= name %></h1>
</body>
</html>
index.css
/* common.css */
body {
margin: 20px auto;
background-color: azure;
}
这里借助 ejs 模版引擎将用户输入的数据渲染到模版文件上
npm install ejs --save
修改cli.js文件:
#! /usr/bin/env node
const inquirer = require('inquirer')
const path = require('path')
const fs = require('fs')
const ejs = require('ejs')
inquirer.prompt([
{
type: 'input', //type:input,confirm,list,rawlist,checkbox,password...
name: 'name', // key 名
message: 'Your name', // 提示信息
default: 'my-node-cli' // 默认值
}
]).then(answers => {
// 模版文件目录
const destUrl = path.join(__dirname, 'templates');
// 生成文件目录
// process.cwd() 对应控制台所在目录
const cwdUrl = process.cwd();
// 从模版目录中读取文件
fs.readdir(destUrl, (err, files) => {
if (err) throw err;
files.forEach((file) => {
// 使用 ejs 渲染对应的模版文件
// renderFile(模版文件地址,传入渲染数据)
console.log(answers,'answers===')
ejs.renderFile(path.join(destUrl, file), answers).then(data => {
// 生成 ejs 处理后的模版文件
fs.writeFileSync(path.join(cwdUrl, file) , data)
})
})
})
})
package.json
{
"name": "lw-cli",
"version": "1.0.0",
"description": "",
"bin": "./bin/cli.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "T-Roc",
"license": "ISC",
"devDependencies": {
"commander": "^7.2.0"
}
}
引入 commander 依赖
npm install commander
bin/cli.js
#! /usr/bin/env node
const program = require('commander')
program
.version('0.1.0')
.command('create ' )
.description('create a new project')
.action(name => {
// 打印命令行输入的值
console.log("project name is " + name)
})
program.parse()
然后执行
npm link
将应用lw-cli
链接到全局:
完成之后,在命令行中执行lw-cli
结果如下:
这个时候就有了 lw-cli 命令使用的说明信息,在 Commands
下面出现了我们刚刚创建的 create
命令 create
,我们在命令行中运行一下:
lw-cli create firstApp
chalk(粉笔)可以美化我们在命令行中输出内容的样式,例如对重点信息添加颜色
// 不指定版本可能会报错require() of ES modules is not supported.
// 当前环境node版本为:14.18.1
npm install chalk@3.0.0
修改bin/cli.js为:
#! /usr/bin/env node
const program = require('commander')
const chalk = require('chalk')
program
.version('0.1.0')
.command('create ' )
.description('create a new project')
.action(name => {
// 打印命令行输入的值
// 文本样式
console.log("project name is " + chalk.bold(name))
// 颜色
console.log("project name is " + chalk.cyan(name))
console.log("project name is " + chalk.green(name))
// 背景色
console.log("project name is " + chalk.bgRed(name))
// 使用RGB颜色输出
console.log("project name is " + chalk.rgb(4, 156, 219).underline(name));
console.log("project name is " + chalk.hex('#049CDB').bold(name));
console.log("project name is " + chalk.bgHex('#049CDB').bold(name))
})
program.parse()
在命令行中运行项目 lw-cli create my-app 看一下效果
// 不指定版本可能会报错require() of ES modules is not supported.
// 当前环境node版本为:14.18.1
npm install ora@4.0.0
修改bin/cli.js为:
#! /usr/bin/env node
const ora = require('ora')
// 自定义文本信息
const message = 'Loading unicorns'
// 初始化
const spinner = ora(message);
// 开始加载动画
spinner.start();
setTimeout(() => {
// 修改动画样式
// Type: string
// Default: 'cyan'
// Values: 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray'
spinner.color = 'red';
spinner.text = 'Loading rainbows';
setTimeout(() => {
// 加载状态修改
spinner.stop() // 停止
spinner.succeed('Loading succeed'); // 成功 ✔
// spinner.fail(text?); 失败 ✖
// spinner.warn(text?); 提示 ⚠
// spinner.info(text?); 信息 ℹ
}, 2000);
}, 2000);
在命令行中运行项目 lw-cli create my-app 看一下效果
// 当前环境node版本为:14.18.1
npm install cross-spawn
修改bin/cli.js为:
#! /usr/bin/env node
const spawn = require('cross-spawn');
const chalk = require('chalk')
// 定义需要安装的依赖
const dependencies = ['vue', 'vuex', 'vue-router'];
// 执行安装
const child = spawn('npm', ['install', '-D'].concat(dependencies), {
stdio: 'inherit'
});
// 监听执行结果
child.on('close', function(code) {
// 执行失败
if(code !== 0) {
console.log(chalk.red('Error occurred while installing dependencies!'));
process.exit(1);
}
// 执行成功
else {
console.log(chalk.cyan('Install finished'))
}
})
在命令行中运行w-cli
看一下效果
同时可以看到执行了安装依赖'vue', 'vuex', 'vue-router'
根据目录结构,重新创建相应文件
{
"name": "zhurong-cli",
"version": "1.0.0",
"description": "simple vue cli",
"main": "index.js",
"bin": {
"zr": "./bin/cli.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": {
"name": "T-Roc",
"email": "[email protected]"
},
"license": "MIT"
}
cli.js
#! /usr/bin/env node
console.log('zhurong-cli working ~')
npm install commander --save
修改cli.js
为:
#! /usr/bin/env node
const program = require('commander')
program
// 定义命令和参数
.command('create ' )
.description('create a new project')
// -f or --force 为强制创建,如果创建的目录存在则直接覆盖
.option('-f, --force', 'overwrite target directory if it exist')
.action((name, options) => {
// 打印执行结果
console.log('name:',name,'options:',options)
})
program
// 配置版本号信息
.version(`v${require('../package.json').version}`)
.usage(' [option]' )
// 解析用户执行命令传入参数
program.parse(process.argv);
在命令行输入 zr
,检查一下命令是否创建成功
我们可以看到 Commands
下面已经有了 create [options]
,接着执行一下这个命令
依次拿到了输入信息,打印了name
和options
创建 lib
文件夹并在文件夹下创建 create.js
// lib/create.js
module.exports = async function (name, options) {
// 验证是否正常取到值
console.log('>>> create.js', name, options)
}
bin/cli.js
#! /usr/bin/env node
const program = require('commander')
program
.command('create ' )
.description('create a new project')
.option('-f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
.action((name, options) => {
// 在 create.js 中执行创建任务,require引入的是一个自执行函数,并传入了两个参数
require('../lib/create.js')(name, options)
})
program
// 配置版本号信息
.version(`v${require('../package.json').version}`)
.usage(' [option]' )
// 解析用户执行命令传入参数
program.parse(process.argv);
//fs-extra 是对 fs 模块的扩展,支持 promise
npm install fs-extra --save
修改create.js
为:
// lib/create.js
const path = require('path')
const fs = require('fs-extra')
module.exports = async function (name, options) {
// 执行创建命令
// 当前命令行选择的目录
const cwd = process.cwd();
// 需要创建的目录地址
const targetAir = path.join(cwd, name)
console.log('需要创建的目录地址===',targetAir)
// 目录是否已经存在?
if (fs.existsSync(targetAir)) {
// 是否为强制创建?
if (options.force) {
await fs.remove(targetAir)
} else {
// TODO:询问用户是否确定要覆盖
}
}
}
执行指令zr create my-project --force
如下:
安装chalk 依赖
// 当前环境node版本为:14.18.1
npm install chalk@3.0.0
修改cli.js为:
#! /usr/bin/env node
const program = require('commander')
const chalk = require('chalk')
program
.command('create ' )
.description('create a new project')
.option('-f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
.action((name, options) => {
// 在 create.js 中执行创建任务
require('../lib/create.js')(name, options)
})
program
// 监听 --help 执行
.on('--help', () => {
// 新增说明信息
console.log(`\r\nRun ${chalk.cyan(`zr --help `)} for detailed usage of given command\r\n`)
})
program
// 配置版本号信息
.version(`v${require('../package.json').version}`)
.usage(' [option]' )
// 解析用户执行命令传入参数
program.parse(process.argv);
安装依赖
npm install figlet
修改cli.js为:
#! /usr/bin/env node
const program = require('commander')
const chalk = require('chalk')
const figlet = require('figlet')
program
.command('create ' )
.description('create a new project')
.option('-f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
.action((name, options) => {
// 在 create.js 中执行创建任务
require('../lib/create.js')(name, options)
})
// bin/cli.js
#! /usr/bin/env node
program
.on('--help', () => {
// 使用 figlet 绘制 Logo
console.log('\r\n' + figlet.textSync('lw', {
font: 'Ghost',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 80,
whitespaceBreak: true
}));
// 新增说明信息
console.log(`\r\nRun ${chalk.cyan(`roc --help `)} show details\r\n`)
})
program
// 配置版本号信息
.version(`v${require('../package.json').version}`)
.usage(' [option]' )
// 解析用户执行命令传入参数
program.parse(process.argv);
输入zr --help
,输入如下,可以看到配置的logo出现了:
安装指定版本依赖
npm install inquirer@8.0.0 --dev
然后询问用户是否进行 Overwrite
cli.js
#! /usr/bin/env node
const program = require('commander')
const chalk = require('chalk')
const figlet = require('figlet')
program
.command('create ' )
.description('create a new project')
.option('--f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
.action((name, options) => {
// 在 create.js 中执行创建任务
require('../lib/create.js')(name, options)
})
// bin/cli.js
#! /usr/bin/env node
program
.on('--help', () => {
// 使用 figlet 绘制 Logo
console.log('\r\n' + figlet.textSync('lw', {
font: 'Ghost',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 80,
whitespaceBreak: true
}));
// 新增说明信息
console.log(`\r\nRun ${chalk.cyan(`roc --help `)} show details\r\n`)
})
program
// 配置版本号信息
.version(`v${require('../package.json').version}`)
.usage(' [option]' )
// 解析用户执行命令传入参数
program.parse(process.argv);
create.js
// lib/create.js
const path = require('path')
// fs-extra 是对 fs 模块的扩展,支持 promise 语法
const fs = require('fs-extra')
const inquirer = require('inquirer')
module.exports = async function (name, options) {
// 执行创建命令
// 当前命令行选择的目录
const cwd = process.cwd();
// 需要创建的目录地址
const targetAir = path.join(cwd, name)
console.log(targetAir,'打印===targetAir')
// 目录是否已经存在?
if (fs.existsSync(targetAir)) {
// 是否为强制创建?
if (options.force) {
await fs.remove(targetAir)
} else {
// 询问用户是否确定要覆盖
let { action } = await inquirer.prompt([
{
name: 'action',
type: 'list',
message: 'Target directory already exists Pick an action:',
choices: [
{
name: 'Overwrite',
value: 'overwrite'
},{
name: 'Cancel',
value: false
}
]
}
])
if (!action) {
return;
} else if (action === 'overwrite') {
// 移除已存在的目录
console.log(`\r\nRemoving...`)
await fs.remove(targetAir)
}
}
}
}
我们来测试一下:
在当前目录,即命令行中显示的目录下手动创建2个目录,这里随便取名为 my-project
和 my-project2
OverWrite
则,my-project
目录会被删除Cancel
则,my-project
目录会保留移除后,再次执行zr create my-project
会发现my-project
并没有创建,这是因为还没有写创建的逻辑。
执行 zr create my-project2 --f
,可以直接看到 my-project2 被移除
⚠️注意:为什么这里只做移除? 因为后面获取到模板地址后,下载的时候会直接创建项目目录
更改package.json为:
{
"name": "lw-cli",
"version": "1.0.0",
"description": "simple vue cli",
"main": "index.js",
"bin": {
"zr": "./bin/cli.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": {
"name": "T-Roc",
"email": "[email protected]"
},
"license": "MIT",
"dependencies": {
"axios": "^1.3.4",
"chalk": "^3.0.0",
"commander": "^10.0.0",
"figlet": "^1.5.2",
"fs-extra": "^11.1.0",
"inquirer": "^8.0.0",
"ora": "^4.0.0"
}
}
bin/cli.js
#! /usr/bin/env node
const program = require('commander')
const chalk = require('chalk')
const figlet = require('figlet')
program
.command('create ' )
.description('create a new project')
.option('-f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
.action((name, options) => {
// 在 create.js 中执行创建任务
require('../lib/create.js')(name, options)
})
program
.on('--help', () => {
// 使用 figlet 绘制 Logo
console.log('\r\n' + figlet.textSync('lw', {
font: 'Ghost',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 80,
whitespaceBreak: true
}));
// 新增说明信息
console.log(`\r\nRun ${chalk.cyan(`roc --help `)} show details\r\n`)
})
program
// 配置版本号信息
.version(`v${require('../package.json').version}`)
.usage(' [option]' )
// 解析用户执行命令传入参数
program.parse(process.argv);
lib/create.js
// lib/create.js
const path = require('path')
// fs-extra 是对 fs 模块的扩展,支持 promise 语法
const fs = require('fs-extra')
const inquirer = require('inquirer')
const Generator = require('./Generator')
module.exports = async function (name, options) {
// 执行创建命令
// 当前命令行选择的目录
const cwd = process.cwd();
// 需要创建的目录地址
const targetAir = path.join(cwd, name)
// 目录是否已经存在?
if (fs.existsSync(targetAir)) {
// 是否为强制创建?
if (options.force) {
await fs.remove(targetAir)
} else {
// 询问用户是否确定要覆盖
let { action } = await inquirer.prompt([
{
name: 'action',
type: 'list',
message: 'Target directory already exists Pick an action:',
choices: [
{
name: 'Overwrite',
value: 'overwrite'
}, {
name: 'Cancel',
value: false
}
]
}
])
if (!action) {
return;
} else if (action === 'overwrite') {
// 移除已存在的目录
await fs.remove(targetAir)
}
}
}
// 创建项目
const generator = new Generator(name, targetAir);
// 开始创建项目
generator.create()
}
lib/Generator.js
// lib/Generator.js
const { getRepoList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
// 添加加载动画
async function wrapLoading(fn, message, ...args) {
// 使用 ora 初始化,传入提示信息 message
const spinner = ora(message);
// 开始加载动画
spinner.start();
try {
// 执行传入方法 fn
const result = await fn(...args);
// 状态为修改为成功
spinner.succeed();
return result;
} catch (error) {
// 状态为修改为失败
spinner.fail('Request failed, refetch ...')
}
}
class Generator {
constructor(name, targetDir) {
// 目录名称
this.name = name;
// 创建位置
this.targetDir = targetDir;
}
// 获取用户选择的模板
// 1)从远程拉取模板数据
// 2)用户选择自己新下载的模板名称
// 3)return 用户选择的名称
async getRepo() {
// 1)从远程拉取模板数据
const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
if (!repoList) return;
// 过滤我们需要的模板名称
const repos = repoList.map(item => item.name);
// 2)用户选择自己新下载的模板名称
const { repo } = await inquirer.prompt({
name: 'repo',
type: 'list',
choices: repos,
message: 'Please choose a template to create project'
})
// 3)return 用户选择的名称
return repo;
}
// 核心创建逻辑
// 1)获取模板名称
// 2)获取 tag 名称
// 3)下载模板到模板目录
async create() {
// 1)获取模板名称
const repo = await this.getRepo()
console.log('用户选择了,repo=' + repo)
}
}
module.exports = Generator;
lib/http.js
// lib/http.js
// 通过 axios 处理请求
const axios = require('axios')
axios.interceptors.response.use(res => {
return res.data;
})
/**
* 获取模板列表
* @returns Promise
*/
async function getRepoList() {
return axios.get('https://api.github.com/orgs/zhurong-cli/repos')
}
/**
* 获取版本信息
* @param {string} repo 模板名称
* @returns Promise
*/
async function getTagList(repo) {
return axios.get(`https://api.github.com/repos/zhurong-cli/${repo}/tags`)
}
module.exports = {
getRepoList,
getTagList
}
运行zr create my-project
出现选择模板选项
选择一个模板后,显示:
修改lib/Generator.js为:
// lib/Generator.js
const { getRepoList, getTagList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
// 添加加载动画
async function wrapLoading(fn, message, ...args) {
// 使用 ora 初始化,传入提示信息 message
const spinner = ora(message);
// 开始加载动画
spinner.start();
try {
// 执行传入方法 fn
const result = await fn(...args);
// 状态为修改为成功
spinner.succeed();
return result;
} catch (error) {
// 状态为修改为失败
spinner.fail('Request failed, refetch ...')
}
}
class Generator {
constructor(name, targetDir) {
// 目录名称
this.name = name;
// 创建位置
this.targetDir = targetDir;
}
// 获取用户选择的模板
// 1)从远程拉取模板数据
// 2)用户选择自己新下载的模板名称
// 3)return 用户选择的名称
async getRepo() {
// 1)从远程拉取模板数据
const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
if (!repoList) return;
// 过滤我们需要的模板名称
const repos = repoList.map(item => item.name);
// 2)用户选择自己新下载的模板名称
const { repo } = await inquirer.prompt({
name: 'repo',
type: 'list',
choices: repos,
message: 'Please choose a template to create project'
})
// 3)return 用户选择的名称
return repo;
}
// 获取用户选择的版本
// 1)基于 repo 结果,远程拉取对应的 tag 列表
// 2)用户选择自己需要下载的 tag
// 3)return 用户选择的 tag
async getTag(repo) {
// 1)基于 repo 结果,远程拉取对应的 tag 列表
const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);
if (!tags) return;
// 过滤我们需要的 tag 名称
const tagsList = tags.map(item => item.name);
// 2)用户选择自己需要下载的 tag
const { tag } = await inquirer.prompt({
name: 'tag',
type: 'list',
choices: tagsList,
message: 'Place choose a tag to create project'
})
// 3)return 用户选择的 tag
return tag
}
// 核心创建逻辑
// 1)获取模板名称
// 2)获取 tag 名称
// 3)下载模板到模板目录
async create() {
// 1)获取模板名称
const repo = await this.getRepo()
// 2) 获取 tag 名称
const tag = await this.getTag(repo)
console.log('用户选择了,repo=' + repo + ',tag=' + tag)
}
}
module.exports = Generator;
运行zr create my-project
为:
选择模板后:
下载远程模版需要使用
download-git-repo
工具包,需要注意一个问题,它不支持promise
的,所以我们这里需要使用util
模块中的promisify
方法对其进行promise
化
npm install download-git-repo --save
进行 promise 化处理,lib/Generator.js
// lib/Generator.js
const { getRepoList, getTagList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
const util = require('util')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise
// 添加加载动画
async function wrapLoading(fn, message, ...args) {
// 使用 ora 初始化,传入提示信息 message
const spinner = ora(message);
// 开始加载动画
spinner.start();
try {
// 执行传入方法 fn
const result = await fn(...args);
// 状态为修改为成功
spinner.succeed();
return result;
} catch (error) {
// 状态为修改为失败
spinner.fail('Request failed, refetch ...')
}
}
class Generator {
constructor(name, targetDir) {
// 目录名称
this.name = name;
// 创建位置
this.targetDir = targetDir;
// 对 download-git-repo 进行 promise 化改造
this.downloadGitRepo = util.promisify(downloadGitRepo);
}
// 获取用户选择的模板
// 1)从远程拉取模板数据
// 2)用户选择自己新下载的模板名称
// 3)return 用户选择的名称
async getRepo() {
// 1)从远程拉取模板数据
const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
if (!repoList) return;
// 过滤我们需要的模板名称
const repos = repoList.map(item => item.name);
// 2)用户选择自己新下载的模板名称
const { repo } = await inquirer.prompt({
name: 'repo',
type: 'list',
choices: repos,
message: 'Please choose a template to create project'
})
// 3)return 用户选择的名称
return repo;
}
// 获取用户选择的版本
// 1)基于 repo 结果,远程拉取对应的 tag 列表
// 2)用户选择自己需要下载的 tag
// 3)return 用户选择的 tag
async getTag(repo) {
// 1)基于 repo 结果,远程拉取对应的 tag 列表
const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);
if (!tags) return;
// 过滤我们需要的 tag 名称
const tagsList = tags.map(item => item.name);
// 2)用户选择自己需要下载的 tag
const { tag } = await inquirer.prompt({
name: 'tag',
type: 'list',
choices: tagsList,
message: 'Place choose a tag to create project'
})
// 3)return 用户选择的 tag
return tag
}
// 核心创建逻辑
// 1)获取模板名称
// 2)获取 tag 名称
// 3)下载模板到模板目录
async create() {
// 1)获取模板名称
const repo = await this.getRepo()
// 2) 获取 tag 名称
const tag = await this.getTag(repo)
console.log('用户选择了,repo=' + repo + ',tag=' + tag)
}
}
module.exports = Generator;
// lib/Generator.js
// lib/Generator.js
const { getRepoList, getTagList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
const util = require('util')
const path = require('path')
const chalk = require('chalk')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise
// 添加加载动画
async function wrapLoading(fn, message, ...args) {
// 使用 ora 初始化,传入提示信息 message
const spinner = ora(message);
// 开始加载动画
spinner.start();
try {
// 执行传入方法 fn
const result = await fn(...args);
// 状态为修改为成功
spinner.succeed();
return result;
} catch (error) {
// 状态为修改为失败
spinner.fail('Request failed, refetch ...')
}
}
class Generator {
constructor(name, targetDir) {
// 目录名称
this.name = name;
// 创建位置
this.targetDir = targetDir;
// 对 download-git-repo 进行 promise 化改造
this.downloadGitRepo = util.promisify(downloadGitRepo);
}
// 下载远程模板
// 1)拼接下载地址
// 2)调用下载方法
async download(repo, tag) {
// 1)拼接下载地址
const requestUrl = `zhurong-cli/${repo}${tag ? '#' + tag : ''}`;
console.log(requestUrl, '====下载地址为')
// 2)调用下载方法
await wrapLoading(
this.downloadGitRepo, // 远程下载方法
'waiting download template', // 加载提示信息
requestUrl, // 参数1: 下载地址
path.resolve(process.cwd(), this.targetDir)) // 参数2: 创建位置
}
// 获取用户选择的模板
// 1)从远程拉取模板数据
// 2)用户选择自己新下载的模板名称
// 3)return 用户选择的名称
async getRepo() {
// 1)从远程拉取模板数据
const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
if (!repoList) return;
// 过滤我们需要的模板名称
const repos = repoList.map(item => item.name);
// 2)用户选择自己新下载的模板名称
const { repo } = await inquirer.prompt({
name: 'repo',
type: 'list',
choices: repos,
message: 'Please choose a template to create project'
})
// 3)return 用户选择的名称
return repo;
}
// 获取用户选择的版本
// 1)基于 repo 结果,远程拉取对应的 tag 列表
// 2)用户选择自己需要下载的 tag
// 3)return 用户选择的 tag
async getTag(repo) {
// 1)基于 repo 结果,远程拉取对应的 tag 列表
const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);
if (!tags) return;
// 过滤我们需要的 tag 名称
const tagsList = tags.map(item => item.name);
// 2)用户选择自己需要下载的 tag
const { tag } = await inquirer.prompt({
name: 'tag',
type: 'list',
choices: tagsList,
message: 'Place choose a tag to create project'
})
// 3)return 用户选择的 tag
return tag
}
// 核心创建逻辑
// 1)获取模板名称
// 2)获取 tag 名称
// 3)下载模板到模板目录
async create() {
// 1)获取模板名称
const repo = await this.getRepo()
// 2) 获取 tag 名称
const tag = await this.getTag(repo)
console.log('用户选择了,repo=' + repo + ',tag=' + tag)
// 3)下载模板到模板目录
await this.download(repo, tag)
// 4)模板使用提示
console.log(`\r\nSuccessfully created project ${chalk.cyan(this.name)}`)
console.log(`\r\n cd ${chalk.cyan(this.name)}`)
console.log(' npm run dev\r\n')
}
}
module.exports = Generator;
这里可以参考我之前写的文章注册发布自己的npm包
上传前将package.json文件的name改称xyjlw-cli
,并把bin里面的zr
改成lw
参靠之前的文章,进行发布
发布成功后,接着将原来项目的package.json
文件的name
改称lw-cli
,bin里面的xyjlw-cli
改成zr
不然还会出现name冲突。
执行npm install -g xyjlw-cli
,全局安装
接着可以在本地文件夹看到下载的脚手架xyjlw-cli
了,