关于前端:项目没亮点前端提效之源码下载器🔥助你上青云

前言

最近在看脚手架的一些源码实现,想着本人做个简易版的脚手架。然而始终没有出发点,搁置了很久。前面在公司拉取代码的时候想到,咱们有时候会去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。

外围

  1. 查看并更新缓存文件
  2. 输出仓库名称、语言等并搜寻
  3. 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💕

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理