Date: 2020-07-02
引入
我们在初始vue-cli的时候,经常使用如下的命令
vue init webpack test
那么是怎么实现的呢?如何自己搭建一个cli工具。
搭建具体流程
-
新建一个
***-cli
的文件夹,如cpm-cli
-
npm init 初始化一个package.json
-
编写自动化下载的脚本
-
使用 gitlab group 或 github organization 存储脚手架项目
-
使用 gitlab api 或 github api 读取项目列表 以 github 为例:
https://api.github.com/orgs/${orgName}/repos
-
使用交互式命令供用户选择项目
-
使用 gitlab api 或 github api 下载脚手架 以 github 为例:
github:${orgName}/${project}
-
重写package.json等文件
-
-
npm publish
npm包
- commander:解析命令行
- download-git-repo:下载git仓库
- inquirer:交互式命令
- axios:调用github接口
- fs:读取和修改文件
- ora:控制台输出loading
- chalk:控制台输出不同颜色的文案
- symbols:控制台console前面的类型
注意事项
- 入口文件顶部:#!/usr/bin/env node
- package.json添加:"bin": { "cpm-cli": "index.js" }
- 安装依赖包时使用-S安装,不要使用-D安装
- 调用program.parse(process.argv)时需要单独写,不要和其他的一起链式调用
- download脚手架偶尔会报错“被拒绝连接connect ECONNREFUSED”,稍后重试即可
- 需要配置github ssh key,否则无法download
代码
#!/usr/bin/env node
'use strict'
/**
* cli脚手架搭建参考:
* https://developer.github.com/v3/
* https://www.jianshu.com/p/edeff714e8a3
* https://blog.csdn.net/weixin_38080573/article/details/97897767
*
* 注意:
* 入口文件顶部添加:#!/usr/bin/env node
* package.josn添加:"bin": { "cpm-cli": "index.js" }
*/
const program = require('commander');
const download = require('download-git-repo');
const inquirer = require('inquirer');
const axios = require('axios');
const fs = require('fs');
const ora = require('ora');
const chalk = require('chalk'); // green/success、red/error、yellow/tip
const symbols = require('log-symbols');
// config
const orgName = 'cpm-cli'; // github organization name
/**
* 抓取github api获取脚手架列表
*/
function getOrgTemplateList () {
return new Promise((resolve, reject) => {
const spinner = ora(chalk.yellow('Request template list ...'));
spinner.start();
axios
.get(`https://api.github.com/orgs/${orgName}/repos`)
.then(res => {
if (res.data && res.data.length) {
const list = res.data.map(item => item.full_name.replace(`${orgName}/`, ''));
spinner.succeed();
resolve(list);
} else {
spinner.fail();
reject();
}
})
.catch((err) => {
spinner.fail();
reject(err);
})
})
}
/**
* 交互式命令获取用户输入和选择
* @param {Array} list 脚手架列表
*/
function showInquirer (list) {
return new Promise((resolve, reject) => {
inquirer
.prompt([
{
type: 'input',
message: 'please input package.json name',
name: 'name',
default: 'demo'
},
{
type: 'input',
message: 'please input package.json description',
name: 'description',
default: 'a demo project'
},
{
type: 'input',
message: 'please input package.json author',
name: 'author',
default: 'chenpengmin'
},
{
type: 'list',
message: 'please choice template',
name: 'template',
choices: list
}
])
.then((answers) => {
resolve(answers);
})
.catch((err) => {
reject(err);
})
})
}
/**
* 下载脚手架
* @param {Object} answers
* @param {String} projectName
*/
function downloadTemp (answers, projectName) {
return new Promise((resolve, reject) => {
const spinner = ora(chalk.yellow(`Downloading ${answers.template} template ...`));
spinner.start();
download(`github:${orgName}/${answers.template}`, projectName, { clone: true }, (err) => {
if (err) {
spinner.fail();
reject(err);
return;
}
spinner.succeed();
resolve();
})
})
}
/**
* 重写package.json,修改name、description、author等参数
* @param {Object} answers
* @param {String} projectName
*/
function rewritePackageJson (answers, projectName) {
const { name, description, author } = answers;
const packagePath = `${projectName}/package.json`;
if (fs.existsSync(packagePath)) {
const packageJson = fs.readFileSync(packagePath);
const packageResult = JSON.stringify(
Object.assign(
{},
JSON.parse(packageJson),
{
name,
description,
author
}
),
null,
'\t'
);
fs.writeFileSync(packagePath, packageResult);
}
}
/**
* 主方法
* @param {String} projectName
*/
async function main (projectName) {
try {
// 第一步:读取github organization列表
const list = await getOrgTemplateList();
// 第二步:交互式命令获取用户选择
const answers = await showInquirer(list);
// 第三步:下载脚手架
await downloadTemp(answers, projectName);
// 第四步:重写package.json
rewritePackageJson(answers, projectName);
console.log(symbols.success, chalk.green(`project ${projectName} was created successfully`));
} catch (err) {
console.log(symbols.error, chalk.red(err || 'something was wrong'));
}
}
program
.version(require('./package.json').version)
.command('init <projectName>')
.action((projectName) => {
if (fs.existsSync(projectName)) {
console.log(symbols.error, chalk.red(`project ${projectName} already exist`));
return;
}
main(projectName);
})
program.parse(process.argv);
{
"name": "cpm-cli",
"version": "0.1.7",
"description": "cpm-cli",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "chenpengmin",
"homepage": "https://github.com/cpm828/cpm-cli",
"keywords": [
"cli",
"cpm",
"cpm-cli"
],
"license": "ISC",
"bin": {
"cpm-cli": "index.js"
},
"dependencies": {
"axios": "^0.19.2",
"chalk": "^4.1.0",
"commander": "^5.1.0",
"download-git-repo": "^3.0.2",
"fs": "0.0.1-security",
"inquirer": "^7.2.0",
"log-symbols": "^4.0.0",
"ora": "^4.0.4",
"shell": "^0.5.0"
}
}