效果
上代码
#! /usr/bin/env node
const {program} = require('commander')
const {version} = require('../package.json')
// cnpm i commander -D
/**
* 当前我们需要做的就是将多个自定义命令的信息用一种数据格式保存起来
* 因为这些个命令的信息将来肯定都需要被使用到,因此这个存储的格式应该是可迭代
* 这里我们就选择对象
*/
// 存储自定义的命令信息
const actionsMap = {
'create' : {
alias: 'crt',
des: '创建本地项目',
examples: ['lgg create <projectname>']
},
'config' : {
alias: 'cfg',
des: '修改配置信息',
examples: [
'lgg config set <k> <v>',
'lgg config get <k>',
]
},
}
// 将上述的信息渲染至命令行终端里
Reflect.ownKeys(actionsMap).forEach((aName) => {
// 当前我们可以拿到 自定义的命令名称了,但是我们最终想要做的事情是利用这个键名
// 找到它所对应的 命令信息,然后再使用 program 操作,将它们渲染在终端里
program
.command(aName)
.alias(actionsMap[aName].alias)
.description(actionsMap[aName].des)
.action(() => {
console.log(aName, '执行了')
})
})
program.on('--help', () => {
console.log('Examples: ')
// 此时我们可以监听到 --help 事件的发生,那么就可以想办法将
// 之前我们存放在数组里的示例代码显示出来
Reflect.ownKeys(actionsMap).forEach((aName) => {
actionsMap[aName].examples.forEach((item) => {
console.log(" " + item)
})
})
})
program.version(version).parse(process.argv)
// 下拉代码是的loading效果
上代码
/**
* ora 专门用于添加 loading 效果
* 安装 Ora cnpm i ora -D
*/
const ora = require('ora')
const spinner = ora('正在拉取')
spinner.start()
// 中间存放的就是耗时操作
setTimeout(() => {
console.log('正在下载.......')
spinner.succeed('拉取成功')
// fail() info()
}, 2000)
字体颜色
上代码
// cnpm i chalk -D
const chalk = require('chalk')
// 1 设置文字颜色
console.log(chalk.green('zce'))
console.log(chalk.red('zcegg'))
console.log(chalk.white('lg'))
// 2 设置背景色
console.log(chalk.bgBlueBright(chalk.green('拉勾教育66')))
// 3 输出带色的段落文字
console.log(chalk`
{green.bold 车马慢}
{red 从前...慢,一生只够爱一次, 要专一}
`)
问题选择
const inquirer = require('inquirer')
// cnpm i inquire -D
const chalk = require('chalk')
// 这个包可以解决:让我输入、让我选、让我决定的交互问题
/**
* 01 我们需要先设计好要交互的问题
* 02 将这个问题交给 inquirer 来处理,用户按交互执行操作之后,就可以获取到它的输入结果
*/
// 01 设计问题
const quesList = [
{
type: 'checkbox', // 这是一个多选型问题
name: 'feature',
message: '选择默认配置',
pageSize: 2,
choices: ['eslint', 'jest', 'babel', 'webpack', 'vue', 'zce', 'zce的朋友']
}
]
// 2 处理问题
inquirer.prompt(quesList).then((answer) => {
console.log(answer)
})
que.js
module.exports = [
{
type: 'confirm',
name: 'private',
message: '是否为私有仓库'
},
{
type: 'input',
name: 'author',
message: 'author'
},
{
type: 'input',
name: 'description',
message: 'description'
},
{
type: 'input',
name: 'license',
message: 'license'
},
]
const axios = require('axios')
let fs = require('fs')
let ncp = require('ncp') //拷贝包的工具 npm i ncp -D
let { promisify } = require('util') // node自带的包
const inquirer = require('inquirer')
let downLoadFn = require('downLoad-git-repo') // npm i downLoad-git-repo -D
let ora = require('ora')
let Metalsmith = require('metalsmith')// npm i metalsmith -D 渲染用的包
let {render} = require('consolidate').ejs// npm i consolidate -D npm i ejs -D渲染模板用的包 package.json
const { Promise, resolve, reject } = require('bluebird')
const { async } = require('rxjs')
downLoadFn = promisify(downLoadFn) // 让downLoadFn 可以支持 async + await
// 工具方法:添加耗时等待
const addLoading = function(fn) {
return async function(...args) {
let spinner = ora('拉取开始...')
spinner.start()
try {
let ret = await fn(...args)
spinner.succeed('拉取成功')
return ret
} catch (err) {
console.log(err)
spinner.fail('拉取失败')
}
}
}
// 工具方法: 获取仓库工具列表
const fetchRepoList = async function() {
let {data} = await axios.get('https://api.github.com/users/zcegg/repos')
let repos = data.map(item => item.name)
return repos
}
// 工具方法: 获取 tags 列表
const fetchTagList = async function(reponame){
let {data} = await axios.get(`https://api.github.com/repos/zcegg/${reponame}/tags`)
let repos = data.map(item => item.name)
return repos
}
// 工具方法:自定义函数完成 git 仓库下载操作
const downLoadRepo = async function(repo, tag) {
// downLoad-git-repo + 缓存
// 定义缓存目录
let cacheDir = `${process.env[process.platform == 'win32' ? 'USERPROFILE' : 'HOME']}/.tmp`
// 处理 downLoad-git-repo 导出的函数的调用规则 downLoadFn(zcegg/create-nm#tagv)
let api = `zcegg/${repo}`
if (tag) {
api += `#${tag}`
}
// 3自定义一个模板下载后的输出目录
let dest = path.resolve(cacheDir, repo)
let flag = fs.existsSync(dest)
// 4执行下载操作
let spinner = ora('开始下载')
spinner.start()
if (flag) {
spinner.stop()
return dest
} else {
await downLoadFn(api, dest)
spinner.succeed('模板下载成功')
return dest
}
}
module.exports = async function (proName) {
// 1 在远端平台上查询当前目标模板是否存在
/**
* 01 此时我在 github 上已经准备好了二个模板仓库
* 02 在使用 lgg 生成项目的时候,我就想去用这二个模块
* 03 所以我们应该先去 github 上看一下这二个项目是否存在.......
* 04 如果去看呢?
* 发请求---》github提供相应的 API 支持--->
*/
// 此时我们就可以在我们的代码里来获取到目标仓库里所提供的列表
// github 官方 API 有一个限次操作,每小时60次
// 同时他也提供了一些方法让我们可以解限速 5000
// 此时我们可以利用 github 生成 token 的方式来解除限制
// 1 获取模板列表
let repos = await addLoading(fetchRepoList)()
console.log(repos)
// 交互问题设置
let {tmpname} = await inquirer.prompt({
type: 'list',
name: 'tmpname',
message: '请选择目标仓库模板',
choices: repos
})
// 拉取tags列表
let tags = await addLoading(fetchTagList)(tmpname)
// 4依据拉回来的tags 分支进行处理 ([v1, v2...] [])
let dest = null
if (tags.length) {
let {tagv} = await inquirer.prompt({
type: 'confirm',
name: 'tagv',
message: '请选择目标版本',
choices: tagv
})
console.log(tagv)
// 依据选择的模板名称和仓库版本号完成具体的下载操作
dest = await downLoadRepo(tmpname, tagv)
console.log(dest)
} else {
// 当代码运行到这里就说明当前仓库是不存在多个 tag版本
let {isDownLoad} = await inquirer.prompt({
type: 'confirm',
name: 'isDownLoad',
message: '当前不存在多个tag是否直接下载'
})
if(isDownLoad) {
dest = await downLoadRepo(tmpname)
console.log(dest)
} else {
return
}
}
// 将模板下载完成后在本地的自定义缓存中存在着具体文件
// 利用这些文件就可以初始化我们的项目
// 两种情况:一种是项目初始化过程中需要用户动态提供数据,一种是项目中不需要渲染动态数据,直接拷贝即可
// ncp 拷贝包的工具 npm i ncp -D
if (fs.existsSync(path.join(dest, 'que.js'))) { // que.js是自己放入目录的 用于判断
console.log('当前是需要渲染数据的')
await new Promise((resolve, reject) => {
Metalsmith(__dirname) // __dirname其实也没用 只不过不传参数会报错
.source(dest)
.destination(path.resolve(proname))
.use((files, metal, done) => {
console.log(files)
let quesArr = require(path.join(dest, 'que.js'))
let answers = await inquirer.prompt(quesArr)
// 当前answers是传递过来的参数, 我们需要在下一个 use 中使用
// 利用metal.metadata() 来保存所有数据,交给下一个use 进行命名用即可
let meta = metal.metadata()
Object.assign(meta, answers)
delete files['que.js']
done()
})
.use((files, metal, done) => {
console.log(files)
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()
})
.build((err) => {
if (err) {
reject()
} else {
resolve()
}
})
})
} else {
console.log('当前是不需要渲染数据的')
ncp(dest, proname)
}
}
/**
* 1. 选择目标仓库模板列表 【create-nm, create-vue】
* 2. 用户通过交互性问题选择目标模板: create-nm
* 3. 获取目标仓库模板列表:【v0.1.0, v0.1.1】
* 4. 选择指定版本
* 5. 下载之前处理缓存目录, 判断缓存是否存在create-nm , 如果存在则直接返回路径进行使用
* 6. 如果缓存中不存在, 则直接下载, 然后返回缓存目录
* 7. 判断当前缓存目录里的模板文件中是否存在 que.js文件, 如果存在则证明需要渲染,如果不存在则证明不需要渲染
* 8. 如果不需要渲染, 我们就直接将文件拷贝到当前项目根目录下即可
*/