前言
最近在看脚手架的一些源码实现,想着本人做个简易版的脚手架。然而始终没有出发点,搁置了很久。前面在公司拉取代码的时候想到,咱们有时候会去github或gitee下载源码到本地运行调试等。所以这给我了一个启发点,搞一个源码下载器!
下载及应用
npm install dl-repo-cli -g
在命令行输出dl-repo-cli间接回车 下一步即可
或可通过配置options手动配置platform和token的缓存配置
dl-repo-cli -p <github|gitee> -t <your token>
请确保你的platform和token是正确的,否则申请有效。
github token可在settings/Developer settings/Personal access tokens下创立,gitee相似
我的项目预览
我的项目地址
github api地址
gitee api地址
技术前瞻
- axios:发申请的
- chalk:给控制台点色彩瞧瞧
- commander:轻松地定义命令和选项
- fs-extra:提供了比原生
fs
模块更多的性能和实用的API - shelljs:能够让咱们在Node.js中轻松地执行Shell命令和脚本
- inquirer:帮忙咱们构建交互式命令行界面(CLI),并且提供了丰盛的用户输出和抉择形式
- ora:在命令行界面中显示一个动静的加载指示器
上面对这些做个简略的了解,相熟者可间接跳过😘
chalk
chalk用法比简略、且反对链式调用,次要用来给控制台输入辨别色彩
console.log(chalk.blue('Hello') + ' World' + chalk.red('!'));
链式
console.log(chalk.blue.bgRed.bold('Hello world!'));
长这样
commander
根本配置
program
.name('dl-repo-cli')
.description('一个在终端运行的源码下载器cli,反对github、gitee')
.version('1.0.0')
program.parse()
长这样
接下来配置options,能够通过option或addOption增加
program
.name('dl-repo-cli')
.description('一个在终端运行的源码下载器cli,反对github、gitee')
.version('1.0.0')
.addOption(
new Option(
"-p, --platform <platform>",
"代码托管平台(github、gitee)"
).choices([GITHUB, GITEE])
)
.option("-t, --token <token>", "代码托管平台的token")
program.parse()
这里别离应用option和addOption,因为我心愿-p这个参数为github或gitee应用new Option能够对选项进行更详尽的配置
配置完长这样
当咱们应用-p参数时只能为github或gitee
,输出其余时会提醒,就像这样
最初能够通过action获取到options参数并书写后续相干逻辑,如下
另外commande能够注册命令,这里咱们用不到,具体用法参考这里
inquirer
inquirer能够用来创立交互式命令行,用过vue和react的脚手架都晓得,创立我的项目时会各种询问你要装什么。最初生成一个你想要的我的项目,上面是个根本用法。
inquirer.prompt([
{
type: "list",
name: "platform",
message: "请抉择平台",
choices: [
{ name: "github", value: "github" },
{ name: "gitee", value: "gitee" },
],
},
{
type: "password",
name: "token",
message: "请输出token",
validate: function (value) {
if (value.trim() === "") {
return "请输出token";
}
return true;
},
},
])
通过配置type能够实现你想要的询问形式,有input、password、confirm、checkbox等等,此处validate参数用来做校验token不为空。
ora
这个能够用来在控制台加载loading,有时候申请工夫过长、或者代码执行过久给一个loading是一个比拟好的交互方式,代码如下。也是十分好了解
const spinner = ora('加载中......');
spinner.start();
setTimeout(() => {
spinner.stop()
}, 3000);
fs-extra
fs-extra其实就是对node的fs模块做了封装解决,也是比较简单这里不做过多赘述
shelljs
能够用它执行shell脚本,咱们源码下载器最初一个步骤就是应用shell.exec('git clone xxx')
来执行下载,
也能够应用execa这个库实现雷同性能。
注释
首先通过npm init创立package.json并创立bin文件夹以及index.js入口文件。而后批改package.json的main、type、bin属性。其中bin字段
用于注册运行脚本的命令,全局装置后,可在全局执行该命令。这也是为什么装置vue和react脚手架后通过cli能够创立我的项目的起因。而后咱们新建bin和src文件,文件构造如下👇
其中,constant.js是常量文件,utils.js是一些辅助函数,而serve上面则是两个平台的api接口文件
入口
个别入口文件搁置bin目录下,当然你轻易起什么名字都能够。入口文件只有三行代码
#! /usr/bin/env node
import entry from '../src/index.js';
entry()
! /usr/bin/env node,这行代码,它的作用是通知零碎这个脚本文件须要应用node来执行。
申请
申请是基于axios,利用工厂模式,针对github及gitee别离做不同的解决。后续如需反对新的平台可间接继承Serve。
外围
- 查看并更新缓存文件
- 输出仓库名称、语言等并搜寻
- clone 下载
对应代码中这三个函数 👇
checkCache
async function checkCache(t, p) {
if (checkFileIsExist(TEMPFILEPATH)) {
const { token, platform } = fs.readJsonSync(TEMPFILEPATH);
fs.writeJsonSync(TEMPFILEPATH, {
token: t ? t : token,
platform: p ? p : platform,
});
} else if (t && p) {
fs.writeJsonSync(TEMPFILEPATH, {
token: t,
platform: p,
});
} else {
await creatCache();
}
}
async function creatCache() {
const answers = await getAnswers([
{
type: "list",
name: "platform",
message: "请抉择平台",
choices: [
{ name: "github", value: GITHUB },
{ name: "gitee", value: GITEE },
],
},
{
type: "password",
name: "token",
message: "请输出token",
validate: function (value) {
if (value.trim() === "") {
return "请输出token";
}
return true;
},
},
]);
fs.writeJsonSync(TEMPFILEPATH, answers);
}
searchRepos
async function searchRepos() {
const answers = await getAnswers([
{
type: "input",
name: "repoName",
message: "请输出仓库名称",
validate: function (value) {
if (value.trim() === "") {
return "请输出仓库名称";
}
return true;
},
},
{
type: "list",
name: "language",
message: "请抉择语言",
choices: LANGUAGE.map((lan) => ({ name: lan, value: lan })),
},
{
type: "input",
name: "author",
message: "请输出作者",
},
]);
await searchRepoByParams(answers);
}
async function searchRepoByParams({ repoName, language, author }) {
const { token, platform } = fs.readJsonSync(TEMPFILEPATH);
const api = platform === GITHUB ? new githubApi(token) : new giteeApi();
const params =
platform === GITHUB
? {
q: author
? `repo:${author}/${repoName}`
: `${repoName}+language:${language}`,
per_page: 30,
page: 1,
}
: {
q: repoName,
owner: author ? author : undefined,
language: language ? language : undefined,
per_page: 30,
page: 1,
access_token: token,
};
const result = await wrapperLoading(
api.searchRepositories.bind(api, params),
{
loadingInfo: "搜寻中......",
}
);
if (
(result?.total_count === 0 && platform === GITHUB) ||
(result?.length === 0 && platform === GITEE) ||
!result
) {
console.log(chalk.red("搜寻后果为空,请查看搜寻条件是否有误后从新输出"));
await searchRepos();
return;
}
console.log(
`共${chalk.green(result?.total_count || result?.length)}条搜寻后果`
);
const data = platform === GITHUB ? result?.items : result;
const choicesRepo = data.map((item) => {
return {
name: `${chalk.green(item.full_name)}(${item.description})`,
value: item.full_name,
};
});
// 抉择仓库
const { full_name } = await getAnswers([
{
type: "list",
name: "full_name",
message: "请抉择仓库",
choices: choicesRepo,
},
]);
repoFileName = full_name?.split("/")[1];
const tagResult = await wrapperLoading(api.searchTags.bind(api, full_name), {
loadingInfo: "搜寻中......"
});
// 解决tag为空的状况
if (tagResult?.length) {
const tagChoices = tagResult?.map((item) => {
return {
name: `${chalk.green(item.name)}`,
value: item.name,
};
});
const { tag } = await getAnswers([
{
type: "list",
name: "tag",
message: "请抉择tag",
choices: tagChoices,
},
]);
downLoadCommand = `git clone --branch ${tag} git@${platform}.com:${full_name}.git`;
} else {
downLoadCommand = `git clone git@${platform}.com:${full_name}.git`;
}
}
downLoadRepo
async function downLoadRepo() {
if (checkFileIsExist(path.join(process.cwd(), repoFileName))) {
const { confirm } = await getAnswers([
{
type: "confirm",
name: "confirm",
message: "以后执行目录下已存在该我的项目,是否强制更新?",
},
]);
if (confirm) {
execaCommand(true)
} else {
shell.exit(1);
}
} else {
execaCommand(false)
}
}
async function execaCommand(rm) {
if (rm) shell.rm("-rf", repoFileName);
const res = shell.exec(downLoadCommand);
if (res?.code === 0) {
shell.echo(chalk.green("下载胜利 √√√"));
}
}
有余
仓库目前默认搜寻30条,不反对分页。可能还有一些别的问题,后续再欠缺吧(等个有缘人😀)
最初
我的项目地址,如果该文章对你有一点帮忙的话,能够帮忙点个star⭐️。thanks💕
发表回复