1 初始化命令
新建工具包初始化自定义命令执行
npm link
设置为全局使用
- 新建bin目录,创建 cli.js 文件
- 在 cli.js 文件内部设置
#! /usr/bin/env node
指令 - 终端切到当前工具包目录,执行
npm link
创建全局链接
2 commander使用
使用 commander 处理自定义帮助命令
- 执行
npm i commander -D
安装自定义命令 - 按语法设置可选 options 与 自定义命令
const { program } = require('commander')
// 新增自定义的可选属性
program.option('-f --framework <framework>', 'select your framework')
program.option('-d --dest <dest>', 'a destination folder')
// 使用 commander 格式化 argv
program.version(require('../package.json').version).parse(process.argv)
处理帮助信息
// 处理帮助信息
const examples = {
create: ['sli create|crt <project>'],
config: [
'sli config|cfg set <k> <v>',
'sli config|cfg get <k>'
]
}
program.on('--help', () => {
console.log('Examples: ')
Object.keys(examples).forEach(function (actionName) {
examples[actionName].forEach((item) => {
console.log(' ' + item)
})
})
})
自定义命令
program
.command('create <project> [others...]')
.alias('crt')
.description('创建新项目')
.action((name, args) => {
console.log(name + '执行了')
console.log(args)
})
抽离help函数
- 将 --help 需要做的事情抽离到单独的文件当中
- 将对应的函数导出供外部进行调用
抽离自定义命令
const {
createActions
} = require('./actions')
const myCommand = function (program) {
//? 01 创建项目命令
program
.command('create <project> [others...]')
.alias('crt')
.description('创建新项目')
.action(createActions)
//? 02 配置项目命令
program
.command('config <set|get> [others...]')
.alias('cfg')
.description('配置项目')
.action((name, args) => {
console.log(name + '执行了')
console.log(args)
})
}
module.exports = myCommand
3 其它工具使用
chalk 使用
const chalk = require('chalk')
//? 文字颜色
console.log(chalk.green('绿色'))
console.log(chalk.keyword('red')('前端开发'))
console.log(chalk.hex('#fff')('前端开发'))
//? 背景颜色
console.log(chalk.bgGray('带背景'))
//? 格式化输出
console.log(chalk.green.bold`
{red 从前慢}
没有前端开发
`)
/**
* 使用 chalk 包可以修改命令行终端字体的颜色
* + 提供的关键字设置颜色:green red orange 等等
* + 提供 keyword 方法接收键字
* + 提供 hex 方法接收16进制
* + 可以设置背景颜色
* + 格式化输出文字内容
*/
ora 使用
import ora from 'ora'
const spinner = ora('正在下载......').start()
spinner.color = 'green'
// spinner.text = 'Loading rainbows'
setTimeout(() => {
// spinner.succeed('下载成功')
// spinner.fail('下载失败')
spinner.info('下载内容')
}, 2000)
/**
* 当前版本只支持 es6Module,可以将 package.json 文件中添加 type:module 字段
* ora 实例化对象然后调用 start
* color设置文字颜色
* text 设置文字内容
*
* succeed 成功回调
* fail 失败回调
* info
* ......
*/
inquirer 使用
基本使用
const inquirer = require('inquirer')
//? 定义问题
let quesList = [
{
type: 'input',
name: 'username',
message: '用户名',
validate(an) {
if (!an) {
return '当前为必填项'
} else {
return true
}
}
}
]
//? 获取结果
inquirer.prompt(quesList).then((an) => {
console.log(an.username)
})
/**
* 基础字段
* + type 定义问题类型
* + name 将来问题的答案会被保存在一个对象当中,这里定义的值就是它的键名
* + message 设置问题的提示信息
* + default 设置默认值
* + validate 函数,接收参数为当前问题的答案,可以添加判断的条件来决定后续走向
*/
递进问题
const inquirer = require('inquirer')
const quesList = [
{
type: 'confirm',
name: 'isLoad',
message: '是否执行下载'
},
{
type: 'list',
name: 'method',
message: '选择下载方式',
choices: ['npm', 'cnpm', 'yarn'],
when(preAn) {
return preAn.isLoad
}
}
]
inquirer.prompt(quesList).then((an) => {
console.log(an)
})
/**
* confirm 询问型问题,返回 true 或者 false
* choices 接收一个数组,给问题提供选项
* when 接收一个参数是一个问题的答案,可用于判断当前问题是否显示
*/
总结
const inquirer = require('inquirer')
// 准备问题
const quesList = [
{
type: 'checkbox',
name: 'feature',
message: '选择基础功能',
pageSize: 2,
choices: ['webpack', 'webpack-cli', 'eslint', 'jest', 'zoe', 'vueRouter', 'React']
}
]
// 处理问题
inquirer.prompt(quesList).then((an) => {
console.log(an.feature)
})
/**
* 问题属性:
* + type:input list confirm checkbox
* + name: 用于做为答案的链出现
* + message: 用于问题的提示信息
* + choices: 出现选项时,设置为列表选项
* + pageSize: 设置每页显示的问题数量
* 常见方法:
* + validate 校验
* + when 判断
*/
4 功能实现
资料
获取组织仓库列表信息
https://api.github.com/orgs/lagoufed/repos
获取个人仓库列表信息
https://api.github.com/users/zcegg/repos
获取指定仓库版本号
https://api.github.com/repos/zcegg/create-nm/tags
查询访问次数
curl -i https://api.github.com/users/octocat
headers={"Authorization":"token "+"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"}
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
步骤分析
已完成:命令、交互工具、业务参数
未完成:
查询远端模板列表(添加交互)
查询选中模板下是否存在多个版本
下载指定模板指定版本
将模板缓存在指定位置
渲染数据写入到指定的目录
请求次数限制
//! 发送请求
let ret = await axios('https://api.github.com/users/zcegg/repos')
console.log(ret.data)
解决次数限制
//! 发送请求
const headers = { "Authorization": "token " + "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" }
let { data } = await axios({
method: 'get',
url: 'https://api.github.com/users/zcegg/repos',
headers: headers
})
const repos = data.map(item => item.name)
console.log(repos)
查询模板信息
const createActions = async function (project) {
//? 定义请求头信息
const headers = { "Authorization": "token " + "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" }
var { data } = await axios({
method: 'get',
url: 'https://api.github.com/users/zcegg/repos',
headers: headers
})
const repos = data.map(item => item.name)
//? 01准备问题
const quesList = [
{
type: 'list',
name: 'tmpRepo',
message: '选择目标模板',
choices: repos
}
]
//? 02 处理问题
const { tmpRepo } = await inquirer.prompt(quesList)
//? 03 查询选中模板信息
var { data } = await axios({
method: 'get',
url: 'https://api.github.com/repos/zcegg/create-nm/tags',
headers: headers
})
const tags = data.map(item => item.name)
console.log(tags, '<----')
}
提取查询方法
const fetchInfo = async function (repoName, tmpName) {
//? 定义token
const token = "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"
const url1 = `https://api.github.com/users/${repoName}/repos`
const url2 = `https://api.github.com/repos/${repoName}/${tmpName}/tags`
const headers = { "Authorization": "token " + token }
const url = !tmpName ? url1 : url2
let { data } = await axios({
method: 'get',
url: url,
headers: headers
})
return data.map(item => item.name)
}
添加耗时及柯理化
//! 工具方法之添加耗时
const addLoading = function (fn) {
return async function (...args) {
const spinner = ora('正在查询').start()
const ret = await fn(...args)
spinner.succeed('查询成功')
return ret
}
}
// const repos = await addLoading(fetchInfo)('zcegg')
版本逻辑处理
let loadUrl = null
if (tags.length) {
// 处理版本
const quesTag = [
{
type: 'list',
name: 'tmpTag',
message: '选择指定版本',
choices: tags
}
]
const { tmpTag } = await inquirer.prompt(quesTag)
} else {
console.log('直接执行下载')
}
处理缓存路径
const toUnixPath = require('../utils/toUnixPath')
// console.log(process.env) // 查询环境变量
// console.log(process.platform) // 查询当平台关键字
console.log(process.env['USERPROFILE'])
console.log(toUnixPath(`${process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']}` + '/.tmp'))
初始化下载函数
//! 工具方法之下载操作
const downLoadRepo = function (repo, tag) {
//? 定义缓存目录
const cacheDir = toUnixPath(`${process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']}` + '/.tmp')
//? 处理参数
let api = `zcegg/${repo}`
if (tag) api += `#/${tag}`
console.log(cacheDir)
console.log(api)
}
实现下载操作
//* 导出下载函数
let downloadFn = require('download-git-repo')
downloadFn = promisify(downloadFn)
//? 定义缓存路径
const cacheDir = toUnixPath(`${process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']}` + '/.tmp')
//? 处理参数
let api = `zcegg/${repo}`
if (tag) api += `#/${tag}`
//? 定义缓存目录
const dest = tag ? path.resolve(cacheDir, repo, tag) : path.resolve(cacheDir, repo)
//? 执行下载操作
const spinner = ora('正在下载......').start()
await downloadFn(api, dest)
spinner.succeed('下载成功')
使用缓存
//? 执行下载操作(判断缓存中是否存)
if (!fs.existsSync(dest)) {
//* 缓存目录中不存在则直接下载
const spinner = ora('正在下载......').start()
await downloadFn(api, dest)
spinner.succeed('下载成功')
}
//? 返回地址用于数据读取
return dest
无数据渲染
//? 05 下载完成之后判断是否需要渲染数据,从而生成本地的项目
// 如果需要渲染则在 package.json 中使用 ejs 语法,同时提前通过 qus.js 来准备问题
if (fs.existsSync(path.join(dest, 'que.js'))) {
console.log('需要渲染')
} else {
// 不需要渲染就直接拷贝
ncp(dest, project)
}
metalsmith使用
//? 05 下载完成之后判断是否需要渲染数据,从而生成本地的项目
// 如果需要渲染则在 package.json 中使用 ejs 语法,同时提前通过 qus.js 来准备问题
if (fs.existsSync(path.join(dest, 'que.js'))) {
//! 需要数据渲染
await new Promise((resolve, reject) => {
MetalSmith(__dirname)
.source(dest)
.destination(path.resolve(project))
.use((files, metal, done) => {
//* files 是当前目录下所有的文件信息
//*
console.log(files)
done()
})
.build((err) => {
if (err) {
reject()
} else {
resolve()
}
})
})
} else {
// 不需要渲染就直接拷贝
ncp(dest, project)
}
设置问题
if (fs.existsSync(path.join(dest, 'que.js'))) {
//! 需要数据渲染
await new Promise((resolve, reject) => {
MetalSmith(__dirname)
.source(dest)
.destination(path.resolve(project))
.use(async (files, metal, done) => {
const quesList = require(path.join(dest, 'que.js'))
const answer = await inquirer.prompt(quesList)
console.log(answer)
done()
})
.build((err) => {
if (err) {
reject()
} else {
resolve()
}
})
})
} else {
// 不需要渲染就直接拷贝
ncp(dest, project)
}
问题数据传递
if (fs.existsSync(path.join(dest, 'que.js'))) {
//! 需要数据渲染
await new Promise((resolve, reject) => {
MetalSmith(__dirname)
.source(dest)
.destination(path.resolve(project))
.use(async (files, metal, done) => {
const quesList = require(path.join(dest, 'que.js'))
const answer = await inquirer.prompt(quesList)
//! 当 answer 的答案我们需要在下一个 use 中进行使用
//! 利用 metal.metadata() 来保存所有的数据,交给下一个 use 进行使用即可
let meta = metal.metadata()
Object.assign(meta, answer)
// 这步操作完成之后,que.js 文件就没有用了,不需要拷贝至项目的目录
// delete files['que.js']
done()
})
.use((files, metal, done) => {
// 获取上一个 use 中拿到的用户数据
let data = metal.metadata()
console.log(data)
done()
})
.build((err) => {
if (err) {
reject()
} else {
resolve()
}
})
})
} else {
// 不需要渲染就直接拷贝
ncp(dest, project)
}
数据渲染
.use((files, metal, done) => {
// 获取上一个 use 中拿到的用户数据
let data = metal.metadata()
//? 找到那些需要渲染数据的具体文件,找到之后将它们的内容转为字符串
//? 转为字符串之后,接下来就可以针对于字符串进行替换实现渲染
Reflect.ownKeys(files).forEach(async (file) => {
if (file.includes('js') || file.includes('json')) {
let content = files[file].contents.toString()
if (content.includes("<%")) {
content = await render(content, data)
files[file].contents = Buffer.from(content)
}
}
})
done()
执行 npm
const { spawn } = require('child_process')
// 执行 npm install
const commandSpawn = (...args) => {
return new Promise((resolve, reject) => {
const childProcess = spawn(...args)
childProcess.stdout.pipe(process.stdout)
childProcess.stdout.pipe(process.stderr)
childProcess.on('close', () => {
resolve()
})
})
}
module.exports = {
commandSpawn
}
//? 06 执行 npm install
const run_command = process.platform === 'win32' ? 'npm.cmd' : 'npm'
await commandSpawn(run_command, ['install'], { cwd: `./${project}` })
//?07 执行 run serve
commandSpawn(run_command, ['run', 'serve'], { cwd: `./${project}` })