自定义脚手架
commander: 参数解析 比如 --help
inquirer: 交互式命令工具,实现命令行选择
ora: loading
download-git-repo: 在git中下载模版
chalk: 粉笔画出控制台各种颜色
metalsmith: 读取所有文件,实现模版渲染
consolidate: 实现统一模版引擎
ncp: 拷贝文件夹
commander
#! /usr/bin/env node
require('../src/main.js');
"bin": {
"jeffywin-cli": "./bin/www"
},
-
- npm link命令 => 链接到本地环境
jeffywin-cli 命令执行bin中的www文件,并通过sudo npm link去链接这个命令
-
- 解析用户命令行输入的参数
const commander = require('commander');
const { version } = require('./constants.js');
commander.version(version).parse(process.argv);
commander
.command(action)
.alias(mapActions[action].alias)
.description(mapActions[action].description)
.action(() => {
if(action !== '*') {
require(path.resolve(__dirname, action))(...process.argv.slice(3))
}
})
const mapActions = {
create: {
alias: 'c',
description: 'create project',
examples: [
'jeffywin-cli create <project-name>'
]
},
config: {
alias: 'conf',
description: 'config project variable',
examples: [
'jeffywin-cli config set <k><v>',
'jeffywin-cli config get <k>'
]
},
'*': {
alias: '',
description: 'command not found',
examples: []
}
}
module.exports = {
mapActions
}
- 拉取代码思路
- 获取项目列表,通过api.github.com
- 获取tag分支
- 下载代码保存到本地 /Users/xxx/.template
- 如果是简单模版,直接ncp复制到当前项目
- 复杂模版,检测如果有ask.js文件,拿到用户输入,通过consolidate渲染
const axios = require('axios');
const ora = require('ora');
const Inquirer = require('inquirer');
const {promisify} = require('util');
const path = require('path');
const fs = require('fs');
let downloadGitRepo = require('download-git-repo');
const Metalsmith = require('metalsmith');
let {render} = require('consolidate').ejs;
render = promisify(render);
const {downloadDirectory} = require('./constants.js');
let ncp = require('ncp');
ncp = promisify(ncp);
downloadGitRepo = promisify(downloadGitRepo);
const featchRepoList = async () => {
const api = 'https://api.github.com/users/jeffywin/repos';
const {data} = await axios.get(api)
return data;
}
const waitLoading = (fn, message) => async (...args) => {
const spinner = ora(message);
spinner.start();
const data = await fn(...args);
spinner.succeed();
return data;
}
const fechTagList = async (repo) => {
const { data } = await axios.get(`https://api.github.com/repos/jeffywin/${repo}/tags`);
return data;
}
const download = async (repo, tag) => {
let api = `jeffywin/${repo}`;
if (tag) {
api += `#${tag}`;
}
const dest = `${downloadDirectory}/${repo}`
await downloadGitRepo(api, dest);
return dest
}
module.exports = async (projectName) => {
let repos = await waitLoading(featchRepoList, 'featching template ....')();
repos = repos.map((item) => item.name);
const { repo } = await Inquirer.prompt({
name: 'repo',
type: 'list',
message: '请选择一个模版',
choices: repos
})
let tags = await waitLoading(fechTagList, 'featching tags ...')(repo);
tags = tags.map((item) => item.name);
const { tag } = await Inquirer.prompt({
name: 'tag',
type: 'list',
message: '请选择一个分支',
choices: tags
})
const result = await waitLoading(download, 'downing template...')(repo, tag);
if (!fs.existsSync(path.resolve(result, 'ask.js'))) {
await ncp(result, path.resolve(projectName));
} else {
await new Promise((resolve, reject) => {
Metalsmith(__dirname)
.source(result)
.destination(path.resolve(projectName))
.use(async (files, metal, done) => {
const args = require(path.join(result, 'ask.js'))
const obj = await Inquirer.prompt(args)
let meta = metal.metadata();
Object.assign(meta, obj);
delete files['ask.js']
done()
})
.use((files, metal, done) => {
const metalData = 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, metalData);
files[file].contents = Buffer.from(content);
}
}
})
done()
})
.build((err, files) => {
if (err) {
reject()
} else {
resolve()
}
})
})
}
}