1 初始化命令
新建工具包初始化自定義命令執(zhí)行
npm link設(shè)置為全局使用
- 新建bin目錄,創(chuàng)建 cli.js 文件
- 在 cli.js 文件內(nèi)部設(shè)置
#! /usr/bin/env node指令 - 終端切到當(dāng)前工具包目錄,執(zhí)行
npm link創(chuàng)建全局鏈接
2 commander使用
使用 commander 處理自定義幫助命令
- 執(zhí)行
npm i commander -D安裝自定義命令 - 按語法設(shè)置可選 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('創(chuàng)建新項目')
.action((name, args) => {
console.log(name + '執(zhí)行了')
console.log(args)
})
抽離help函數(shù)
- 將 --help 需要做的事情抽離到單獨的文件當(dāng)中
- 將對應(yīng)的函數(shù)導(dǎo)出供外部進行調(diào)用
抽離自定義命令
const {
createActions
} = require('./actions')
const myCommand = function (program) {
//? 01 創(chuàng)建項目命令
program
.command('create <project> [others...]')
.alias('crt')
.description('創(chuàng)建新項目')
.action(createActions)
//? 02 配置項目命令
program
.command('config <set|get> [others...]')
.alias('cfg')
.description('配置項目')
.action((name, args) => {
console.log(name + '執(zhí)行了')
console.log(args)
})
}
module.exports = myCommand
3 其它工具使用
chalk 使用
const chalk = require('chalk')
//? 文字顏色
console.log(chalk.green('綠色'))
console.log(chalk.keyword('red')('前端開發(fā)'))
console.log(chalk.hex('#fff')('前端開發(fā)'))
//? 背景顏色
console.log(chalk.bgGray('帶背景'))
//? 格式化輸出
console.log(chalk.green.bold`
{red 從前慢}
沒有前端開發(fā)
`)
/**
* 使用 chalk 包可以修改命令行終端字體的顏色
* + 提供的關(guān)鍵字設(shè)置顏色:green red orange 等等
* + 提供 keyword 方法接收鍵字
* + 提供 hex 方法接收16進制
* + 可以設(shè)置背景顏色
* + 格式化輸出文字內(nèi)容
*/
ora 使用
import ora from 'ora'
const spinner = ora('正在下載......').start()
spinner.color = 'green'
// spinner.text = 'Loading rainbows'
setTimeout(() => {
// spinner.succeed('下載成功')
// spinner.fail('下載失敗')
spinner.info('下載內(nèi)容')
}, 2000)
/**
* 當(dāng)前版本只支持 es6Module,可以將 package.json 文件中添加 type:module 字段
* ora 實例化對象然后調(diào)用 start
* color設(shè)置文字顏色
* text 設(shè)置文字內(nèi)容
*
* succeed 成功回調(diào)
* fail 失敗回調(diào)
* info
* ......
*/
inquirer 使用
基本使用
const inquirer = require('inquirer')
//? 定義問題
let quesList = [
{
type: 'input',
name: 'username',
message: '用戶名',
validate(an) {
if (!an) {
return '當(dāng)前為必填項'
} else {
return true
}
}
}
]
//? 獲取結(jié)果
inquirer.prompt(quesList).then((an) => {
console.log(an.username)
})
/**
* 基礎(chǔ)字段
* + type 定義問題類型
* + name 將來問題的答案會被保存在一個對象當(dāng)中,這里定義的值就是它的鍵名
* + message 設(shè)置問題的提示信息
* + default 設(shè)置默認值
* + validate 函數(shù),接收參數(shù)為當(dāng)前問題的答案,可以添加判斷的條件來決定后續(xù)走向
*/
遞進問題
const inquirer = require('inquirer')
const quesList = [
{
type: 'confirm',
name: 'isLoad',
message: '是否執(zhí)行下載'
},
{
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 接收一個數(shù)組,給問題提供選項
* when 接收一個參數(shù)是一個問題的答案,可用于判斷當(dāng)前問題是否顯示
*/
總結(jié)
const inquirer = require('inquirer')
// 準備問題
const quesList = [
{
type: 'checkbox',
name: 'feature',
message: '選擇基礎(chǔ)功能',
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: 用于做為答案的鏈出現(xiàn)
* + message: 用于問題的提示信息
* + choices: 出現(xiàn)選項時,設(shè)置為列表選項
* + pageSize: 設(shè)置每頁顯示的問題數(shù)量
* 常見方法:
* + validate 校驗
* + when 判斷
*/
4 功能實現(xiàn)
資料
獲取組織倉庫列表信息
https://api.github.com/orgs/lagoufed/repos
獲取個人倉庫列表信息
https://api.github.com/users/zcegg/repos
獲取指定倉庫版本號
https://api.github.com/repos/zcegg/create-nm/tags
查詢訪問次數(shù)
curl -i https://api.github.com/users/octocat
headers={"Authorization":"token "+"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"}
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
步驟分析
已完成:命令、交互工具、業(yè)務(wù)參數(shù)
未完成:
查詢遠端模板列表(添加交互)
查詢選中模板下是否存在多個版本
下載指定模板指定版本
將模板緩存在指定位置
渲染數(shù)據(jù)寫入到指定的目錄
請求次數(shù)限制
//! 發(fā)送請求
let ret = await axios('https://api.github.com/users/zcegg/repos')
console.log(ret.data)
解決次數(shù)限制
//! 發(fā)送請求
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: '選擇目標(biāo)模板',
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('直接執(zhí)行下載')
}
處理緩存路徑
const toUnixPath = require('../utils/toUnixPath')
// console.log(process.env) // 查詢環(huán)境變量
// console.log(process.platform) // 查詢當(dāng)平臺關(guān)鍵字
console.log(process.env['USERPROFILE'])
console.log(toUnixPath(`${process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']}` + '/.tmp'))
初始化下載函數(shù)
//! 工具方法之下載操作
const downLoadRepo = function (repo, tag) {
//? 定義緩存目錄
const cacheDir = toUnixPath(`${process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']}` + '/.tmp')
//? 處理參數(shù)
let api = `zcegg/${repo}`
if (tag) api += `#/${tag}`
console.log(cacheDir)
console.log(api)
}
實現(xiàn)下載操作
//* 導(dǎo)出下載函數(shù)
let downloadFn = require('download-git-repo')
downloadFn = promisify(downloadFn)
//? 定義緩存路徑
const cacheDir = toUnixPath(`${process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']}` + '/.tmp')
//? 處理參數(shù)
let api = `zcegg/${repo}`
if (tag) api += `#/${tag}`
//? 定義緩存目錄
const dest = tag ? path.resolve(cacheDir, repo, tag) : path.resolve(cacheDir, repo)
//? 執(zhí)行下載操作
const spinner = ora('正在下載......').start()
await downloadFn(api, dest)
spinner.succeed('下載成功')
使用緩存
//? 執(zhí)行下載操作(判斷緩存中是否存)
if (!fs.existsSync(dest)) {
//* 緩存目錄中不存在則直接下載
const spinner = ora('正在下載......').start()
await downloadFn(api, dest)
spinner.succeed('下載成功')
}
//? 返回地址用于數(shù)據(jù)讀取
return dest
無數(shù)據(jù)渲染
//? 05 下載完成之后判斷是否需要渲染數(shù)據(jù),從而生成本地的項目
// 如果需要渲染則在 package.json 中使用 ejs 語法,同時提前通過 qus.js 來準備問題
if (fs.existsSync(path.join(dest, 'que.js'))) {
console.log('需要渲染')
} else {
// 不需要渲染就直接拷貝
ncp(dest, project)
}
metalsmith使用
//? 05 下載完成之后判斷是否需要渲染數(shù)據(jù),從而生成本地的項目
// 如果需要渲染則在 package.json 中使用 ejs 語法,同時提前通過 qus.js 來準備問題
if (fs.existsSync(path.join(dest, 'que.js'))) {
//! 需要數(shù)據(jù)渲染
await new Promise((resolve, reject) => {
MetalSmith(__dirname)
.source(dest)
.destination(path.resolve(project))
.use((files, metal, done) => {
//* files 是當(dāng)前目錄下所有的文件信息
//*
console.log(files)
done()
})
.build((err) => {
if (err) {
reject()
} else {
resolve()
}
})
})
} else {
// 不需要渲染就直接拷貝
ncp(dest, project)
}
設(shè)置問題
if (fs.existsSync(path.join(dest, 'que.js'))) {
//! 需要數(shù)據(jù)渲染
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)
}
問題數(shù)據(jù)傳遞
if (fs.existsSync(path.join(dest, 'que.js'))) {
//! 需要數(shù)據(jù)渲染
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)
//! 當(dāng) answer 的答案我們需要在下一個 use 中進行使用
//! 利用 metal.metadata() 來保存所有的數(shù)據(jù),交給下一個 use 進行使用即可
let meta = metal.metadata()
Object.assign(meta, answer)
// 這步操作完成之后,que.js 文件就沒有用了,不需要拷貝至項目的目錄
// delete files['que.js']
done()
})
.use((files, metal, done) => {
// 獲取上一個 use 中拿到的用戶數(shù)據(jù)
let data = metal.metadata()
console.log(data)
done()
})
.build((err) => {
if (err) {
reject()
} else {
resolve()
}
})
})
} else {
// 不需要渲染就直接拷貝
ncp(dest, project)
}
數(shù)據(jù)渲染
.use((files, metal, done) => {
// 獲取上一個 use 中拿到的用戶數(shù)據(jù)
let data = metal.metadata()
//? 找到那些需要渲染數(shù)據(jù)的具體文件,找到之后將它們的內(nèi)容轉(zhuǎn)為字符串
//? 轉(zhuǎn)為字符串之后,接下來就可以針對于字符串進行替換實現(xiàn)渲染
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()
執(zhí)行 npm
const { spawn } = require('child_process')
// 執(zhí)行 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 執(zhí)行 npm install
const run_command = process.platform === 'win32' ? 'npm.cmd' : 'npm'
await commandSpawn(run_command, ['install'], { cwd: `./${project}` })
//?07 執(zhí)行 run serve
commandSpawn(run_command, ['run', 'serve'], { cwd: `./${project}` })